Проверить, находится ли какой-либо (весь) символ строки в заданном диапазоне

У меня есть строка, содержащая символы юникода (кириллица):

myString1 = 'Австрия'
myString2 = 'AustriЯ'

Я хочу проверить, все ли элементы в строке являются английскими (ASCII). Теперь я использую цикл:

for char in myString1:
    if ord(s) not in range(65,91):
         break

Поэтому, если я нахожу первый неанглийский элемент, я прерываю цикл. Но для данного примера вы можете видеть, что строка может содержать много английских символов и Unicode в конце. Таким образом я проверю всю строку. Кроме того, если вся строка на английском языке, я все равно проверяю каждый символ.

Есть ли более эффективный способ сделать это? Я думаю о чем-то вроде:

if any(myString[:]) is not in range(65,91)

person Mikhail_Sam    schedule 26.12.2017    source источник
comment
Возможный дубликат Как проверить, находится ли строка в Python находится в ASCII?   -  person YGouddi    schedule 26.12.2017
comment
Хм, range(65,91) совсем маленький: только прописные буквы! 'Austria 'будет отклонен, потому что он содержит строчные буквы, а ord('a') уже 97. Даже не говоря о пунктуации и цифрах, которые действительно находятся в кодировке ASCII. Что именно вы пытаетесь сделать?   -  person Serge Ballesta    schedule 26.12.2017
comment
@SergeBallesta, ты прав. На самом деле я использую два интервала: range(65,91) и range(48,58). Поэтому я использую двойное условие в заявлении if.   -  person Mikhail_Sam    schedule 26.12.2017
comment
Разве любое слово в названии не должно быть всем?   -  person timgeb    schedule 26.12.2017
comment
Были бы в ваших строках только буквы или могли бы быть также пробелы и точки?   -  person cs95    schedule 26.12.2017
comment
@ cᴏʟᴅsᴘᴇᴇᴅ они могут содержать что угодно. Цифры, знаки препинания, пробелы и юникод   -  person Mikhail_Sam    schedule 26.12.2017
comment
@Mikhail_Sam А, значит, ни один из этих ответов, ни ваше исходное решение не будут работать, если вы не учтете их все.   -  person cs95    schedule 26.12.2017


Ответы (4)


Вы можете ускорить проверку, используя set (O(1) содержит проверку), особенно если вы проверяете несколько строк для одного и того же диапазона, поскольку для создания начального набора также требуется одна итерация. Затем вы можете использовать all для раннего итерационного шаблона, который лучше подходит чем any здесь:

import string

ascii = set(string.ascii_uppercase)
ascii_all = set(string.ascii_uppercase + string.ascii_lowercase)

if all(x in ascii for x in my_string1):
    # my_string1 is all ascii

Конечно, любую all конструкцию можно преобразовать в any с помощью закона ДеМоргана:

if not any(x not in ascii for x in my_string1):
    # my_string1 is all ascii

Обновлять:

Один хороший подход на основе чистого набора, не требующий полной итерации, как указано Artyer:

if ascii.issuperset(my_string1):
    # my_string1 is all ascii
