Это O (n), но аргумент поиска индекса - красная сельдь.
Как работает итерация
Скорость поиска индекса будет иметь значение, если вы сделаете это:
for index in range(len(mystring)):
char = mystring[index]
...
Но вы не используете индексы. Вы используете итератор , точнее string итератор:
>>> iter('test')
<str_iterator object at 0x03569820>
Этот итератор запоминает, где он находится в строке (как угодно, не не нужно быть "индексом"). И может неоднократно запрашиваться следующий символ:
>>> it = iter('test')
>>> next(it)
't'
>>> next(it)
'e'
>>> next(it)
's'
>>> next(it)
't'
>>> next(it)
Traceback (most recent call last):
File "<pyshell#200>", line 1, in <module>
next(it)
StopIteration
Это то, что делает for
-l oop. Он создает этот итератор и затем неоднократно запрашивает его для следующего значения, пока итератор не скажет ему остановиться. И каждое значение, которое он получает от итератора, он передает вашему коду в названной вами переменной. Другими словами, for
-l oop - это просто посредник между итератором и вашим кодом в теле l oop.
В отличие от строк, представьте простой связанный список , Поиск индекса в связанном списке занимает O (n), поскольку каждый поиск требует перехода от начала связанного списка к нужному узлу. Но вы все равно можете легко выполнить полную итерацию в O (n), верно? И объект итератора будет хранить ссылку на следующий узел, поэтому он предоставит его за O (1) времени (и затем переместит свою ссылку вперед). Таким образом, для связанного списка, for
-l oop с использованием индексов будет принимать O (n 2 ), но обычный pythoni c for
-l oop (неявно используя связанный итератор списка) будет O (n).
Вы можете даже mimi c a for
-l oop с while
-l oop и явным итератором, который вы обрабатываете сами вместо этого позволить for
-l oop справиться с этим за вас. Вместо
for char in 'test':
print(char)
сделайте это:
it = iter('test')
while True:
try:
char = next(it)
except StopIteration:
break
print(char)
Это напечатает это:
t
e
s
t
Временная сложность итерации строки
Давайте посмотрите на исходный код Я не очень знаком с этим, но я опишу, во что я верю. Помните, что это str_iterator
? То, что str
в Python 3 называется unicode
в Python 2, и это все еще имя в C исходном коде Python 3. Так что в unicodeobject.c
мы найдите строку "str_iterator"
, и она находится в разделе «Итератор Юникода». Отрывки:
/********************* Unicode Iterator **************************/
typedef struct {
...
Py_ssize_t it_index;
PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} unicodeiterobject;
...
unicodeiter_next(unicodeiterobject *it)
{
...
seq = it->it_seq;
...
void *data = PyUnicode_DATA(seq);
Py_UCS4 chr = PyUnicode_READ(kind, data, it->it_index);
item = PyUnicode_FromOrdinal(chr);
if (item != NULL)
++it->it_index;
return item;
...
}
...
PyTypeObject PyUnicodeIter_Type = {
...
"str_iterator", /* tp_name */
...
};
Так что это unicodeiterobject
с указателем it_seq
на строку, которую он повторяет, и индексом it_index
. А функция next
использует их для получения следующего символа, увеличивает индекс и возвращает символ. Хорошо, оказывается, что итератор использует индекс для внутреннего использования. Но это индексирование находится на более низком, более прямом внутреннем уровне, чем индексирование, которое вы делаете из Python, в котором используется функция unicode_getitem
:
static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
void *data;
enum PyUnicode_Kind kind;
Py_UCS4 ch;
...
if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return unicode_char(ch);
}
Оба выглядят одинаково и в конечном итоге используют PyUnicode_READ(kind, data, index)
, Я не мог найти этот, но он должен быть довольно простым и O (1), делая всю итерацию O (n).
Еще одна вещь: ответ / вопрос , что @NickParsons, о котором говорилось выше, имеет дело с беспокойством по поводу Python использования многобайтовых символьных представлений переменного размера, которые могли бы выполнять поиск по индексу O (n) вместо O (1). Даже если бы это было , это затронуло бы только функцию unicode_getitem
. Не итератор str_iterator
. Потому что итератор, безусловно, будет использовать не наивный «строковый индекс», а указатель на первый байт следующего символа, чтобы он мог читать и переходить в O (1). Таким образом, вся его итерация все равно будет O (n).