Подтвердить что ты не робот

Эффективно проверяя, что строка состоит из одного символа в Python

Что такое эффективный способ проверить, что строка s в Python состоит всего из одного символа, например 'A'? Что-то вроде all_equal(s, 'A'), которое будет вести себя следующим образом:

all_equal("AAAAA", "A") = True

all_equal("AAAAAAAAAAA", "A") = True

all_equal("AAAAAfAAAAA", "A") = False

Два, казалось бы, неэффективные способы: сначала преобразовать строку в список и проверить каждый элемент, или второй - использовать регулярное выражение. Есть ли более эффективные способы или они лучше всего подходят для Python? Спасибо.

4b9b3361

Ответ 1

Это, безусловно, самый быстрый, в несколько раз быстрее, чем даже count(), просто поразив это превосходным набором времени по умолчанию для mgilson:

s == len(s) * s[0]

Здесь вся проверка выполняется внутри кода Python C, который просто:

  • выделяет символы len (s);
  • заполняет пробел первым символом;
  • сравнивает две строки.

Чем длиннее строка, тем больше бонус времени. Однако, как пишет mgilson, он создает копию строки, поэтому, если длина строки составляет много миллионов символов, это может стать проблемой.

Как мы видим из результатов синхронизации, как правило, самые быстрые способы решения задачи не выполняют никакого кода Python для каждого символа. Тем не менее, решение set() также выполняет всю работу внутри кода C библиотеки Python, но оно все еще медленное, возможно, из-за использования строки через интерфейс объекта Python.

UPD: Что касается пустого случая строки. Что делать с этим сильно зависит от задачи. Если задание "проверить, являются ли все символы в строке одинаковыми", s == len(s) * s[0] является допустимым ответом (никакие символы не означают ошибку, а исключение - в порядке). Если задача "проверить, есть ли только один уникальный символ", пустая строка должна дать нам False, а ответ будет s and s == len(s) * s[0] или bool(s) and s == len(s) * s[0], если вы предпочитаете получать логические значения. Наконец, если мы понимаем задачу как "проверить, нет ли других символов", результатом для пустой строки является True, а ответ - not s or s == len(s) * s[0].

Ответ 2

>>> s = 'AAAAAAAAAAAAAAAAAAA'
>>> s.count(s[0]) == len(s)
True

Это не короткое замыкание. Версия, которая выполняет короткое замыкание, будет:

>>> all(x == s[0] for x in s)
True

Однако у меня есть ощущение, что благодаря оптимизированной реализации C версия с коротким замыканием, вероятно, будет работать лучше на некоторых строках (в зависимости от размера и т.д.)


Вот простой timeit script, чтобы проверить некоторые другие опубликованные варианты:

import timeit
import re

def test_regex(s,regex=re.compile(r'^(.)\1*$')):
    return bool(regex.match(s))

def test_all(s):
    return all(x == s[0] for x in s)

def test_count(s):
    return s.count(s[0]) == len(s)

def test_set(s):
    return len(set(s)) == 1

def test_replace(s):
    return not s.replace(s[0],'')

def test_translate(s):
    return not s.translate(None,s[0])

def test_strmul(s):
    return s == s[0]*len(s)

tests = ('test_all','test_count','test_set','test_replace','test_translate','test_strmul','test_regex')

print "WITH ALL EQUAL"
for test in tests:
    print test, timeit.timeit('%s(s)'%test,'from __main__ import %s; s="AAAAAAAAAAAAAAAAA"'%test)
    if globals()[test]("AAAAAAAAAAAAAAAAA") != True:
        print globals()[test]("AAAAAAAAAAAAAAAAA")
        raise AssertionError

print
print "WITH FIRST NON-EQUAL"
for test in tests:
    print test, timeit.timeit('%s(s)'%test,'from __main__ import %s; s="FAAAAAAAAAAAAAAAA"'%test)
    if globals()[test]("FAAAAAAAAAAAAAAAA") != False:
        print globals()[test]("FAAAAAAAAAAAAAAAA")
        raise AssertionError

На моей машине (OS-X 10.5.8, core2duo, python2.7.3) с этими надуманными (короткими) строками, str.count курит set и all, и немного бьет str.replace, но выделяется str.translate, а strmul в настоящее время лидирует с хорошим запасом:

WITH ALL EQUAL
test_all 5.83863711357
test_count 0.947771072388
test_set 2.01028490067
test_replace 1.24682998657
test_translate 0.941282987595
test_strmul 0.629556179047
test_regex 2.52913498878

WITH FIRST NON-EQUAL
test_all 2.41147494316
test_count 0.942595005035
test_set 2.00480484962
test_replace 0.960338115692
test_translate 0.924381017685
test_strmul 0.622269153595
test_regex 1.36632800102

Тайминги могут быть немного (или даже значительно?) разными между разными системами и с разными строками, поэтому стоит обратить внимание на фактическую строку, которую вы планируете пропустить.

В конце концов, если вы нажмете лучший случай для all достаточно, и ваши строки достаточно длинны, вы можете рассмотреть этот. Это лучший алгоритм... Я бы избегал решения set, хотя я не вижу случая, когда он мог бы выбить решение count.

Если память может быть проблемой, вам нужно избегать str.translate, str.replace и strmul, поскольку они создают вторую строку, но в наши дни это обычно не является проблемой.

Ответ 3

Вы можете преобразовать в набор и проверить, что есть только один элемент:

len(set("AAAAAAAA"))

Ответ 4

Попробуйте использовать встроенную функцию all:

all(c == 'A' for c in s)

Ответ 5

Добавление другого решения этой проблемы

>>> not "AAAAAA".translate(None,"A")
True

Ответ 6

Если вам нужно проверить, являются ли все символы в строке одинаковыми и равны заданному символу, вам нужно удалить все дубликаты и проверить, равен ли конечный результат одиночному символу.

>>> set("AAAAA") == set("A")
True

Если вы хотите найти, есть ли дубликат, просто проверьте длину

>>> len(set("AAAAA")) == 1
True

Ответ 7

Интересные ответы пока. Здесь другое:

flag = True
for c in 'AAAAAAAfAAAA':
    if not c == 'A': 
        flag = False
        break

Единственное преимущество, которое я могу придумать, заключается в том, что ему не нужно проходить всю строку, если он обнаруживает несогласованный характер.

Ответ 8

not len("AAAAAAAAA".replace('A', ''))