【问题标题】:How to join innermost elements of a deep nested list using zip如何使用 zip 连接深层嵌套列表的最里面的元素
【发布时间】:2019-12-03 17:39:31
【问题描述】:

假设我有以下包含列表的列表:

samples = [
    # First sample
    [
        # Think 'x' as in input variable in ML
        [
            ['A','E'], # Data
            ['B','F']  # Metadata
        ],
        # Think 'y' as in target variable in ML
        [
            ['C','G'], # Data
            ['D','H'], # Metadata
        ]
    ],
    # Second sample
    [
        [
            ['1'],
            ['2']
        ],
        [
            ['3'],
            ['4']
        ]
    ]
]

我所追求的输出如下所示:

>>> samples
[
    ['A','E','1'], # x.data
    ['B','F','2'], # x.metadata
    ['C','G','3'], # y.data
    ['D','H','4']  # y.metadata
]

我的问题是,是否存在利用 Python 的 zip 函数以及一些列表推导来实现此目的的方法?

我已经搜索了一些解决方案,但例如 thisthis 处理使用 zip 处理不同的列表,而不是内部列表。

实现这一点的方法很可能只是对样本进行简单的迭代,如下所示:

x,x_len,y,y_len=[],[],[],[]

for sample in samples:
    x.append(sample[0][0])
    x_len.append(sample[0][1])
    y.append(sample[1][0])
    y_len.append(sample[1][1])

samples = [
    x,
    x_len,
    y,
    y_len
]

我仍然很好奇是否有一种方法可以利用 zip 而不是 for 循环样本及其嵌套列表。

请注意,datametadata 的长度可能因样本而异。

【问题讨论】:

  • 最里面的列表总是长度为 1 吗?
  • 你能修改最初创建样本的内容吗?
  • @AKX 不,他们不是。它们实际上是可变长度张量。然而,这可能与这里无关。我只是想知道是否存在一种方法来组合来自类似结构的列表对象的数据。
  • @Sayse 不幸的是,由于对我的数据使用了两个单独的框架(torchskorch),我的选择非常有限。

标签: python list nested iteration list-comprehension


【解决方案1】:

IIUC,一种方法是使用itertools.chainzip(samples)的结果展平:

from itertools import chain

new_samples = [
    list(chain.from_iterable(y)) for y in zip(
        *((chain.from_iterable(*x)) for x in zip(samples))
    )
]

print(new_samples)
#[['A', 'E', '1'], ['B', 'F', '2'], ['C', 'G', '3'], ['D', 'H', '4']]

分步说明

1) 第一次在samples 上调用zip

print(list(zip(samples)))
#[([[['A', 'E'], ['B', 'F']], [['C', 'G'], ['D', 'H']]],),
# ([[['1'], ['2']], [['3'], ['4']]],)]

请注意,在上面输出的两行中,如果元素被展平,您将拥有zip 所需的结构,以便获得最终结果。

2) 使用itertools.chain to flatten(这将是很多more efficient than using sum)。

print([list(chain.from_iterable(*x)) for x in zip(samples)])
#[[['A', 'E'], ['B', 'F'], ['C', 'G'], ['D', 'H']],
# [['1'], ['2'], ['3'], ['4']]]

3) 现在再次拨打zip

print(list(zip(*((chain.from_iterable(*x)) for x in zip(samples)))))
#[(['A', 'E'], ['1']),
# (['B', 'F'], ['2']),
# (['C', 'G'], ['3']),
# (['D', 'H'], ['4'])]

4) 现在你基本上有了你想要的,除了列表是嵌套的。所以再次使用itertools.chain 来展平最终列表。

print(
    [
        list(chain.from_iterable(y)) for y in zip(
            *((chain.from_iterable(*x)) for x in zip(samples))
        )
    ]
)
#[['A', 'E', '1'], ['B', 'F', '2'], ['C', 'G', '3'], ['D', 'H', '4']]

【讨论】:

  • 能否请您详细说明生产 zip-object 的单线?代码的一些 cmets 甚至可能会打开它。不过效果很好!
  • @PetteriNevavuori 我添加了一些分步说明
  • 在您添加了额外的迭代之后,这个答案以及清晰的解释至少对我来说似乎是最好的。我会接受的!
【解决方案2】:

你可以这样做:

res = [[y for l in x for y in l] for x in zip(*([x for var in sample for x in var] for sample in samples))]

print([list(i) for i in res])

给出你的例子:

[['A', 'E', '1'], ['B', 'F', '2'], ['C', 'G', '3'], ['D', 'H', '4']]

这基本上将每个“样本”扁平化为一个列表并将其打包到一个大列表中,然后将其解包到 zip 中,然后将每个压缩元素打包到一个列表中。

