Какова временная сложность итерации по двухсторонней очереди в Python?

Какова временная сложность итерации, а точнее каждой итерации по двухсторонней очереди из библиотеки коллекций в Python?

Пример такой:

elements = deque([1,2,3,4])
for element in elements:
  print(element)

Является ли каждая итерация постоянной операцией O (1)? или он выполняет линейную операцию O (n), чтобы добраться до элемента на каждой итерации?

В Интернете есть много ресурсов для временной сложности со всеми другими методами deque, такими как appendleft, append, popleft, pop. Кажется, нет никакой информации о временной сложности об итерации двухсторонней очереди.

Спасибо!


person perseverance    schedule 16.11.2017    source источник
comment
deque = двусторонняя очередь. Таким образом, поиск n-го элемента равен O(n)   -  person Jean-François Fabre    schedule 16.11.2017
comment
Да. Но вы можете получить линейную итерацию, если сделаете for element in elements:   -  person juanpa.arrivillaga    schedule 16.11.2017
comment
Другими словами, итерация по двухсторонней очереди является линейной, но повторное индексирование двухсторонней очереди является квадратичным.   -  person juanpa.arrivillaga    schedule 16.11.2017
comment
@juanpa.arrivillaga теперь я полностью понимаю, почему for i in range(len(elements)) ужасно непитоновский. о, и len(elements), вероятно, O(n), если не реализовано какое-то кэширование.   -  person Jean-François Fabre    schedule 16.11.2017
comment
@Jean-FrançoisFabre Итак, чтобы уточнить, на каждой итерации выполняется операция O (n), чтобы добраться до элемента [i]? Несмотря на то, что это по существу двусвязный список, и каждое движение влево или вправо может быть O (1)?   -  person perseverance    schedule 16.11.2017
comment
@perseverance да, если вы выполняете индексацию, это то, что должно произойти. К счастью, если вы хотите выполнить итерацию двухсторонней очереди, вам не нужно этого делать. Вы почти никогда не должны делать for i in range(len(elements)). Это просто уродливо и подвержено ошибкам с list объектами, которые имеют произвольный доступ, но с deque объектами вы почувствуете боль операции индексации O (N).   -  person juanpa.arrivillaga    schedule 16.11.2017
comment
@juanpa.arrivillaga Это то, что я ищу. Таким образом, на каждой итерации это фактически операция O (1), и в целом, чтобы добраться до конца итерации, это операция O (n).   -  person perseverance    schedule 16.11.2017
comment
каждая итерация (правильно выполненная) просто берет следующий элемент очереди, поэтому да O (1).   -  person Jean-François Fabre    schedule 16.11.2017
comment
@perseverance: это было бы O (1) за итерацию, если бы вы делали это правильно, но вы делаете это неправильно.   -  person user2357112 supports Monica    schedule 16.11.2017
comment
@perseverance Нет. Использование for i in range(len(my_deque)): print(my_deque[i]) будет квадратичным. Использование for x in my_deque: print(x) будет линейным   -  person juanpa.arrivillaga    schedule 16.11.2017
comment
@user2357112 user2357112 Как мне сделать это правильно? Просьба уточнить! Спасибо!   -  person perseverance    schedule 16.11.2017
comment
Перебирайте деку напрямую, а не индексы, как уже сказал juanpa.arrivillaga.   -  person user2357112 supports Monica    schedule 16.11.2017
comment
Спасибо, парни! @juanpa.arrivillaga, не могли бы вы добавить это как ответ?   -  person perseverance    schedule 16.11.2017
comment
Как говорилось в комментариях, правильный способ сделать это — всегда перебирать саму вещь: for elem in elements.   -  person Daniel Roseman    schedule 16.11.2017
comment
@ Jean-FrançoisFabre Я не верю, что это дублирующий вопрос, потому что речь идет не о произвольном доступе в деке. Речь идет об итерации по деку.   -  person perseverance    schedule 16.11.2017
comment
Я колеблюсь. Речь идет о неправильном повторении дека. Я проголосовал за закрытие и не буду открывать заново, но у других комментаторов есть на это право, и я не возражаю против того, чтобы они поступали так, если считали, что это другой вопрос (всегда приятнее, когда у повторно открывающего есть благословение ближе, не то, чтобы это было строго необходимо, но...)   -  person Jean-François Fabre    schedule 16.11.2017
comment
также обратите внимание (в связанном вопросе), что deque умнее, чем простой двусторонний список. Is использует блоки, так что это лучше, чем O(n).   -  person Jean-François Fabre    schedule 16.11.2017
comment
@Jean-FrançoisFabre: Это все еще O (n). У него просто лучший постоянный множитель, чем если бы это был двусвязный список из учебника, а не развернутый список.   -  person user2357112 supports Monica    schedule 16.11.2017
comment
@ user2357112 спасибо за разъяснения. Мои знания о сложности тоже хрестоматийные :)   -  person Jean-François Fabre    schedule 16.11.2017
comment
Я снова открыл это, так как я действительно думаю, что вопрос о сложности итерации на самом деле не отвечает в дубликате (и на самом деле нигде не задокументирован, насколько я могу найти), поэтому я прибегнул к эмпирическому тестированию и проверке Исходный код CPython. Мой C очень посредственный, так что любой может поправить мою интерпретацию исходного кода.   -  person juanpa.arrivillaga    schedule 17.11.2017
comment
Никто еще не дал ссылки на документацию? docs.python.org/2/library/collections.html#collections. deque Доступ по индексу равен O(1) на обоих концах, но замедляется до O(n) в середине   -  person en_Knight    schedule 17.11.2017
comment
@juanpa.arrivillaga, возможное исключение из почти никогда: как перебрать первые 10 000 элементов очереди из 100 000 элементов? Я вижу два плохих варианта: итерация по диапазону индексов (медленная) или итерация по двухсторонней очереди с сохранением счетчика для остановки (уродливый код). Учитывая сюжет ниже, я бы выбрал первый вариант.   -  person Rainald62    schedule 25.06.2018
comment
@Rainald62 используйте itertools.islice   -  person juanpa.arrivillaga    schedule 25.06.2018


