Какова временная сложность итерации через deque в Python?

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

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

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

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

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

Спасибо!

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;
}

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

for block in d:
    for x in block:
        ...
Другие вопросы по тегам