【讨论】:

  • 如果样本中数据的长度不同怎么办?例如尝试设置samples[0][0][0] = ['A','E']。此解决方案仅返回最内部列表的第一个元素。
  • 你没有提到它应该如何处理这种情况。应该是:[['A', 'E'], '1'] 还是 ['A', 'E', '1']
  • 您是对的,对此我深表歉意。出于某种原因,我首先认为这无关紧要,但显然它确实如此。我已经编辑了问题以反映我对输出的期望。
  • 好吧,修好了。现在它适用于任何样本中的所有长度,只是有点混乱:)
【解决方案3】:

这是另一个解决方案。相当丑陋,但它确实使用了zip,甚至两次!

>>> sum(map(lambda y: list(map(lambda x: sum(x, []), zip(*y))), zip(*samples)), [])
[['A', '1'], ['B', '2'], ['C', '3'], ['D', '4']]

看看它是如何工作的很有趣,但请不要实际使用它;它既难以阅读,算法也很糟糕。

【讨论】:

  • 这实际上适用于不同长度的样本!不过确实很难读。
  • @PetteriNevavuori OP 提到你不应该这样做,但原因是因为使用using sum(x, []) to flatten a list takes quadratic time and is really inefficient
  • 特别相关,因为@PetteriNevavuori 使用“可变长度张量”,在这种情况下,这实际上可能很重要,因为它们在 ML 上下文中可能很长。
  • 现在@pault 稍微改变了他的回应,他的回应对我来说是最清楚的。感谢您提供解决方案!在我看来,效率确实很重要。
【解决方案4】:

那里不是最适合您使用的数据结构。我建议重构代码并选择除 3 次嵌套列表之外的其他内容来保留数据,但如果目前不可能,我建议采用以下方法:

import itertools


def flatten(iterable):
    yield from itertools.chain.from_iterable(iterable)


result = []
for elements in zip(*map(flatten, samples)):
    result.append(list(flatten(elements)))

对于你的例子,它给出了:

[['A', 'E', '1'], 
 ['B', 'F', '2'], 
 ['C', 'G', '3'], 
 ['D', 'H', '4']]

测试超过 2 个样本:

samples = [[[['A', 'E'], ['B', 'F']],
            [['C', 'G'], ['D', 'H']]],
           [[['1'], ['2']], 
            [['3'], ['4']]], 
           [[['5'], ['6']],
            [['7'], ['8']]]]

给予:

[['A', 'E', '1', '5'],
 ['B', 'F', '2', '6'],
 ['C', 'G', '3', '7'],
 ['D', 'H', '4', '8']]

说明:

flattengenerator function 只是将嵌套迭代的 1 层展平。它基于itertools.chain.from_iterable 函数。在map(flatten, samples) 中,我们将此函数应用于samples 的每个元素:

>>> map(flatten, samples)
<map at 0x3c6685fef0>  # <-- map object returned, to see result wrap it in `list`:

>>> list(map(flatten, samples))
[<generator object flatten at 0x0000003C67A2F9A8>,  # <-- will flatten the 1st sample
 <generator object flatten at 0x0000003C67A2FA98>,  # <-- ... the 2nd
 <generator object flatten at 0x0000003C67A2FB10>]  # <-- ... the 3rd and so on if there are more

# We can see what each generator will give by applying `list` on each one of them
>>> list(map(list, map(flatten, samples)))
[[['A', 'E'], ['B', 'F'], ['C', 'G'], ['D', 'H']],
 [['1'], ['2'], ['3'], ['4']],
 [['5'], ['6'], ['7'], ['8']]]

接下来,我们可以使用zip 来遍历展平的样本。请注意,我们不能直接将其应用于map 对象:

>>> list(zip(map(flatten, samples)))
[(<generator object flatten at 0x0000003C66944138>,),
 (<generator object flatten at 0x0000003C669441B0>,),
 (<generator object flatten at 0x0000003C66944228>,)]

我们应该先unpack它:

>>> list(zip(*map(flatten, samples)))
[(['A', 'E'], ['1'], ['5']),
 (['B', 'F'], ['2'], ['6']),
 (['C', 'G'], ['3'], ['7']),
 (['D', 'H'], ['4'], ['8'])]

# or in a for loop:
>>> for elements in zip(*map(flatten, samples)):
...     print(elements)
(['A', 'E'], ['1'], ['5'])
(['B', 'F'], ['2'], ['6'])
(['C', 'G'], ['3'], ['7'])
(['D', 'H'], ['4'], ['8'])

最后,我们只需要将每个 elements 元组中的所有列表连接在一起。我们可以为此使用相同的flatten 函数:

>>> for elements in zip(*map(flatten, samples)):
...     print(list(flatten(elements)))
['A', 'E', '1', '5']
['B', 'F', '2', '6']
['C', 'G', '3', '7']
['D', 'H', '4', '8']

您只需将它们全部放回列表中,如第一个代码示例所示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2019-01-08
    • 1970-01-01
    • 2020-07-20
    • 2023-03-07
    • 2018-08-21
    • 2021-10-21
    • 2015-03-03
    相关资源
    最近更新 更多