Ответы (1)


Если ваша конструкция выглядит примерно так:

elements = deque([1,2,3,4])
for i in range(len(elements)):
    print(elements[i])

Вы перебираете не deque, вы перебираете объект range, а затем индексируете deque. Это делает время итерации полиномиальным, поскольку каждая операция индексации elements[i] равна O(n). Однако на самом деле итерация по deque занимает линейное время.

for x in elements:
    print(x)

Вот быстрый эмпирический тест:

import timeit
import pandas as pd
from collections import deque

def build_deque(n):
    return deque(range(n))

def iter_index(d):
    for i in range(len(d)):
        d[i]

def iter_it(d):
    for x in d:
        x

r = range(100, 10001, 100)

index_runs = [timeit.timeit('iter_index(d)', 'from __main__ import build_deque, iter_index, iter_it; d = build_deque({})'.format(n), number=1000) for n in r]
it_runs = [timeit.timeit('iter_it(d)', 'from __main__ import build_deque, iter_index, iter_it; d = build_deque({})'.format(n), number=1000) for n in r]

df = pd.DataFrame({'index':index_runs, 'iter':it_runs}, index=r)
df.plot()

И получившийся график: введите здесь описание изображения

Теперь мы можем увидеть, как реализован протокол итератора для deque объектов в Исходный код CPython:

Во-первых, сам объект deque:

typedef struct BLOCK {
    struct BLOCK *leftlink;
    PyObject *data[BLOCKLEN];
    struct BLOCK *rightlink;
} block;

typedef struct {
    PyObject_VAR_HEAD
    block *leftblock;
    block *rightblock;
    Py_ssize_t leftindex;       /* 0 <= leftindex < BLOCKLEN */
    Py_ssize_t rightindex;      /* 0 <= rightindex < BLOCKLEN */
    size_t state;               /* incremented whenever the indices move */
    Py_ssize_t maxlen;
    PyObject *weakreflist;
} dequeobject;

Итак, как указано в комментариях, deque представляет собой двусвязный список «блочных» узлов, где блок, по сути, представляет собой массив указателей на объекты Python. Теперь для протокола итератора:

typedef struct {
    PyObject_HEAD
    block *b;
    Py_ssize_t index;
    dequeobject *deque;
    size_t state;          /* state when the iterator is created */
    Py_ssize_t counter;    /* number of items remaining for iteration */
} dequeiterobject;

static PyTypeObject dequeiter_type;

static PyObject *
deque_iter(dequeobject *deque)
{
    dequeiterobject *it;

    it = PyObject_GC_New(dequeiterobject, &dequeiter_type);
    if (it == NULL)
        return NULL;
    it->b = deque->leftblock;
    it->index = deque->leftindex;
    Py_INCREF(deque);
    it->deque = deque;
    it->state = deque->state;
    it->counter = Py_SIZE(deque);
    PyObject_GC_Track(it);
    return (PyObject *)it;
}

...

static PyObject *
dequeiter_next(dequeiterobject *it)
{
    PyObject *item;

    if (it->deque->state != it->state) {
        it->counter = 0;
        PyErr_SetString(PyExc_RuntimeError,
                        "deque mutated during iteration");
        return NULL;
    }
    if (it->counter == 0)
        return NULL;
    assert (!(it->b == it->deque->rightblock &&
              it->index > it->deque->rightindex));

    item = it->b->data[it->index];
    it->index++;
    it->counter--;
    if (it->index == BLOCKLEN && it->counter > 0) {
        CHECK_NOT_END(it->b->rightlink);
        it->b = it->b->rightlink;
        it->index = 0;
    }
    Py_INCREF(item);
    return item;
}

Как видите, итератор по существу отслеживает индекс блока, указатель на блок и счетчик общего количества элементов в двухсторонней очереди. Он останавливает итерацию, если счетчик достигает нуля, если нет, он захватывает элемент с текущим индексом, увеличивает индекс, уменьшает счетчик и проверяет, следует ли переходить к следующему блоку или нет. Другими словами, очередь может быть представлена ​​​​как список списков в Python, например. d = [[1,2,3],[4,5,6]], и он повторяется

for block in d:
    for x in block:
        ...
person juanpa.arrivillaga    schedule 16.11.2017
comment
Отличный ответ во всем. Я ожидаю, что график будет монотонным, хотя - почему их большие колебания на синей кривой? - person en_Knight; 17.11.2017
comment
@en_Knight Я мог только догадываться об источнике, но, вероятно, это в основном шум. - person juanpa.arrivillaga; 17.11.2017
comment
Возможно :) Кроме того, мне очень нравится цитировать реализацию, хотя, возможно, ничего не стоит, что это поведение не зависит от реализации, оно также цитируется в спецификации. - person en_Knight; 17.11.2017
comment
@en_Knight Мне не удалось найти ничего конкретно об итерации, но если вы дадите ссылку, я добавлю ее в ответ. - person juanpa.arrivillaga; 17.11.2017