【问题标题】:python itertools groupby several attributespython itertools groupby几个属性
【发布时间】:2021-11-17 14:22:27
【问题描述】:

我正在尝试按属性对对象进行分组。我知道如何做一个,但如何包含几个属性,例如:

class Node:
    def __init__(self, name1, name2, id_):
        self.name1 = name1
        self.name2 = name2
        self.id_= id_
 def __repr__(self):
        return f"<{self.id_}>{self.name1}-{self.name2}"

l = [Node('WWA', 'KATO', 1), Node('WWA', 'POZ', 2), Node('SZCZ', 'GDN', 3), Node('GDN', 'BYD', 4), Node('BIA', 'OLS', 5), Node('LUB', 'WWA',6 )]

get_attr = attrgetter("name1")
l= [list(g) for k, g in groupby(sorted(l, key=get_attr), get_attr )]

结果:

[[<5>BIA-OLSZ],
 [<4>GDN-BYD],
 [<6>LUB-WWA],
 [<3>SZCZ-GDN],
 [<1>WWA-KATO, <2>WWA-POZ]]

我想按 name1 或 name2 分组并得到它:

[[<5>BIA-OLSZ],
 [<4>GDN-BYD],<3>SZCZ-GDN],
 [<1>WWA-KATO, <2>WWA-POZ, <6>LUB-WWA]]

【问题讨论】:

  • 如果有另一个具有 LUB 属性的节点,您将如何对 LUB-WMA 进行分组?你会在“WMA”和“LUB”组中复制它,还是第一个属性有优先级,或者其他什么?
  • 这是我想要得到的结果:[[BIA-OLSZ], [GDN-BYD], [SZCZ-GDN], [WWA -KATO, W​​WA-POZ], LUB-WWA]] 我想要具有共同名称1-名称1、名称2-名称2或名称1-名称2的对象
  • 这已经很清楚了,我的问题是如果 LUB-GDN 在列表中,您会将它放在哪里?它会与 、 和 或与 分组,还是会导致 与 进入不同的组?
  • 另外,您的意思是 LUB-WWA 位于前一个子列表中,还是您的目标真的是让它根本不在子列表中?您的预期结果:[&lt;1&gt;WWA-KATO, &lt;2&gt;WWA-POZ], &lt;6&gt;LUB-WWA] 与您的要求相矛盾。你的意思是[&lt;1&gt;WWA-KATO, &lt;2&gt;WWA-POZ, &lt;6&gt;LUB-WWA]] 吗?
  • 对不起我的错误,应该是这样的:[[BIA-OLSZ], [GDN-BYD],SZCZ-GDN], [ WWA-加藤,WWA-POZ,LUB-WWA]]

标签: python itertools


【解决方案1】:

您无法使用 groupby 执行此操作,因为您需要为每个组设置两个标识符。

要为每个组设置多个键,您可以使用字典,其中节点列表与两个键(名称 1 和名称 2)相关联。将每个节点添加到与其名称之一相对应的组中,将生成一个(冗余)组列表作为字典的 values()。然后,您可以从中获取不同的(非空)组:

d = dict()
for n in l:
    d.setdefault(n.name1, d.setdefault(n.name2,[])).append(n) # link both names
l = [*{id(g):g for g in d.values() if g}.values()]            # distinct groups

print(l)

[[<1>WWA-KATO, <2>WWA-POZ, <6>LUB-WWA], 
 [<3>SZCZ-GDN, <4>GDN-BYD], 
 [<5>BIA-OLS]]

请注意,这并没有正式解决我关于重叠组属性的原始问题,因此如果我们将 Node('LUB','GDN',7) 添加到列表中,它将最终出现在可能的组之一中或者可能不是您想要的位置:

l = [Node('WWA', 'KATO', 1), Node('WWA', 'POZ', 2), Node('SZCZ', 'GDN', 3), 
     Node('GDN', 'BYD', 4), Node('BIA', 'OLS', 5), Node('LUB', 'WWA',6 ), 
     Node('LUB', 'GDN',7 )]

d = dict()
for n in l:
    d.setdefault(n.name1, d.setdefault(n.name2,[])).append(n) 
l = [*{id(g):g for g in d.values() if g}.values()]            

print(l)

[[<1>WWA-KATO, <2>WWA-POZ, <6>LUB-WWA, <7>LUB-GDN], 
 [<3>SZCZ-GDN, <4>GDN-BYD], 
 [<5>BIA-OLS]]

这可以通过选择具有最多节点数的属性作为分组键来解决。 Counter 类(来自集合)可以帮助确定属性频率。

from collections import Counter
f = Counter(n.name1 for n in l) + Counter(n.name2 for n in l) # frequencies
d = dict()
for n in l:
    k = (n.name1,n.name2)[(f[n.name2],n.name2)>(f[n.name1],n.name1)]
    d.setdefault(k,[]).append(n)  # group with most frequent attrib.
l = list(d.values())

print(l)
[[<1>WWA-KATO, <2>WWA-POZ, <6>LUB-WWA],
 [<3>SZCZ-GDN, <4>GDN-BYD, <7>LUB-GDN],
 [<5>BIA-OLS]]

请注意,我还在频率比较中使用属性本身,以便一致地应用平局(基于更大的名称)

【讨论】:

  • 非常感谢,非常聪明的解决方案。我将使用第二种方法。顺便说一句,我发现你可以使用这样的东西:(x, y) [True \ False]。你能告诉我这行代码是如何工作的吗(我知道 setdefault 返回一个键的值或插入一个具有默认值的键): d.setdefault (n.name1, d.setdefault (n.name2, [])) . Append (n) -> (in one iteration) {"KATO: WWA-KATO, "WWA": WWA-KATO} 这里发生了什么(这很神奇:)):[* {id ( g): g for g in d.values () if g} .values ()].
  • 从一个空字典中,内部 setdefault 为 name2 创建一个 key:list 对,该列表成为 name1 的默认值,最终两个键引用同一个列表。稍后在任何具有这两个键的节点上,它们的 name1/name2 将附加到现有列表,因为如果另一个节点的 name2 相同,它将使用列表作为 name1 的默认值,如果 name1 相同,则默认值将被忽略并使用现有列表。
  • id() 函数返回对象的内部标识符(例如列表)。通过将其用作字典理解中的键,原始字典 (d.values()) 的每个列表将仅在 id:list 的临时字典中保存一次。然后通过获取临时 id:list 字典的 values() 形成最终的组列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-28
相关资源
最近更新 更多