Итератор кругового списка в Python

101

Мне нужно перебирать круговой список, возможно, много раз, каждый раз, начиная с последнего посещенного элемента.

Вариант использования - это пул соединений. Клиент запрашивает соединение, итератор проверяет, доступно ли указанное соединение, и возвращает его, в противном случае выполняет цикл до тех пор, пока не найдет доступное соединение.

Есть ли удобный способ сделать это в Python?

user443854
источник

Ответы:

161

Используйте itertools.cycle, вот его точное предназначение:

from itertools import cycle

lst = ['a', 'b', 'c']

pool = cycle(lst)

for item in pool:
    print item,

Вывод:

a b c a b c ...

(Петли вечны, очевидно)


Чтобы вручную продвигать итератор и извлекать из него значения одно за другим, просто вызовите next(pool):

>>> next(pool)
'a'
>>> next(pool)
'b'
Лукас Граф
источник
2
Вы печатаете элементы в цикле. Что хочу выйти из цикла и вернуться позже? (Я хочу начать с того места, где остановился).
user443854 01
7
@ user443854 используйте pool.next()для получения единственного следующего элемента из цикла
Джейкоб Кролл
4
@ user443854 FWIW, это гораздо лучший ответ, чем мой. Нет причин возвращаться к повторной реализации библиотечных функций!
Джейкоб Кролл
5
pool.next () у меня не работал, только next (pool). Наверное, из-за Python 3?
fjsj
6
@fjsj, что правильно, на Python 3 вам нужно использовать next(iterator)(который, кстати, также отлично работает на Python 2.x, и, следовательно, является канонической формой, которую следует использовать). См. Виден ли генератор.next () в python 3.0? для более подробного объяснения. Соответственно обновил свой ответ.
Лукас Граф
55

Правильный ответ - использовать itertools.cycle . Но предположим, что такой библиотечной функции не существует. Как бы вы это реализовали?

Используйте генератор :

def circular():
    while True:
        for connection in ['a', 'b', 'c']:
            yield connection

Затем вы можете использовать forоператор для бесконечной итерации или вызвать next()для получения единственного следующего значения из итератора генератора:

connections = circular()
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
#....
Джейкоб Кролл
источник
Ницца! Как он знает, что нужно начинать заново, когда список исчерпан?
user443854 01
1
@ user443854 while Trueсредство повторять вечно
Джейкоб Кролл
3
@juanchopanza: Ага; itertools.cycleэто лучший ответ. Это показывает, как можно было бы написать ту же функцию, если itertoolsона недоступна :)
Джейкоб Кролл
Сохраняет ли простой генератор также копию каждого элемента, как это itertools.cycleделает? Или простой генератор будет более эффективным с точки зрения памяти? Согласно cycleдокументам :Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
dthor
2
@dthor этот генератор создает список из трех элементов и обрабатывает его, затем уничтожает список и создает новый навсегда. Эта документация cycleподразумевает, что итерация ввода преобразуется в listдо запуска генератора, поскольку iterableона « годна только для одного прохода по набору значений».
Джейкоб Кролл
9

Или можно сделать так:

conn = ['a', 'b', 'c', 'd', 'e', 'f']
conn_len = len(conn)
index = 0
while True:
    print(conn[index])
    index = (index + 1) % conn_len

печатает abcdefab c ... навсегда

viky.pat
источник
4

вы можете сделать это с помощью append(pop())цикла:

l = ['a','b','c','d']
while 1:
    print l[0]
    l.append(l.pop(0))

или for i in range()цикл:

l = ['a','b','c','d']
ll = len(l)
while 1:
    for i in range(ll):
       print l[i]

или просто:

l = ['a','b','c','d']

while 1:
    for i in l:
       print i

все из которых печатают:

>>>
a
b
c
d
a
b
c
d
...etc.

из трех я был бы склонен к подходу append (pop ()) как функции

servers = ['a','b','c','d']

def rotate_servers(servers):
    servers.append(servers.pop(0))
    return servers

while 1:
    servers = rotate_servers(servers)
    print servers[0]
легкое присутствие
источник
1
Проголосовать за это, потому что это помогло мне с совершенно другим вариантом использования, когда я просто хочу перебирать список несколько раз, каждый раз с продвижением элемента start на один шаг. Мой вариант использования - перебирать игроков в игре в покер, продвигая шайбу дилера вперед на одного игрока в каждом раунде.
Johan
3

Если вы хотите изменитьn время цикла , ncycles примените рецепт itertools :

from itertools import chain, repeat


def ncycles(iterable, n):
    "Returns the sequence elements n times"
    return chain.from_iterable(repeat(tuple(iterable), n))


list(ncycles(["a", "b", "c"], 3))
# ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
пиланг
источник
2

Вам нужен собственный итератор - я адаптирую итератор из этого ответа .

from itertools import cycle

class ConnectionPool():
    def __init__(self, ...):
        # whatever is appropriate here to initilize
        # your data
        self.pool = cycle([blah, blah, etc])
    def __iter__(self):
        return self
    def __next__(self):
        for connection in self.pool:
            if connection.is_available:  # or however you spell it
                return connection
Итан Фурман
источник