person schwobaseggl    schedule 26.12.2017
comment
Умное решение, оно мне нравится! - person user1767754; 26.12.2017
comment
Красиво и элегантно. +1 - person Sohaib Farooqi; 26.12.2017
comment
Интересное решение. Можете ли вы дать мне еще один совет: что, если мне нужны не только ascii_uppercase, но и все символы ascii? Могу ли я использовать только `(string.ascii) '? - person Mikhail_Sam; 26.12.2017
comment
@Mikhail_Sam Я добавил эту опцию: нужно комбинировать ascii_lowercase и ascii_uppercase - person schwobaseggl; 26.12.2017
comment
Или мы можем использовать ascii_letters :) Спасибо! У меня есть еще два интересных вопроса: как вы думаете - какой метод подойдет вам или stackoverflow.com/a/47976402/4960953 быть быстрее? И второй: что вы думаете об этом решении: stackoverflow.com/a/196391/4960953 - person Mikhail_Sam; 26.12.2017
comment
@Mikhail_Sam С алгоритмической точки зрения мое решение должно быть лучше, потому что преобразование набора Даниэля Санчеса строки всегда будет повторять всю строку в то время как мой сломается на первом не-ascii char. Думаю, действительно ли это имеет значение или преобладает C-оптимизация заданных операций, во многом зависит от ваших данных. - person schwobaseggl; 26.12.2017
comment
@schwobaseggl Почему бы не получить лучшее из обоих? ascii = set(string.ascii_letters); if ascii.issuperset(my_string): - person Artyer; 26.12.2017
comment
@Artyer Очень хорошее замечание и весьма логичное, учитывая обсуждение :) Я добавил это. - person schwobaseggl; 26.12.2017

Другой способ, как предлагает @schwobaseggl, но с использованием методов полного набора:

import string
ascii = string.ascii_uppercase + string.ascii_lowercase
if set(my_string).issubset(ascii):
    #myString is ascii
person Netwave    schedule 26.12.2017
comment
Должно быть быстрее: set(my_string).issubset(string.ascii_uppercase + string.ascii_lowercase) - person cs95; 26.12.2017

re выглядит довольно быстро:

import re

# to check whether any outside ranges (->MatchObject) / all in ranges (->None)
nonletter = re.compile('[^a-zA-Z]').search

# to check whether any in ranges (->MatchObject) / all outside ranges (->None)
letter = re.compile('[a-zA-Z]').search

bool(nonletter(myString1))
# True

bool(nonletter(myString2))
# True

bool(nonletter(myString2[:-1]))
# False

Контрольные показатели для двух примеров OP и одного положительного (набор - @schwobaseggl, набор - @DanielSanchez):

Австрия
re               0.48832818 ± 0.09022105 µs
set              0.58745548 ± 0.01759877 µs
setset           0.81759223 ± 0.03595184 µs
AustriЯ
re               0.51960442 ± 0.01881561 µs
set              1.03043942 ± 0.02453405 µs
setset           0.54060076 ± 0.01505265 µs
tralala
re               0.27832978 ± 0.01462306 µs
set              0.88285526 ± 0.03792728 µs
setset           0.43238688 ± 0.01847240 µs

Код теста:

import types
from timeit import timeit
import re
import string
import numpy as np

def mnsd(trials):
    return '{:1.8f} \u00b1 {:10.8f} \u00b5s'.format(np.mean(trials), np.std(trials))

nonletter = re.compile('[^a-zA-Z]').search
letterset = set(string.ascii_letters)

def f_re(stri):
    return not nonletter(stri)

def f_set(stri):
    return all(x in letterset for x in stri)

def f_setset(stri):
    return set(stri).issubset(letterset)

for stri in ('Австрия', 'AustriЯ', 'tralala'):
    ref = f_re(stri)
    print(stri)
    for name, func in list(globals().items()):
        if not name.startswith('f_') or not isinstance(func, types.FunctionType):
            continue
        try:
            assert ref == func(stri)
            print("{:16s}".format(name[2:]), mnsd([timeit(
                'f(stri)', globals={'f':func, 'stri':stri}, number=1000) * 1000 for i in range(1000)]))

        except:
            print("{:16s} apparently failed".format(name[2:]))
person Paul Panzer    schedule 26.12.2017
comment
Спасибо за интересное решение! Не очевидно, что re работает быстрее, чем другие методы! - person Mikhail_Sam; 26.12.2017
comment
@Mikhail_Sam Он делает аналогичные вещи, такие как решения loop / any / all, но я предполагаю, что цикл происходит в скомпилированном коде, поэтому он быстрее. Из трех примеров кажется, что создание MatchObject отвечает за значительную часть затрат, потому что, если нет совпадения (последний пример), мы работаем намного быстрее. - person Paul Panzer; 26.12.2017
comment
Я понял! Но я думал, что регулярные ругательства сами по себе медленны, и всегда старался их не использовать, если это возможно. Но теперь я вижу, что ошибался :) - person Mikhail_Sam; 27.12.2017

Невозможно избежать повторения. Однако вы, безусловно, можете сделать его более эффективным, выполняя not 65 <= ord(s) <= 91, а не сравнивая с диапазоном.

person Daniel Roseman    schedule 26.12.2017
comment
OP не пометил Python3, но, вероятно, следует упомянуть, что проверка членства на range не должна быть заметно медленнее в Python3, чем сравнение с целыми числами. - person timgeb; 26.12.2017