在记住索引的同时遍历多维列表
Iterating through multi-dimentional lists while remembering indices
我有一个 4 维数组,用于存储 5 维特征中的点值 space。幸运的是,所有特征都可以用整数来描述,从 0 开始(因此,可以用列表索引编码)。当我处理我的数据时,我会做这样的事情(请注意,我不仅需要值,还需要所有相应的特征):
for f1 in range(len(data)):
for f2 in range(len(data[f1])):
for f3 in range(len(data[f1][f2])):
for f4 in range(len(data[f1][f2][f3])):
for f5 in range(len(data[f1][f2][f3][f4])):
value = data[f1][f2][f3][f4][f5]
process_point(f1, f2, f3, f4, f5, value)
但是,由于嵌套级别高,这看起来有点难看。有没有办法重构这段代码以减少嵌套? flatten 函数有不同的实现;但是,它们旨在仅保留值,而不保留索引。如果解决方案不会导致性能大幅下降,那也很好。
为了让代码看起来更好一点,你可以使用enumerate
:
for f1, d1 in enumerate(data):
for f2, d2 in enumerate(d1):
for f3, d3 in enumerate(d2):
for f4, d4 in enumerate(d3):
for f5, value in enumerate(d4):
process_point(f1, f2, f3, f4, f5, value)
基于递归的更通用的方法可以删除嵌套:
def access(d):
if type(d) == list:
for f, v in enumerate(d):
for r in access(v):
yield [f] + r
else:
yield [d]
用法示例:
a = \ # 4 levels deep only, don't want to have nightmares with lists tonight
[
[
[
[1, 2, 3],
],
[
[11, 22, 33],
],
],
[
[
[111, 222, 333],
],
],
[
[
[1111, 2222, 3333],
],
],
]
for arg in access(a):
process_point(*arg)
被解包到函数 process_points
中的 arg
将在这个列表中:
[[0, 0, 0, 0, 1],
[0, 0, 0, 1, 2],
[0, 0, 0, 2, 3],
[0, 1, 0, 0, 11],
[0, 1, 0, 1, 22],
[0, 1, 0, 2, 33],
[1, 0, 0, 0, 111],
[1, 0, 0, 1, 222],
[1, 0, 0, 2, 333],
[2, 0, 0, 0, 1111],
[2, 0, 0, 1, 2222],
[2, 0, 0, 2, 3333]]
也就是说,递归方法中参数列表的构建不是很有效。如果您可以更疯狂地修改代码,您可能需要考虑更好地表示数据,例如评论中指出的基于字典的表示。
扩展我的评论。整数元组可以很容易地迭代,尤其是使用 itertools。它们可以按 lex 顺序排序(例如 (0,1,3) < (0,1,4))。如果您使用 data = dict()
创建数据,那么您的代码中任何看起来像
的行
data[i][j][k][l][m] = v
可以替换为
data[(i,j,k,l,m)] = v
甚至只是(通过隐式元组形成)
data[i,j,k,l,m] = v
以下代码片段显示了一些可能性:
import itertools
import random
data = dict()
points = list(itertools.product(range(0,9), repeat = 5))
rand_points = random.sample(points,10)
rand_points.sort() # in standard lex order
for p in rand_points:
data[p] = random.random()
def process(point,value):
print(str(point) + " maps to " +str(value))
for point in rand_points:
process(point,data[point])
我的(随机)输出:
(0, 3, 8, 2, 6) maps to 0.815091491066791
(0, 5, 6, 6, 1) maps to 0.25432836286289706
(0, 6, 8, 1, 5) maps to 0.4797033192218453
(2, 7, 1, 1, 6) maps to 0.9141711104231289
(4, 4, 6, 8, 7) maps to 0.016541794083792083
(4, 7, 3, 4, 3) maps to 0.42496849947725746
(5, 1, 5, 8, 1) maps to 0.4475971644821247
(6, 6, 6, 6, 1) maps to 0.40198989791011164
(7, 6, 7, 6, 3) maps to 0.9720480406273648
(8, 5, 4, 8, 0) maps to 0.8147848652511576
请注意完全没有嵌套循环。您为掌握 itertools 所做的任何努力都是无价的,因为它是一个功能强大但未得到充分利用的模块。
我有一个 4 维数组,用于存储 5 维特征中的点值 space。幸运的是,所有特征都可以用整数来描述,从 0 开始(因此,可以用列表索引编码)。当我处理我的数据时,我会做这样的事情(请注意,我不仅需要值,还需要所有相应的特征):
for f1 in range(len(data)):
for f2 in range(len(data[f1])):
for f3 in range(len(data[f1][f2])):
for f4 in range(len(data[f1][f2][f3])):
for f5 in range(len(data[f1][f2][f3][f4])):
value = data[f1][f2][f3][f4][f5]
process_point(f1, f2, f3, f4, f5, value)
但是,由于嵌套级别高,这看起来有点难看。有没有办法重构这段代码以减少嵌套? flatten 函数有不同的实现;但是,它们旨在仅保留值,而不保留索引。如果解决方案不会导致性能大幅下降,那也很好。
为了让代码看起来更好一点,你可以使用enumerate
:
for f1, d1 in enumerate(data):
for f2, d2 in enumerate(d1):
for f3, d3 in enumerate(d2):
for f4, d4 in enumerate(d3):
for f5, value in enumerate(d4):
process_point(f1, f2, f3, f4, f5, value)
基于递归的更通用的方法可以删除嵌套:
def access(d):
if type(d) == list:
for f, v in enumerate(d):
for r in access(v):
yield [f] + r
else:
yield [d]
用法示例:
a = \ # 4 levels deep only, don't want to have nightmares with lists tonight
[
[
[
[1, 2, 3],
],
[
[11, 22, 33],
],
],
[
[
[111, 222, 333],
],
],
[
[
[1111, 2222, 3333],
],
],
]
for arg in access(a):
process_point(*arg)
被解包到函数 process_points
中的 arg
将在这个列表中:
[[0, 0, 0, 0, 1],
[0, 0, 0, 1, 2],
[0, 0, 0, 2, 3],
[0, 1, 0, 0, 11],
[0, 1, 0, 1, 22],
[0, 1, 0, 2, 33],
[1, 0, 0, 0, 111],
[1, 0, 0, 1, 222],
[1, 0, 0, 2, 333],
[2, 0, 0, 0, 1111],
[2, 0, 0, 1, 2222],
[2, 0, 0, 2, 3333]]
也就是说,递归方法中参数列表的构建不是很有效。如果您可以更疯狂地修改代码,您可能需要考虑更好地表示数据,例如评论中指出的基于字典的表示。
扩展我的评论。整数元组可以很容易地迭代,尤其是使用 itertools。它们可以按 lex 顺序排序(例如 (0,1,3) < (0,1,4))。如果您使用 data = dict()
创建数据,那么您的代码中任何看起来像
data[i][j][k][l][m] = v
可以替换为
data[(i,j,k,l,m)] = v
甚至只是(通过隐式元组形成)
data[i,j,k,l,m] = v
以下代码片段显示了一些可能性:
import itertools
import random
data = dict()
points = list(itertools.product(range(0,9), repeat = 5))
rand_points = random.sample(points,10)
rand_points.sort() # in standard lex order
for p in rand_points:
data[p] = random.random()
def process(point,value):
print(str(point) + " maps to " +str(value))
for point in rand_points:
process(point,data[point])
我的(随机)输出:
(0, 3, 8, 2, 6) maps to 0.815091491066791
(0, 5, 6, 6, 1) maps to 0.25432836286289706
(0, 6, 8, 1, 5) maps to 0.4797033192218453
(2, 7, 1, 1, 6) maps to 0.9141711104231289
(4, 4, 6, 8, 7) maps to 0.016541794083792083
(4, 7, 3, 4, 3) maps to 0.42496849947725746
(5, 1, 5, 8, 1) maps to 0.4475971644821247
(6, 6, 6, 6, 1) maps to 0.40198989791011164
(7, 6, 7, 6, 3) maps to 0.9720480406273648
(8, 5, 4, 8, 0) maps to 0.8147848652511576
请注意完全没有嵌套循环。您为掌握 itertools 所做的任何努力都是无价的,因为它是一个功能强大但未得到充分利用的模块。