【问题标题】:Python join two nested listsPython加入两个嵌套列表
【发布时间】:2017-03-01 00:03:16
【问题描述】:

我有两个嵌套的字符串列表:

listA = [["SomeString1", "A", "1"],
         ["SomeString2", "A", "2"],
         ["SomeString3", "B", "1"],
         ["SomeString4", "B", "2"]]


listB = [["OtherString1", "A", "1"],
         ["OtherString2", "A", "2"],
         ["OtherString3", "B", "1"],
         ["OtherString4", "B", "2"]]

对于 A 中的每个列表,我想在 B 中找到 (sublistB[1] == sublistA[1]) and (sublistB[2] == sublistA[2])(零索引)的列表。

然后我想将“B”子列表的第一个条目附加到“A”子列表中,这样最终输出将是:

joined = [["SomeString1", "A", "1", "OtherString1"],
         ["SomeString2", "A", "2", "OtherString2"],
         ["SomeString3", "B", "1", "OtherString3"],
         ["SomeString4", "B", "2", "OtherString4"]]

或者更好的是,将条目插入到位置 1:

joined = [["SomeString1", "OtherString1", "A", "1"],
         ["SomeString2", "OtherString2", "A", "2"],
         ["SomeString3", "OtherString3", "B", "1"],
         ["SomeString4", "OtherString4", "B", "2"]]

在 python 中执行此操作的最佳方法是什么?我有一个实现,但有 3 个嵌套循环,这需要一些时间。 我感觉mapfilter 和/或reduce 可能会有所帮助,但不确定如何实施?

请注意,列表不一定像我在此处的示例中那样整齐排列。

另外,这非常重要 - 列表的长度可能不同,也不能保证每个子列表都包含匹配项。如果找不到匹配项,我想追加无。

【问题讨论】:

  • 顺序重要吗?字典可能是更好的数据结构,例如使用 ("A", "1") 作为键。
  • 顺序无关紧要。如果输出是字典没问题,但输入是两个列表列表

标签: python list join filtering


【解决方案1】:

使用字典“索引”来自listB 的字符串:

listBstrings = {tuple(lst[1:]): lst[0] for lst in listB}

这会将(listB[x][1], listB[x][2]) 元组映射到listB[x][0] 字符串。现在您可以查找这些并在单个循环中生成joined

joined = [[lst[0], listBstrings[lst[1], lst[2]]] + lst[1:] for lst in listA]

如果listB 中从未出现这两个元素,您可能需要使用listBstrings.get((lst[1], lst[2]), '') 来生成默认的空字符串。

总而言之,这需要线性时间 O(N + M),其中 N 和 M 是输入列表长度。将此与您的嵌套循环方法进行比较,后者需要 O(N * M) 二次时间。不同之处在于,使用上述方法,两个包含 10 个元素的列表每个需要 20 次迭代,而嵌套循环解决方案中的 100 个,我的 100 个元素需要 200 次迭代,而嵌套需要 10.000 次迭代,等等。

演示:

>>> from pprint import pprint
>>> listA = [["SomeString1", "A", "1"],
...          ["SomeString2", "A", "2"],
...          ["SomeString3", "B", "1"],
...          ["SomeString4", "B", "2"]]
>>> listB = [["OtherString1", "A", "1"],
...          ["OtherString2", "A", "2"],
...          ["OtherString3", "B", "1"],
...          ["OtherString4", "B", "2"]]
>>> listBstrings = {tuple(lst[1:]): lst[0] for lst in listB}
>>> joined = [[lst[0], listBstrings[lst[1], lst[2]]] + lst[1:] for lst in listA]
>>> pprint(joined)
[['SomeString1', 'OtherString1', 'A', '1'],
 ['SomeString2', 'OtherString2', 'A', '2'],
 ['SomeString3', 'OtherString3', 'B', '1'],
 ['SomeString4', 'OtherString4', 'B', '2']]

