如何从中间开始完全遍历列表?
How to iterate completely through a list after starting in the middle?
python 的新手。从中间(或任何一点)开始后如何完全遍历列表?例如,如果我有:
[a,b,c,d,e,f,g]
我想从 d 开始,然后是 e、f、g、a、b、c。但我不知道该怎么做。我希望某些东西也能够执行 b、c、d、e、f、g、a 或从任何起点完全迭代的任何顺序。
我发现这个 discussing something similar, and an 通过以下方式解决了这个问题:
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1]
但是,老实说,我不知道如何解释这段代码是如何完成这项任务的。我确实认为我正在寻找的任何东西都与此类似。
您可以试试这个方法:
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> start = 3
>>> [a[(start + j) % len(a)] for j in range(len(a))]
[4, 5, 6, 7, 1, 2, 3]
另一种方法(这在后台是不必要的复杂):
>>> a[start:] + a[:start]
[4, 5, 6, 7, 1, 2, 3]
在此解决方案中,我使用 range(len(items))
生成一个可迭代对象,其值从 0 到列表长度减去 1
这意味着在这个例子中,在 for 循环中
i 的值将为 0,1,2,3... 所以 i + startIndex 将为 3, 4,5,...
这几乎就是我们想要的,但我们需要确保不会遗漏列表开头的元素。当 (i + startIndex)
除以列表的长度时,%
运算符得到余数。这确保了一旦 i 大于或等于列表的长度就打印其余项目,因为余数将为 0、1、2 ...
items = ["a", "b", "c", "d", "e", "f", "g"]
startIndex = 3
for i in range(len(items)):
print(items[(i + startIndex) % len(items)])
如前所述,您只需将两个切片相加即可:
>>> a[3:] + a[:3]
[4, 5, 6, 7, 1, 2, 3]
但是 +
运算符会创建一个新列表(即使您只打算遍历它一次),如果列表非常长,这可能是不可取的。
如果您想要切片语法的简单性 和 您希望能够对列表元素进行一次迭代 而无需 将所有内容复制到一个新列表中,有 itertools.chain
,它允许您将多个可迭代对象链接在一起,而无需将它们连接到 in-memory 列表中:
>>> import itertools
>>> for n in itertools.chain(a[3:], a[:3]):
... print(n)
...
4
5
6
7
1
2
3
这是一个非常高效的 迭代器(因为您使用了 iterator
标签并多次说“迭代”)。它仅使用 O(1) space 并且非常快:
from itertools import islice, chain
def rotated(lst, start):
it = iter(lst)
next(islice(it, start, start), None)
return chain(it, islice(lst, start))
lst = list('abcdefgh')
start = 3
print(*rotated(lst, start))
输出(Try it online!):
d e f g h a b c
具有一百万个数字列表的基准测试,显示 tracemalloc
峰值和运行时间。使用到目前为止建议的迭代器:
list_comprehension 8,697,776 bytes 1331.9 milliseconds
add_list_slices 16,000,000 bytes 28.4 milliseconds
iadd_list_slices 13,000,048 bytes 23.1 milliseconds
rotated_deque_1 8,243,328 bytes 26.9 milliseconds
rotated_deque_2 296 bytes 8.1 milliseconds
chained_list_slices 8,000,240 bytes 18.1 milliseconds
chained_iterators 360 bytes 11.6 milliseconds <-- mine
第一个 deque
解决方案就是问题所要求的(从列表开始)。第二个是 ShadowRanger 的建议(使用 get-go 中的 deque
,而不是列表)。
代码(Try it online!):
import tracemalloc as tm
from timeit import default_timer as timer
from collections import deque
from itertools import islice, chain
def list_comprehension(a, start):
return [a[(start + j) % len(a)] for j in range(len(a))]
def add_list_slices(a, start):
return a[start:] + a[:start]
def iadd_list_slices(a, start):
b = a[start:]
b += a[:start]
return b
def chained_list_slices(lst, start):
return chain(lst[start:], lst[:start])
def rotated_deque_1(lst, start):
d = deque(lst)
d.rotate(-start)
return d
def rotated_deque_2(lst, start):
deck.rotate(-start)
return deck
# deck.rotate(start) is done in the benchmark code, after the iteration
def chained_iterators(lst, start):
it = iter(lst)
next(islice(it, start, start), None)
return chain(it, islice(lst, start))
funcs = [
list_comprehension,
add_list_slices,
iadd_list_slices,
rotated_deque_1,
rotated_deque_2,
chained_list_slices,
chained_iterators,
]
a = list(range(10))
start = 3
deck = deque(a)
for func in funcs:
print(*func(a, start))
n = 10 ** 6
a = list(range(n))
start = n // 2
deck = deque(a)
for _ in range(3):
print()
for func in funcs:
tm.start()
t0 = timer()
deque(func(a, start), 0)
if func is rotated_deque_2:
deck.rotate(start)
t1 = timer()
print(f'{func.__name__:{max(len(func.__name__) for func in funcs)}} '
f'{tm.get_traced_memory()[1]:10,} bytes '
f'{(t1 - t0) * 1e3:6.1f} milliseconds')
tm.stop()
这不是给定所写代码和所问问题的答案,但您可以考虑在迭代之前使用 a collections.deque
instead of a list
from the get-go. With deque
, you can do a rotate
operation,正常迭代(没有复杂的 skipping/slicing 逻辑),然后(如果你want/need到)rotate
它回到原来的位置。
例如,与其构建 list
,不如从头构建 deque
:
from collections import deque
a = deque(range(1, 8))
a.rotate(-3) # Shifts three elements from beginning to end
for x in a: # deque is actually reordered, so just iterate normally
print(x)
a.rotate(3) # Shifts three elements from end back to beginning
输出:
4
5
6
7
1
2
3
python 的新手。从中间(或任何一点)开始后如何完全遍历列表?例如,如果我有:
[a,b,c,d,e,f,g]
我想从 d 开始,然后是 e、f、g、a、b、c。但我不知道该怎么做。我希望某些东西也能够执行 b、c、d、e、f、g、a 或从任何起点完全迭代的任何顺序。
我发现这个
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1]
但是,老实说,我不知道如何解释这段代码是如何完成这项任务的。我确实认为我正在寻找的任何东西都与此类似。
您可以试试这个方法:
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> start = 3
>>> [a[(start + j) % len(a)] for j in range(len(a))]
[4, 5, 6, 7, 1, 2, 3]
另一种方法(这在后台是不必要的复杂):
>>> a[start:] + a[:start]
[4, 5, 6, 7, 1, 2, 3]
在此解决方案中,我使用 range(len(items))
生成一个可迭代对象,其值从 0 到列表长度减去 1
这意味着在这个例子中,在 for 循环中 i 的值将为 0,1,2,3... 所以 i + startIndex 将为 3, 4,5,...
这几乎就是我们想要的,但我们需要确保不会遗漏列表开头的元素。当 (i + startIndex)
除以列表的长度时,%
运算符得到余数。这确保了一旦 i 大于或等于列表的长度就打印其余项目,因为余数将为 0、1、2 ...
items = ["a", "b", "c", "d", "e", "f", "g"]
startIndex = 3
for i in range(len(items)):
print(items[(i + startIndex) % len(items)])
如前所述,您只需将两个切片相加即可:
>>> a[3:] + a[:3]
[4, 5, 6, 7, 1, 2, 3]
但是 +
运算符会创建一个新列表(即使您只打算遍历它一次),如果列表非常长,这可能是不可取的。
如果您想要切片语法的简单性 和 您希望能够对列表元素进行一次迭代 而无需 将所有内容复制到一个新列表中,有 itertools.chain
,它允许您将多个可迭代对象链接在一起,而无需将它们连接到 in-memory 列表中:
>>> import itertools
>>> for n in itertools.chain(a[3:], a[:3]):
... print(n)
...
4
5
6
7
1
2
3
这是一个非常高效的 迭代器(因为您使用了 iterator
标签并多次说“迭代”)。它仅使用 O(1) space 并且非常快:
from itertools import islice, chain
def rotated(lst, start):
it = iter(lst)
next(islice(it, start, start), None)
return chain(it, islice(lst, start))
lst = list('abcdefgh')
start = 3
print(*rotated(lst, start))
输出(Try it online!):
d e f g h a b c
具有一百万个数字列表的基准测试,显示 tracemalloc
峰值和运行时间。使用到目前为止建议的迭代器:
list_comprehension 8,697,776 bytes 1331.9 milliseconds
add_list_slices 16,000,000 bytes 28.4 milliseconds
iadd_list_slices 13,000,048 bytes 23.1 milliseconds
rotated_deque_1 8,243,328 bytes 26.9 milliseconds
rotated_deque_2 296 bytes 8.1 milliseconds
chained_list_slices 8,000,240 bytes 18.1 milliseconds
chained_iterators 360 bytes 11.6 milliseconds <-- mine
第一个 deque
解决方案就是问题所要求的(从列表开始)。第二个是 ShadowRanger 的建议(使用 get-go 中的 deque
,而不是列表)。
代码(Try it online!):
import tracemalloc as tm
from timeit import default_timer as timer
from collections import deque
from itertools import islice, chain
def list_comprehension(a, start):
return [a[(start + j) % len(a)] for j in range(len(a))]
def add_list_slices(a, start):
return a[start:] + a[:start]
def iadd_list_slices(a, start):
b = a[start:]
b += a[:start]
return b
def chained_list_slices(lst, start):
return chain(lst[start:], lst[:start])
def rotated_deque_1(lst, start):
d = deque(lst)
d.rotate(-start)
return d
def rotated_deque_2(lst, start):
deck.rotate(-start)
return deck
# deck.rotate(start) is done in the benchmark code, after the iteration
def chained_iterators(lst, start):
it = iter(lst)
next(islice(it, start, start), None)
return chain(it, islice(lst, start))
funcs = [
list_comprehension,
add_list_slices,
iadd_list_slices,
rotated_deque_1,
rotated_deque_2,
chained_list_slices,
chained_iterators,
]
a = list(range(10))
start = 3
deck = deque(a)
for func in funcs:
print(*func(a, start))
n = 10 ** 6
a = list(range(n))
start = n // 2
deck = deque(a)
for _ in range(3):
print()
for func in funcs:
tm.start()
t0 = timer()
deque(func(a, start), 0)
if func is rotated_deque_2:
deck.rotate(start)
t1 = timer()
print(f'{func.__name__:{max(len(func.__name__) for func in funcs)}} '
f'{tm.get_traced_memory()[1]:10,} bytes '
f'{(t1 - t0) * 1e3:6.1f} milliseconds')
tm.stop()
这不是给定所写代码和所问问题的答案,但您可以考虑在迭代之前使用 a collections.deque
instead of a list
from the get-go. With deque
, you can do a rotate
operation,正常迭代(没有复杂的 skipping/slicing 逻辑),然后(如果你want/need到)rotate
它回到原来的位置。
例如,与其构建 list
,不如从头构建 deque
:
from collections import deque
a = deque(range(1, 8))
a.rotate(-3) # Shifts three elements from beginning to end
for x in a: # deque is actually reordered, so just iterate normally
print(x)
a.rotate(3) # Shifts three elements from end back to beginning
输出:
4
5
6
7
1
2
3