如果键字符串中的任何字符匹配,则对字典的值求和

Sum values of dictionary if any character within key strings match

假设我有一个包含字符串键和整数值的字典,例如:

{'255': 8,
 '323': 1,
 '438': 1,
 '938': 2,
 '166': 1,
 '117': 10,
 '777': 2
}

我想创建一个包含以下信息的新词典:

在上面的示例中,结果类似于:

{
 'group1' : 12, # Sum of key values for '255' (8), '323' (1), '438' (1), '938' (2)
 'group2' : 13  # Sum of key values for '166' (1), '117' (10), '777' (2)
}

澄清一下,group1 是通过以下方式创建的:255 有一个 2 与 323 中的 2 相匹配。然后 323 中的 3 也匹配 438 中的 3。最后,438 中的 8(或 3)匹配 938 中的 8(或 3)。因此,以相同的顺序添加这些键的值,我们得到 8 + 1 + 1 + 2 = 12.

None 的上述键值 (group1) 与其余键值合并(对于键 166117777;组成 group2) 因为 group1 键中的 none 个字符匹配 group2 键中的 任何 个字符。

Group2 的创建方式相同,将 166 中的 1 匹配到 117 中的 1,并将 117 中的 7 匹配到 777 中的 7 .

最后的笔记:

完成此任务的有效方法是什么?

我考虑过将键值转换为 numpy 矩阵,其中行代表单个键,列代表每个字符的值。然而,沿着这条路走下去很快就会变得相当复杂,如果有更好的方法,我宁愿避免它。

尝试:

d = {"255": 8, "323": 1, "438": 1, "938": 2, "166": 1, "117": 10, "777": 2}

keys, groups = list(d), {}
while keys:
    current = keys.pop()
    s_current = set(current)
    for k in groups:
        if s_current.intersection(k):
            groups[k].append(d[current])
            groups[current] = groups[k]
            break
    else:
        groups[current] = [d[current]]

out = {}
for k, v in groups.items():
    if id(v) not in out:
        out[id(v)] = sum(v)


print(out)

打印:

{140318922050368: 13, 140318911221184: 12}

一点解释:

我们创建了一个临时字典 groups,其中键来自原始字典 d,值是原始字典中与键共享一个字符的值列表。注意:列表在键之间 共享 - 这些列表的总数等于不同组的总数。

您可以使用 union-find data structure to solve this problem. The networkx package provides an implementation, but there's nothing stopping you from writing your own.

本质上,我们维护了一组不相交的集合。最初,每个字符串都属于它自己的不相交集。对于每对字符串,如果它们有共同的字母,我们将它们所属的不相交集合合并。这最终为我们提供了我们正在寻找的组。

从这里开始,我们使用 .to_sets() 方法进行分组,并计算所需的总和:

from networkx.utils.union_find import UnionFind

data = # dictionary in question, omitted for brevity
keys = list(data.keys())

uf = UnionFind(data.keys())

for outer_idx in range(len(keys)):
    for inner_idx in range(outer_idx + 1, len(keys)):
        if set(keys[outer_idx]) & set(keys[inner_idx]):
            uf.union(keys[outer_idx], keys[inner_idx])

result = {}
for idx, group in enumerate(uf.to_sets()):
    result[idx] = sum(data[key] for key in group)

print(result)

这输出:

{0: 12, 1: 13}

这与@BrokenBenchmark 的答案类似,但它是 self-contained,并且它还首先按数字分组,因此没有 O(n^2) doubly-nested 遍历所有字符串.

from collections import defaultdict

def get_edges(strings):
    # group together common digits.
    by_digit = [[] for _ in range(10)]
    for i, s in enumerate(strings):
        for digit in map(int, s):
            by_digit[digit].append(i)

    # attach the first of each group to the rest.
    for digit_locations in by_digit:
        if digit_locations:
            x = digit_locations[0]
            for y in digit_locations[1:]:
                yield (x, y)

def union_find_groups(edges, n):
    parent = list(range(n))
    size = [1] * n

    def find(x):
        # find (path-splitting)
        while parent[x] != x:
            x, parent[x] = parent[x], parent[parent[x]]
        return x

    def union(x, y):
        x = find(x)
        y = find(y)
        if x == y:
            return
        # Union (by size)
        if size[x] < size[y]:
            x, y = y, x
        parent[y] = x
        size[x] += size[y]

    for x, y in edges:
        union(x, y)
    
    result = defaultdict(list)
    for x in range(n):
        result[find(x)].append(x)

    return result.values()

def main(input_data):
    strings = list(input_data.keys())
    index_groups = union_find_groups(get_edges(strings), len(strings))
    string_groups = [[strings[i] for i in group] for group in index_groups]
    print(string_groups)
    results = [sum(input_data[s] for s in group) for group in string_groups]
    return {f"group{i}": total for i, total in enumerate(results)}

if __name__ == "__main__":
    result = main({
        '255': 8,
        '323': 1,
        '438': 1,
        '938': 2,
        '166': 1,
        '117': 10,
        '777': 2
    })
    print(result)

结果:

[['255', '323', '438', '938'], ['166', '117', '777']]
{'group0': 12, 'group1': 13}