统一字典列表

uniqify a list of dictionaries

我在字符串中收到这样的数据:

foo = """Port  Mac Address       group-addr      vlan    ver
         s2p2  0100.5e00.0004    239.0.0.4       1       1
         s2p0  0100.5e00.0005    239.0.0.8       1       1
         s2p1  0100.5e00.0004    239.0.0.4       1       1"""

我希望将其格式化为 table。当数据进入 table 时,我希望每个数据单独一行,除非后 4 个相同(mac、group、vlan、ver)。如果发生这种情况,我希望将数据放在一行上并将两个端口并排打印

Vlan      Group       Type   Version     Port List
-----------------------------------------------------------------------
1         239.0.0.4   igmp   v1          s2p1, s2p2
1         239.0.0.8   igmp   v1          s2p0

我将数据解析成字典列表:

def parse_lines(lines):
  headers = lines[0].split()
  entries = []
  for r in lines[1:]:
    if not len(r): continue    # skip blank lines
    vals = r.split()
    e = dict(zip(headers,vals))
    entries.append(e)
  return entries

def print_table():
    print "%s %10s %10s %14s %15s" % ("Vlan", "Group", "Type", "Version", "Port List")
    print "---------------------------------------------------------"
    if foo is not None:
        entries = foo.replace("Mac Address", "Mac-Address")    
        entries = parse_lines(entries.split("\n"))

这给我留下了字典列表,格式示例:

[{'group-addr': '239.0.0.4', 'vlan': '1', 'ver': '1', 'Port': 's2p1', 'Mac-Address': '0100.5e00.0004'}, {'group-addr': '239.0.0.5', 'vlan': '1', 'ver': '1', 'Port': 's2p1', 'Mac-Address': '0100.5e00.0005'}]

我应该如何处理这些以便在打印前比较和存储它们? 创建一个新的字典?比较非端口值与整个先前字典的等价性,然后如果它们都相同,则测试端口并将值添加到新字典?

根据 Peter Bengtsson 的 Fastest way to uniqify a list in Python,结果证明两种性能最好的方法是 将序列转换为集合 - 有序或正常。

那里的代码是相当旧的 Python 版本,因此我不会将其粘贴到此处。相反,Does Python have an ordered set? 给出了第一个选项的概述(第二个是内置类型)。

要成为集合的成员,您的元素必须是可哈希的。所以,你需要例如使用 namedtuple 而不是字典来存储记录。

如果我没理解错的话,你可以按照下面的方法做。

对于您收到的每一行,获取除端口之外的所有值,并将它们作为元组键添加到字典中。

('239.0.0.4','0100.5e00.0004', '1', '1') = (group-addr, Mac-Address, vlan, ver)

当然你可以选择你最喜欢的顺序保存

与元组键关联的值是集合端口。

最后你会得到很多键值对。全部放入字典。

因此字典将如下所示:

{(group-addr, Mac-Address, vlan, ver): set(port1, port2), ...}

要添加新元素,您可以这样做:

try:
    dict[(group-addr, Mac-Address, vlan, ver)].add(port)
except KeyError:
    dict[(group-addr, Mac-Address, vlan, ver)] = set(port)

我现在无法测试,但我希望你明白逻辑。

使用Mac Address、group-addr、vlan 和 ver 作为对公共元素进行分组的键,您应该在最初创建 dict 时这样做,但这是一个使用您问题中的数据的示例:

foo = """Port  Mac Address       group-addr      vlan    ver
         s2p2  0100.5e00.0004    239.0.0.4       1       1
         s2p0  0100.5e00.0005    239.0.0.8       1       1
         s2p1  0100.5e00.0004    239.0.0.4       1       1"""

from collections import defaultdict
d = defaultdict(set)
lines = foo.splitlines()

for line in lines[1:]:
    prt,mc,gp,vl,vr = line.split()
    d[(mc,gp,vl,vr)].add(prt)
print(d)
defaultdict(<type 'set'>, {('0100.5e00.0004', '239.0.0.4', '1', '1'): set(['s2p2', 's2p1']), ('0100.5e00.0005', '239.0.0.8', '1', '1'): set(['s2p0'])})


print "%s %10s  %14s %15s" % ("Vlan", "Group", "Version", "Port List")
print "---------------------------------------------------------"
for mc, gp, vl, vr in d:
    print("{:<10} {:<14} {:<15}".format(vl,gp,vr)) +",".join(d[mc, gp, vl, v])

Vlan      Group         Version       Port List
---------------------------------------------------------
1          239.0.0.4      1              s2p2,s2p1
1          239.0.0.8      1              s2p0

一个非常幼稚的解决方案:

from collections import defaultdict
def group_entries(entries):
    grouped = defaultdict(list)
    for entry in entries:
        port = entry.pop("Port")
        key = tuple(entry.items())
        grouped[key].append(port)
    results = []
    for entry, ports in grouped.items():
        entry = dict(entry)
        entry["ports"] = ", ".join(ports)
        results.append(entry)
    return results


def print_table():
    print "%s %10s %10s %14s %15s" % ("Vlan", "Group", "Type", "Version", "Port List")
    print "---------------------------------------------------------"
    if foo is not None:
        entries = foo.replace("Mac Address", "Mac-Address")    
        entries = parse_lines(entries.split("\n"))
        entries = group_entries(entries)
        # etc

但这在更大的数据集上可能效率很低。

重要的是使用您希望保持不变的字段(mac、group、vlan 和 ver)为字典创建一个元组键。然后,创建一个变量来保存端口。我选择了一个列表 - 你可以使用其他人建议的集合 - 当你打印到 "uniqify" 端口时,我将它作为一个选项包括在内。我没有特别对输出进行任何格式化 - 只是按照您的指南进行操作。我看不出最后的 table 中的 "type" 是从哪里来的 - 但我相信你可以适应它。

此外,您的最终 table 不包含 MAC 列。如果您不需要每个 MAC 地址一行,只需将其从字典键

中删除
foo = """Port  Mac Address       group-addr      vlan    ver
             s2p2  0100.5e00.0004    239.0.0.4       1       1
             s2p0  0100.5e00.0005    239.0.0.8       1       1
             s2p1  0100.5e00.0004    239.0.0.4       1       1"""

lines = foo.splitlines()
headers = lines[0]

machineDict={}
for line in lines[1:]:
    prt,mac,grp,vl,vr = line.split()
    try:
        #try to add a new port to the entry with this key
        machineDict[(mac,grp,vl,vr)].append(prt)
    except KeyError:
        #key error signals the dictionary doesn't contain that key
        # so create an entry
        machineDict[(mac,grp,vl,vr)] = [prt]

print "%s %10s %10s %14s %15s" % ("Vlan", "Group", "Type", "Version", "Port List")
print "---------------------------------------------------------"
for (mac,grp,vl,vr),portList in machineDict.items():
    print "%s %10s %10s %14s %15s" % (vl,grp,"typeVar",vr,list(set(portList)))

请注意 list(set(portList)) 构造只是 "uniqifies" 您的 mac 网络的端口列表。因为它们在您的输入数据中可能是独一无二的 - 如果适合您,您可以将其替换为 portList