【讨论】:

  • 谢谢,没关系 - 您已将其减少到两个循环,这很好。
  • @jramm: 这是两个 sequential 循环;一个在 listB 上,另一个在 listA 上。复杂度 O(N + M) 其中 N 和 M 是列表大小。您使用了 嵌套 循环,导致 O(N * M) 或更差的性能。
  • 此解决方案无法正确处理listB 不包含与listA 行之一对应的行的情况。修复起来很简单:在listBstrings中查找字符串时只需使用.get()方法,并使用None作为默认参数。
  • @jramm:换句话说,当您将元素添加到列表时,我的解决方案会导致线性时间增加,而您的解决方案会导致时间二次增加。两个列表,每个列表包含 10 个元素,用我的解决方案处理需要 20 次迭代,你的嵌套循环需要 100 次迭代,等等。
【解决方案2】:

与@MartijnPieters 回答类似的方法,但使用字典生成器:

from pprint import pprint
listA = [["SomeString1", "A", "1"],
         ["SomeString2", "A", "2"],
         ["SomeString3", "B", "1"],
         ["SomeString4", "B", "2"],
         ["SomeString5", "C", "1"]]
listB = [["OtherString1", "A", "1"],
         ["OtherString2", "A", "2"],
         ["OtherString3", "B", "1"],
         ["OtherString4", "B", "2"], 
         ["OtherString5", "C", "2"]]
dictB = dict( ((x[1], x[2]), x[0]) for x in listB )
joined = [ [ a[0], dictB.get((a[1], a[2])), a[1], a[2] ] for a in listA ]
pprint(joined)

结果:

[['SomeString1', 'OtherString1', 'A', '1'],
 ['SomeString2', 'OtherString2', 'A', '2'],
 ['SomeString3', 'OtherString3', 'B', '1'],
 ['SomeString4', 'OtherString4', 'B', '2'],
 ['SomeString5', None, 'C', '1']]

我不确定使用 dict 生成器是否会导致更快的评估,但它可能会节省内存使用。


另一种变体是使用两个 dict 理解并迭代其中一个的项目:

dictA = dict( ((x[1], x[2]), x[0]) for x in listA )
dictB = dict( ((x[1], x[2]), x[0]) for x in listB )
joined = [ [ v, dictB.get(k), k[0], k[1] ] for k, v in dictA.iteritems() ]

也许更有知识的 pythonistas 可以评论这两种不同方法的优缺点(或者我会发布另一个问题)。

【讨论】:

    【解决方案3】:

    这是我对嵌套循环连接的实现。它需要两个列表,以及另外两个包含要连接的列的索引的列表。例如:如果将 a[1] 连接到 b[2] 并将 a[2] 连接到 b[3],则参数将如下所示: 加入(a,[1,2],b,[2,3])

    listA = [["SomeString1", "A", "1"],
             ["SomeString2", "A", "2"],
             ["SomeString3", "B", "1"],
             ["SomeString4", "B", "2"]]
    
    
    listB = [["OtherString1", "A", "1"],
             ["OtherString2", "A", "2"],
             ["OtherString3", "B", "1"],
             ["OtherString4", "B", "2"]]
    
    def join(a,a_keys,b,b_keys):
        joined = []
        for i,a_rec in enumerate(a):
            for j,b_rec in enumerate(b):
                satisfies_keys = True
                for l in range(0,len(a_keys)):
                    if a[i][a_keys[l]] != b[j][b_keys[l]]:
                        satisfies_keys = False
                if satisfies_keys:
                    joined.append([a_rec, b_rec])
        return joined
    
    print(join(listA,[1,2],listB,[1,2]))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-04
      • 2016-03-04
      • 2012-08-31
      • 1970-01-01
      • 2022-01-20
      • 1970-01-01
      • 2014-02-11
      • 1970-01-01
      相关资源
      最近更新 更多