在 Python 中,如何迭代给定列表的上一个、当前和下一个值?
In Python, how do I iterate over previous, current and next values of a given list?
我什至不知道从哪里开始为这个问题提供一个合适的标题。如果可以,请提出更好的建议。
编辑:
抱歉,我需要澄清我的问题。我正在寻找的东西是:
* 内存高效(即使用 itertools、迭代器或生成器)和
* 泛型可以处理具有任意偏移量的任意大小的元组的结果。
* 无需重复代码即可重复使用。
一些示例用例:
- func(3, -1) -- PREV, CURR, NEXT
- func(2, -1) -- PREV, CURR
- func(2, 0) -- CURR, NEXT
- 甚至 func(5, -2) -- PREV, PREV, CURR, NEXT, NEXT
这是一个示例,其中包含用例 #1 的值
例如,如果我的列表是:['abc', 'def', 'ghi', 'jkl']
迭代的结果是:
jkl abc def
abc def ghi
def ghi jkl
ghi jkl abc
很明显,我在这里使用了一些包装,即,对于第一次迭代,我使用列表中的最后一个值作为前一个值。同样,对于最后一次迭代,我使用列表中的第一个值作为下一个值。
我的问题是,是否有更 pythonic 的方式使用不同的 itertools function or a better itertools recipe 来做我想做的事。
这是我想提出的一些新代码,作为我自己的问题的解决方案,它基于@maya 的回答,但更通用,使用 yield 而不是 print。
def cyclic_n_tuples(seq, n=3, offset=-1):
seq_len = len(seq)
offset = seq_len + offset if offset < 0 else offset
for i in range(offset, offset + seq_len):
if (start := i % seq_len) < (end := (i + n) % seq_len):
yield seq[start:end]
else:
yield seq[start:] + seq[:end]
seq = "111 222 333 444 555 666 777 888 999 000".split()
for i in cyclic_n_tuples(seq, 5, -2):
print(*i)
上面的输出
999 000 111 222 333
000 111 222 333 444
111 222 333 444 555
222 333 444 555 666
333 444 555 666 777
444 555 666 777 888
555 666 777 888 999
666 777 888 999 000
777 888 999 000 111
888 999 000 111 222
与原始问题一起发布的代码:
我提出了 2 个版本的通用函数,return 满足上述要求的元组列表。
首先从原始列表创建多个切片,然后使用 zip 创建元组列表。可能不是最有效的内存解决方案...
第二个使用 itertools chain 和 islice 函数来执行相同的操作,但不会创建数组的多个副本。但是,由于迭代器的性质,我必须不断创建移位列表的新副本。那,它看起来很混乱,而且不是很 pythonic。
两个函数采用相同的参数
* the_list
个要操作的值
* n
returned 值
中每个元组中的项目数
* offset
给定位置数(正数或负数)以将原始输入移动
对于我们希望元组包含先前值的用例,需要移动输入。
from itertools import islice, chain
def wrapit(the_list, n, offset=0):
shifted_list = the_list[offset:] + the_list[:offset]
list_of_lists = [shifted_list[i:] + shifted_list[:i] for i in range(n)]
return zip(*list_of_lists)
def iter_wrapit(the_list, n, offset=0):
offset = offset if offset >= 0 else len(the_list) + offset
lst_list = [
chain(
islice(chain(islice(the_list, offset, None), islice(the_list, 0, offset)), i, None),
islice(chain(islice(the_list, offset, None), islice(the_list, 0, offset)), 0, i)
) for i in range(n)
]
return zip(*lst_list)
def main():
a = "abc def ghi jkl".split()
print(a)
print("-" * 10)
for t in wrapit(a, 3, -1):
print(*t)
print("-" * 10)
for t in iter_wrapit(a, 3, -1):
print(*t)
print("-" * 10)
if __name__ == "__main__":
main()
您可以在滑动 window 函数之上构建它:
def cyclic_triples(seq):
if not seq:
return
if len(seq) == 1:
yield (seq[0], seq[0], seq[0])
return
yield (seq[-1], seq[0], seq[1])
yield from map(tuple, windows(seq, 3))
yield (seq[-2], seq[-1], seq[0])
其中 windows
的示例实现是:
from collections import deque
def windows(iterable, size):
d = deque((), size)
it = iter(iterable)
for i in range(size - 1):
d.append(next(it, None))
for x in it:
d.append(x)
yield d
试试这个:
def iter_item(lst: list) -> list:
for i in range(len(lst)):
start = len(lst) - 1 if i < 1 else i - 1
end = (start + 3) % len(lst)
if end > start:
print(*lst[start:end])
else:
print(*(lst[start:] + lst[:end]))
iter_item(['abc', 'def', 'ghi', 'jkl'])
使用切片:
def rotate(seq, k):
return seq[k:] + seq[:k]
l = ['abc', 'def', 'ghi', 'jkl']
for i, j, k in zip(rotate(l, -1), l, rotate(l, 1)):
print(i, j, k)
好的,这是我觉得最好的答案。它是通用的,因为它可以 return 任何长度的元组并处理正偏移和负偏移。感谢所有提供意见的人。
def cyclic_n_tuples(seq, n=3, offset=-1):
seq_len = len(seq)
offset = seq_len + offset if offset < 0 else offset
for i in range(offset, offset + seq_len):
if (start := i % seq_len) < (end := (i + n) % seq_len):
yield seq[start:end]
else:
yield seq[start:] + seq[:end]
seq = "111 222 333 444 555 666 777 888 999 000".split()
for i in cyclic_n_tuples(seq, 5, -2):
print(*i)
以上示例输出
999 000 111 222 333
000 111 222 333 444
111 222 333 444 555
222 333 444 555 666
333 444 555 666 777
444 555 666 777 888
555 666 777 888 999
666 777 888 999 000
777 888 999 000 111
888 999 000 111 222
我什至不知道从哪里开始为这个问题提供一个合适的标题。如果可以,请提出更好的建议。
编辑:
抱歉,我需要澄清我的问题。我正在寻找的东西是:
* 内存高效(即使用 itertools、迭代器或生成器)和
* 泛型可以处理具有任意偏移量的任意大小的元组的结果。
* 无需重复代码即可重复使用。
一些示例用例:
- func(3, -1) -- PREV, CURR, NEXT
- func(2, -1) -- PREV, CURR
- func(2, 0) -- CURR, NEXT
- 甚至 func(5, -2) -- PREV, PREV, CURR, NEXT, NEXT
这是一个示例,其中包含用例 #1 的值
例如,如果我的列表是:['abc', 'def', 'ghi', 'jkl']
迭代的结果是:
jkl abc def
abc def ghi
def ghi jkl
ghi jkl abc
很明显,我在这里使用了一些包装,即,对于第一次迭代,我使用列表中的最后一个值作为前一个值。同样,对于最后一次迭代,我使用列表中的第一个值作为下一个值。
我的问题是,是否有更 pythonic 的方式使用不同的 itertools function or a better itertools recipe 来做我想做的事。
这是我想提出的一些新代码,作为我自己的问题的解决方案,它基于@maya 的回答,但更通用,使用 yield 而不是 print。
def cyclic_n_tuples(seq, n=3, offset=-1):
seq_len = len(seq)
offset = seq_len + offset if offset < 0 else offset
for i in range(offset, offset + seq_len):
if (start := i % seq_len) < (end := (i + n) % seq_len):
yield seq[start:end]
else:
yield seq[start:] + seq[:end]
seq = "111 222 333 444 555 666 777 888 999 000".split()
for i in cyclic_n_tuples(seq, 5, -2):
print(*i)
上面的输出
999 000 111 222 333
000 111 222 333 444
111 222 333 444 555
222 333 444 555 666
333 444 555 666 777
444 555 666 777 888
555 666 777 888 999
666 777 888 999 000
777 888 999 000 111
888 999 000 111 222
与原始问题一起发布的代码:
我提出了 2 个版本的通用函数,return 满足上述要求的元组列表。
首先从原始列表创建多个切片,然后使用 zip 创建元组列表。可能不是最有效的内存解决方案...
第二个使用 itertools chain 和 islice 函数来执行相同的操作,但不会创建数组的多个副本。但是,由于迭代器的性质,我必须不断创建移位列表的新副本。那,它看起来很混乱,而且不是很 pythonic。
两个函数采用相同的参数
* the_list
个要操作的值
* n
returned 值
中每个元组中的项目数
* offset
给定位置数(正数或负数)以将原始输入移动
对于我们希望元组包含先前值的用例,需要移动输入。
from itertools import islice, chain
def wrapit(the_list, n, offset=0):
shifted_list = the_list[offset:] + the_list[:offset]
list_of_lists = [shifted_list[i:] + shifted_list[:i] for i in range(n)]
return zip(*list_of_lists)
def iter_wrapit(the_list, n, offset=0):
offset = offset if offset >= 0 else len(the_list) + offset
lst_list = [
chain(
islice(chain(islice(the_list, offset, None), islice(the_list, 0, offset)), i, None),
islice(chain(islice(the_list, offset, None), islice(the_list, 0, offset)), 0, i)
) for i in range(n)
]
return zip(*lst_list)
def main():
a = "abc def ghi jkl".split()
print(a)
print("-" * 10)
for t in wrapit(a, 3, -1):
print(*t)
print("-" * 10)
for t in iter_wrapit(a, 3, -1):
print(*t)
print("-" * 10)
if __name__ == "__main__":
main()
您可以在滑动 window 函数之上构建它:
def cyclic_triples(seq):
if not seq:
return
if len(seq) == 1:
yield (seq[0], seq[0], seq[0])
return
yield (seq[-1], seq[0], seq[1])
yield from map(tuple, windows(seq, 3))
yield (seq[-2], seq[-1], seq[0])
其中 windows
的示例实现是:
from collections import deque
def windows(iterable, size):
d = deque((), size)
it = iter(iterable)
for i in range(size - 1):
d.append(next(it, None))
for x in it:
d.append(x)
yield d
试试这个:
def iter_item(lst: list) -> list:
for i in range(len(lst)):
start = len(lst) - 1 if i < 1 else i - 1
end = (start + 3) % len(lst)
if end > start:
print(*lst[start:end])
else:
print(*(lst[start:] + lst[:end]))
iter_item(['abc', 'def', 'ghi', 'jkl'])
使用切片:
def rotate(seq, k):
return seq[k:] + seq[:k]
l = ['abc', 'def', 'ghi', 'jkl']
for i, j, k in zip(rotate(l, -1), l, rotate(l, 1)):
print(i, j, k)
好的,这是我觉得最好的答案。它是通用的,因为它可以 return 任何长度的元组并处理正偏移和负偏移。感谢所有提供意见的人。
def cyclic_n_tuples(seq, n=3, offset=-1):
seq_len = len(seq)
offset = seq_len + offset if offset < 0 else offset
for i in range(offset, offset + seq_len):
if (start := i % seq_len) < (end := (i + n) % seq_len):
yield seq[start:end]
else:
yield seq[start:] + seq[:end]
seq = "111 222 333 444 555 666 777 888 999 000".split()
for i in cyclic_n_tuples(seq, 5, -2):
print(*i)
以上示例输出
999 000 111 222 333
000 111 222 333 444
111 222 333 444 555
222 333 444 555 666
333 444 555 666 777
444 555 666 777 888
555 666 777 888 999
666 777 888 999 000
777 888 999 000 111
888 999 000 111 222