【问题标题】:Group a list of tuples on two values, and return a list of all the third value对两个值的元组列表进行分组,并返回所有第三个值的列表
【发布时间】:2019-09-12 15:51:46
【问题描述】:

我有一个元组列表,每个元组包含三个值。我想将它们“汇总”或分组,以便对于前两个值相同的所有元组,它将返回一个列表列表,其中每个组件列表包含:1:第一个值,2:第二个值,3 : 与前两个匹配的所有第三个值的列表。

因为我在这里编写了整个脚本,所以我对数据类型有一定的灵活性,所以如果我以完全错误的方式接近它,请告诉我。我确实想知道是否有更简单的方法可以使用 Pandas 来完成它。

我想知道使用itertools.groupby() 是否有可能实现这一点。我认为它可能需要与 operator.itemgetter() 结合使用才能访问各种元组的正确部分。

import itertools
import operator

list = [(1, 1, 4), (1, 1, 9), (1, 1, 14), (2, 1, 12), (2, 1, 99), (2, 6, 14), (2, 6, 19)]

list=sorted(list)

def sorter(list):
     grouper = itertools.groupby(list, operator.itemgetter(0))
     for key, subiter in grouper:
          l = []
          grouper2 = itertools.groupby(subiter, operator.itemgetter(0))
          for key, subiter in grouper2: 
               l.append(subiter)
               yield key, l

这段代码代表了我所想的大致方向,但它不会产生所需的输出。所需的输出是:

[[1, 1, [4, 9, 14]], [2, 1, [12, 99]], [2, 6, [14, 19]]]

再次,我在这里的数据类型方面具有很大的灵活性,所以如果我遇到这个错误,我愿意尝试完全不同的东西。

【问题讨论】:

    标签: python python-3.x list


    【解决方案1】:

    无需使用两个嵌套的groupby 按单个字段分组。而是使用带有两个参数的 itemgetterlambda 来同时按前两个值分组,然后使用列表解析来获取最终元素。

    >>> from itertools import groupby
    >>> from operator import itemgetter
    >>> lst = [(1, 1, 4), (1, 1, 9), (1, 1, 14), (2, 1, 12), (2, 1, 99), (2, 6, 14), (2, 6, 19)]
    >>> [(*k, [x[2] for x in g]) for k, g in groupby(lst, key=itemgetter(0, 1))]
    [(1, 1, [4, 9, 14]), (2, 1, [12, 99]), (2, 6, [14, 19])]
    

    如果出于某种原因,您想要使用两个单独的groupby,您可以使用这个:

    >>> [(k1, k2, [x[2] for x in g2]) for k1, g1 in groupby(lst, key=itemgetter(0))
    ...                               for k2, g2 in groupby(g1,  key=itemgetter(1))]
    [(1, 1, [4, 9, 14]), (2, 1, [12, 99]), (2, 6, [14, 19])]
    

    当然,这也可以用作常规(嵌套)循环,更符合您的原始代码:

    def sorter(lst):
         for k1, g1 in groupby(lst, key=itemgetter(0)):
             for k2, g2 in groupby(g1, key=itemgetter(1)):
                 yield (k1, k2, [x[2] for x in g2])
    

    或者使用单个groupby,返回一个生成器对象:

    def sorter(lst):
        return ((*k, [x[2] for x in g]) for k, g in groupby(lst, key=itemgetter(0, 1)))
    

    与往常一样,这假定lst 已经是sorted 由相同的key 提供。如果不是,请先排序。

    【讨论】:

    • 请注意,如果输入尚未排序,而不是先排序,这会使解决方案的平均时间复杂度为 O(n log n),它在其他答案中切换到基于 dict 的解决方案之一会更有效,以将时间成本保持在线性复杂性。
    【解决方案2】:

    另一种方法是像这样使用defaultdict

    from collections import defaultdict
    x = [(1, 1, 4), (1, 1, 9), (1, 1, 14), (2, 1, 12), (2, 1, 99), (2, 6, 14), (2, 6, 19)]
    d = defaultdict(list)
    for i in x:
        d[i[:2]].append(i[2])
    out = [[*i, j] for i, j in d.items()]
    print(out)
    

    打印

    [[1, 1, [4, 9, 14]], [2, 1, [12, 99]], [2, 6, [14, 19]]]
    

    【讨论】:

    • 可能要指出,这仅适用于列表中的元素是元组(在本例中为元组),否则(对于列表)d[i[:2]] 将不起作用。
    • @tobias_k 是的,好点。由于问题是关于元组列表的,我认为没关系,但感谢您指出。
    【解决方案3】:

    您可以通过在遍历输入列表时将值附加到子列表来创建将键映射到值的 dict,然后使用列表推导式遍历 dict 项以输出所需的子列表解包后的钥匙:

    lst = [(1, 1, 4), (1, 1, 9), (1, 1, 14), (2, 1, 12), (2, 1, 99), (2, 6, 14), (2, 6, 19)]
    mapping = {}
    for *keys, value in lst:
        mapping.setdefault(tuple(keys), []).append(value)
    print([[*keys, value] for keys, value in mapping.items()])
    

    这个输出:

    [[1, 1, [4, 9, 14]], [2, 1, [12, 99]], [2, 6, [14, 19]]]
    

    【讨论】:

    • 您的解决方案与我的结果相同,但更简洁。你能解释一下 splat 运算符在for *keys, value in lst: 中是如何工作的吗?它怎么知道只获取元组的前两个元素?
    • 具有解包操作符的项目在其他项目的位置被解析后得到剩余的切片。您可以参考PEP-3132 了解它的工作原理。
    • 不错,但仅当键始终是前 n-1 个元素时才有效。
    • @tobias_k 是的,根据 OP 对输入规范的描述应该没问题。
    【解决方案4】:

    tobias_k 打败了我。使用 groupby 假设属于同一组的元组彼此相邻。

    from itertools import groupby
    
    
    tuples = [
        (1, 1, 4),
        (1, 1, 9),
        (1, 1, 14),
        (2, 1, 12),
        (2, 1, 99),
        (2, 6, 14),
        (2, 6, 19)
    ]
    
    lists = [[*key, list(t[2] for t in group)] for key, group in groupby(tuples, key=lambda t: t[:2])]
    print(lists)
    

    【讨论】:

      【解决方案5】:

      pandas 版本可以这样实现:

      df = pd.DataFrame(l, columns=['a', 'b', 'c']) # create dataframe
      df = df.groupby(['a', 'b'])['c'].apply(list).to_frame().reset_index() #groubpy and create the list
      df.values.tolist() # unlist row to list of lists
      
      [[1, 1, [4, 9, 14]], [2, 1, [12, 99]], [2, 6, [14, 19]]]
      

      【讨论】:

        【解决方案6】:

        我会使用set 创建一组唯一的键(元组),然后只需遍历列表并将第三个值附加到字典中的键。如果您想稍后将其转换为列表列表,您可以。

        list = [(1, 1, 4), (1, 1, 9), (1, 1, 14), (2, 1, 12), (2, 1, 99), (2, 6, 14), (2, 6, 19)]
        
        setoftuples = set((item[0],item[1]) for item in list)
        
        dictoftuples = {n: [] for n in setoftuples}
        
        for tup in list:
            dictoftuples[(tup[0],tup[1])].append(tup[2])
        
        print(dictoftuples)
        

        【讨论】:

          猜你喜欢
          • 2013-11-19
          • 1970-01-01
          • 1970-01-01
          • 2019-11-25
          • 1970-01-01
          • 1970-01-01
          • 2017-03-04
          • 2019-03-21
          • 1970-01-01
          相关资源
          最近更新 更多