展开并平铺一个不规则的嵌套列表

4

我知道关于扁平化嵌套列表的主题已经被详细讨论过了,但是我认为我的任务有点不同,而且我找不到任何信息。

我正在编写一个爬虫程序,输出结果是一个嵌套列表。顶层列表元素应成为电子表格形式中的数据行。然而,由于嵌套列表长度经常不同,因此我需要在扁平化列表之前扩展它们。

这是一个例子。我有

   [ [ "id1", [["x", "y", "z"], [1, 2]],    ["a", "b", "c"]],
     [ "id2", [["x", "y", "z"], [1, 2, 3]], ["a", "b"]],
     [ "id3", [["x", "y"],      [1, 2, 3]], ["a", "b", "c", ""]] ]

我最终想要的输出是:
   [[ "id1", "x", "y",  z, 1, 2, "", "a", "b", "c", ""],
    [ "id2", "x", "y",  z, 1, 2,  3, "a", "b",  "", ""],
    [ "id3", "x", "y", "", 1, 2,  3, "a", "b", "c", ""]]

然而,像这样的中间列表:
   [ [ "id1", [["x", "y", "z"], [1, 2, ""]], ["a", "b", "c", ""]],
     [ "id2", [["x", "y", "z"], [1, 2,  3]], ["a", "b",  "", ""]],
     [ "id3", [["x", "y",  ""], [1, 2,  3]], ["a", "b", "c", ""]] ]

如果我能够简单地展开它,那就更好了。

每次迭代都会构建顶层列表元素(行),并将其附加到完整列表中。我想在最后转换完整列表会更容易一些?

嵌套元素的结构应该相同,但此时我无法确定。如果结构看起来像这样,我想我会遇到问题。

   [ [ "id1", [[x, y, z], [1, 2]],             ["a", "b", "c"]],
     [ "id2", [[x, y, z], [1, 2, 3]], ["bla"], ["a", "b"]],
     [ "id3", [[x, y],    [1, 2, 3]],          ["a", "b", "c", ""]] ]

应该变成

   [[ "id1", x, y,  z, 1, 2, "",    "", "a", "b", "c", ""],
    [ "id2", x, y,  z, 1, 2,  3, "bla", "a", "b",  "", ""],
    [ "id3", x, y, "", 1, 2,  3,    "", "a", "b", "c", ""]]

感谢任何评论,如果这很琐碎,请原谅,我对Python还很陌生。

1
请澄清一下您希望如何表示空白,因为[x, y, , 1, 2, 3, "a", "b", "c", ""]看起来不像是有效的Python列表 - 您必须在y之后和1之前放置一些内容。您希望它是None吗?但这将与您在列表末尾用作空白""冲突... - Ihor Kaharlichenko
1
不清楚xyz是什么。它们是预先定义的某种常量或变量吗? - Ihor Kaharlichenko
你会如何处理最后一个例子?我的意思是数据集中的第二行有4个元素,而其余行只有3个。是否应该在右侧用空格填充其余的行? - Ihor Kaharlichenko
再次扩展问题。在这种情况下,应该插入空白,以便结果显示为所示。我对应的列表/列进行了对齐。 - ilprincipe
在您的最后一个示例中,您如何知道要使用 "" 填充项目#1和项目#3,因为是由于 bla 而不是填充 ["a", "b", "c", ""] 中的项目#3 以使其变为 ["bla", "", "", ""] - sloth
显示剩余5条评论
3个回答

6

对于“相同结构”的情况,我有一个简单的解决方案,使用递归生成器和itertools中的izip_longest函数。这段代码适用于Python 2,但稍作修改(在注释中说明),也可在Python 3上运行:

from itertools import izip_longest # in py3, this is renamed zip_longest

def flatten(nested_list):
    return zip(*_flattengen(nested_list)) # in py3, wrap this in list()

def _flattengen(iterable):
    for element in izip_longest(*iterable, fillvalue=""):
        if isinstance(element[0], list):
            for e in _flattengen(element):
                yield e
        else:
            yield element

在Python 3.3中,由于PEP 380的支持,这将变得更加简单,它允许递归步骤for e in _flatengen(element): yield e变成yield from _flattengen(element)

这是一个优雅而灵活的解决方案,也是使用递归有效的绝佳场所。 - Dale Bustad
我目前正在测试这个,看起来它能够正常工作。我已经识别出大部分结构偏差的情况,并会提前处理好它们。 - ilprincipe

3

实际上,在结构不相同的通用情况下,没有解决方案。 例如,普通算法会将["bla"]["a", "b", "c"]匹配,结果将是:

 [  [ "id1", x, y,  z, 1, 2, "",   "a", "b", "c", "",  "",  ""],
    [ "id2", x, y,  z, 1, 2,  3, "bla",  "",  "", "", "a", "b"],
    [ "id3", x, y, "", 1, 2,  3,   "a", "b", "c", "",  "",  ""]]

但是,如果您知道会有许多行,每行以一个ID开头,后面跟着一个嵌套列表结构,则下面的算法应该可以解决问题:

import itertools

def normalize(l):
    # just hack the first item to have only lists of lists or lists of items
    for sublist in l:
        sublist[0] = [sublist[0]]

    # break the nesting
    def flatten(l):
        for item in l:
            if not isinstance(item, list) or 0 == len([x for x in item if isinstance(x, list)]):
                yield item
            else:
                for subitem in flatten(item):
                    yield subitem

    l = [list(flatten(i)) for i in l]

    # extend all lists to greatest length
    list_lengths = { }
    for i in range(0, len(l[0])):
        for item in l:
            list_lengths[i] = max(len(item[i]), list_lengths.get(i, 0))

    for i in range(0, len(l[0])):
        for item in l:
            item[i] += [''] * (list_lengths[i] - len(item[i]))

    # flatten each row
    return [list(itertools.chain(*sublist)) for sublist in l]

l = [ [ "id1", [["x", "y", "z"], [1, 2]],    ["a", "b", "c"]],
      [ "id2", [["x", "y", "z"], [1, 2, 3]], ["a", "b"]],
      [ "id3", [["x", "y"],      [1, 2, 3]], ["a", "b", "c", ""]] ]
l = normalize(l)
print l

0
def recursive_pad(l, spacer=""):
    # Make the function never modify it's arguments.
    l = list(l)

    is_list = lambda x: isinstance(x, list)
    are_subelements_lists = map(is_list, l)
    if not any(are_subelements_lists):
        return l

    # Would catch [[], [], "42"]
    if not all(are_subelements_lists) and any(are_subelements_lists):
        raise Exception("Cannot mix lists and non-lists!")

    lengths = map(len, l)
    if max(lengths) == min(lengths):
        #We're already done
        return l
    # Pad it out
    map(lambda x: list_pad(x, spacer, max(lengths)), l)
    return l

def list_pad(l, spacer, pad_to):
    for i in range(len(l), pad_to):
        l.append(spacer)

if __name__ == "__main__":
    print(recursive_pad([[[[["x", "y", "z"], [1, 2]], ["a", "b", "c"]], [[[x, y, z], [1, 2, 3]], ["a", "b"]], [[["x", "y"], [1, 2, 3]], ["a", "b", "c", ""]] ]))

编辑:实际上,我误读了你的问题。这段代码解决了一个稍微不同的问题。


有一个打字错误:lengths = map(len(l)) 应该改为 lengths = map(len, l) - sloth
如果我理解正确的话,这应该将列表扩展到一个共同的长度?然而,当我运行它时,实际上并没有改变任何东西。 - ilprincipe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接