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

Как продлить, имитировать или эмулировать функцию диапазона?

Я сделал небольшую функцию генератора для диапазонов символов:

>>> def crange(start, end):
...     for i in range(ord(start), ord(end)+1):
...             yield chr(i)
...

И тогда я могу это сделать:

>>> print(*crange('a','e'))
a b c d e

Ура! Но это не работает:

>>> crange('a','e')[::2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable

И это работает, но O (n), в отличие от range O (1):

>>> 'y' in crange('a','z')
True

Это означает, что для поиска номера персонажа 109999 из 110 000 требуется около 0,35 секунды. 109999 in range(110000), конечно, быстро.

В этот момент моя первая мысль заключалась в простом подклассе. К сожалению:

>>> class A(range):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'range' is not an acceptable base type

Итак, я думаю, мне придется каким-то образом подражать ему, что позволяет передавать символы в качестве аргументов, работает как range внутри, и создает символы. К сожалению, я не уверен, как действовать дальше. Я попробовал dir():

>>> print(*dir(range), sep='\n')
__class__
__contains__
__delattr__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__getitem__
__gt__
__hash__
__init__
__iter__
__le__
__len__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__reversed__
__setattr__
__sizeof__
__str__
__subclasshook__
count
index
start
step
stop

который позволяет мне видеть, какие функции там, но я не уверен, что они делают, или как range использует их. Я искал источник для range, но он в C, и я не знаю, где найти его оболочку Python (у него есть один, правда?).

Куда я иду отсюда, и я должен пойти туда?

4b9b3361

Ответ 1

Чтобы добавить к Martin Konecny ​​ответ. Вероятно, вы хотите использовать внутренний диапазон для всего и преобразовать между chr и ord.

class crange:
    def __init__(self, *args, **kwargs):
        args = [ord(arg) for arg in args]
        kwargs = {key: ord(val) for key, val in kwargs.items()}
        self.range = range(*args, **kwargs)

    def __iter__(self):
        for n in self.range:
            yield chr(n)

    def __contains__(self, c):
        return ord(c) in self.range

    def __getitem__(self, i):
        if isinstance(i, slice):
            ret = crange('\x00')
            ret.range = self.range[i]
            return ret
        else:
            return chr(self.range[i])

    def __repr__(self):
        return  "crange({}, {})".format(
            repr(chr(self.range.start)), repr(chr(self.range.stop)))

r = crange('a', 'f')
print(list(r))
print('b' in r)
print('f' in r)
print(r[:2])

Другими словами: если мы не можем подклассировать его, мы можем использовать композицию объекта.

Ответ 2

В этот момент моя первая мысль заключалась в простом подклассе.

range была функцией в Python2 и "окончательном" классе в Python3 (подробнее здесь) - в обоих случаях вы не можете подкласса. Вам нужно будет создать класс crange, который простирается от object в качестве базового типа.

class crange(object):

И это работает, но O (n), в отличие от диапазона O (1)

В Python 3 существует метод __contains__, который вы определите для своего объекта.

Для объектов, которые не определяют __contains__(), тест членства сначала пытается выполнить итерацию через __iter__(), а затем старый протокол итерации последовательности через __getitem__(), см. этот раздел в справочной системе языка.

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

Для простого примера, если ваш диапазон составляет от 1 до 1 000 000, тривиально определить, находится ли 23546 в этом диапазоне (1 < 23546 < 1000000). Конечно, фактическая реализация немного сложнее и добавляет способность обрабатывать шаги шага и т.д.

Относительно:

Ура! Но это не работает: >>> crange('a','e')[::2]

В этом случае вам нужно определить __getitem__ на вашем объекте. Вот пример некоторых из необходимых методов:

class crange(object):
    def __init__(self, start, end, step=1):
        # initialize your range object
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        # enable iteration over your object
        # (assume step size is 1)
        for i in range(ord(self.start), ord(self.end)+1):
            yield chr(i)

    def __getitem__(self, i):
        # enable accessing items in your range by index
        # also enable crange('a','e')[::2]
        # (assuming step size of 1)
        if isinstance( i, slice ):
            # implement slicing 
        else:
            return chr(ord(self.start) + i)

    def __contains__(self, char):
        # enable O(1) determination of whether a value is in your range
        # (assume step size is 1)
        return ord(self.start) <= ord(char) < ord(self.end)

    def __len__(self):
        # return length (assuming step size of 1)
        return ord(self.end) - ord(self.start)