【问题标题】:How to merge dictionaries and group by value in python?如何在python中合并字典和按值分组?
【发布时间】:2014-12-10 15:59:58
【问题描述】:

我在 python 中有这样的字典:

{
'k1': 'AAB',
'k2': 'AAB',
'k3': 'ABA',
'k4': 'AAB',
'k5': 'A--',
'k6': 'AB-',
'k7': 'BB-',
'k8': 'B--',
'k9': 'B-B',
'k10': '--C',
}

其中“-”是通配符。我想通过按键分组来合并它们

{
'k1, k2, k4, [k5]' : 'AAB',
'k3, (k6), [k5]' : 'ABA',
'{k7, k8, k9}' : 'B--',
'{k10}' : '--C',
}

只有一种可能的组合(即k6)匹配时,带有通配符的项目应放在括号之间,如果匹配多个组合(即k5),则应放在括号之间。此外,当带通配符的值与任何其他不带通配符的完整元素都不匹配时),它们应该在大括号之间,并且值应该是具有更多通配符的那个。

我已经设法使用正则表达式将字符串与通配符进行比较:

# v1 and v2 are the values to compare
v1_reg = v1.replace("-","?")
cont = 0
for s in v2:
    if s == "-":
        v1_reg = v1_reg[:cont] + "?" + v1_reg[cont+1:]
    cont += 1
if(fnmatch(v2, v1_reg)):
   ##THERE'S A MATCH

我仍然没有找到分组的方法。

【问题讨论】:

  • 我认为您的意思是按价值分组。
  • 为什么k5 匹配ABA
  • 是的,这是我的错误,已更新
  • 一个通配符可以匹配多少个字符?
  • 建议的解决方案是唯一可能的吗?为什么?对不起,如果这是一个虚拟问题...

标签: python regex dictionary


【解决方案1】:

以下代码将根据需要进行匹配和分组,并且作为奖励,它将过滤通配符不匹配。我也为您实现了TransUnit 之间的比较。使用它来查看两个TransUnits 是否相等或一个更一般。自己检查一下,看看它是如何工作的:

from collections import defaultdict

class Char:
    def __init__(self, c):
        self._c = c

    def match(self, obj): return obj.match_c(self._c)

    def match_c(self, cc):
        return self._c == cc

    def match_w(self): return True


class WildCard:
    def __init__(self):
        pass

    def match(self, obj): return obj.match_w()

    def match_c(self, cc): return True

    def match_w(self): return True


class TransUnit(object):
    def __init__(self, key, s):
        self._val = s
        self._arr = [WildCard() if c == '-' else Char(c) for c in s]
        self._key = key
        self._matches = 0
        self._has_wildcard = any(map(lambda x: isinstance(x, WildCard), self._arr))

    @property
    def has_wildcard(self): return self._has_wildcard

    @property
    def val(self): return self._val

    def found_match(self): self._matches += 1

    def match(self, obj):
        if isinstance(obj, str):
            return TransUnit(None, obj).match_arr(self._arr)
        return obj.match_arr(self._arr)

    def match_arr(self, arr):
        if len(self._arr) != len(arr): return False
        for i in range(len(arr)):
            if not self._arr[i].match(arr[i]):
                return False
        return True

    def compare(self, obj):
        return -obj.compare_arr(self._arr)

    def compare_arr(self, arr):
        if not self.match_arr(arr): raise Exception()
        for i in range(len(arr)):
            if not isinstance(self._arr[i], WildCard) and isinstance(arr[i], WildCard):
                return -1
            elif not isinstance(arr[i], WildCard) and isinstance(self._arr[i], WildCard):
                return 1
        return 0

    def __str__(self):
        if self._has_wildcard:
            if self._matches <= 1:
                return '(' + self._key + ')'
            else:
                return '[' + self._key + ']'
        else:
            return self._key

    __repr__ = __str__


def group(dct):
    trans = [TransUnit(k, dct[k]) for k in dct]
    wilds = [t for t in trans if t.has_wildcard]
    nonwilds = [t for t in trans if not t.has_wildcard]
    grouper = defaultdict(list)
    for n in nonwilds:
        grouper[n.val].append(n)

    notmatched = []
    for w in wilds:
        matched = False
        for k, lst in grouper.items():
            if w.match(k):
                w.found_match()
                lst.append(w)
                matched = True
        if not matched:
            notmatched.append(w)

    return (grouper, notmatched)

测试:

>>> group({
'k1': 'AAB',
'k2': 'AAB',
'k3': 'ABA',
'k4': 'AAB',
'k5': 'A--',
'k6': 'AB-',
'k7': 'BB-',
'k8': 'B--',
'k9': 'B-B',
})
(defaultdict(<type 'list'>, {'ABA': [k3, (k6), [k5]], 'AAB': [k2, k1, k4, [k5]]}), [(k7), (k9), (k8)])

【讨论】:

  • 嗯,您的解决方案比我试图获得的解决方案更专业。让我看看,我会回来的
  • 这是一个非常干净的解决方案,谢谢。现在,我正在尝试将不匹配的元素与它们自己分组
  • @JuanmaCrescente 这也很容易。提示:将没有匹配的节点视为节点,则每对节点之间的边表示节点相互匹配。现在你要做的就是看看这个图被分成了多少块,然后把每个块中的节点放在一起。
  • 两种解决方案都很棒 IMO,我将其标记为已接受的答案,因为它是第一个
【解决方案2】:

编辑:我已将其修改为您的新规范。

也许有点矫枉过正。但这有效。 (请原谅可怕的变量名) 这不是最有效的方法,但它遵循合乎逻辑的步骤(至少对我而言)希望这能满足您的需要,但您应该以此为指导并对其进行改进以满足您的特定需求。

from collections import defaultdict
from operator import itemgetter
from pprint import pprint
from re import match

## Class to help display the results as you want
class Organizer():

    def __getitem__(self):
        return Organizer()

    def __init__(self):
        self.normal = []
        self.parens = []
        self.brackets = []
        self.fancy = []

    def __str__(self):
        ret = ''
        for group, a, z in [(self.normal, '', ''),
                            (self.parens, '(', ')'),
                            (self.brackets, '[', ']'),
                            (self.fancy, '{', '}')]:
            if group:  # If there is something to format
                ret += self._frmt(group, a, z)
        return ret.strip(', ')  # Remove extra formatting.

    def _frmt(self, group, a, z):
        return ', {}{}{}'.format(a, ', '.join(sorted(group)), z)

## Function that does what you want.
def match_to_wildcards(key_value_pairs):
    values_as_keys, partial = defaultdict(Organizer), {}
    # Prepare the initial groups.
    for key, value in key_value_pairs.items():
        if '-' in value:
            partial[key] = value
        else:
            values_as_keys[value].normal.append(key)
    # Clean up the partial matches.
    partial_matches = {k: [f for f in values_as_keys if match(v.replace('-', '.'), f)] for k, v in partial.items()}
    for key, values in partial_matches.items():
        if not values:
            continue
        for value in values:
            if len(values) > 1:
                values_as_keys[value].brackets.append(key)
            else:
                values_as_keys[value].parens.append(key)
            if key in partial:
                partial.pop(key)
    # Filter the last unmatched items.
    combined_unmatched = {u: [v for v in partial.values() if match(u.replace('-', '.'), v)] for u in partial.values()}
    need_to_be_rearranged = {k: [(p, p.count('-')) for p, j in combined_unmatched.items() if k in j] for k, v in combined_unmatched.items() if len(v) == 1}
    # Sort the last unmatched items.
    for key, value in partial.items():
        if value in need_to_be_rearranged:
            value_key = max(need_to_be_rearranged[value], key=itemgetter(1))[0]
            values_as_keys[value_key].fancy.append(key)
        else:
            values_as_keys[value].fancy.append(key)
    # Return the results, format them.
    return {str(v): k for k, v in values_as_keys.items()}

现在使用您的数据运行代码。

## Test
data_set = {
    'k1': 'AAB',
    'k2': 'AAB',
    'k3': 'ABA',
    'k4': 'AAB',
    'k5': 'A--',
    'k6': 'AB-',
    'k7': 'BB-',
    'k8': 'B--',
    'k9': 'B-B',
    'k10': '--C'
}
pprint(match_to_wildcards(data_set))
# {'k1, k2, k4, [k5]': 'AAB',
#  'k3, (k6), [k5]': 'ABA',
#  '{k10}': '--C',
#  '{k7, k8, k9}': 'B--'}

【讨论】:

  • 谢谢。根据规范,我意识到k9应该匹配k8和k7,并且值应该是B--。
  • @JuanmaCrescente 检查编辑。我希望你喜欢它。
猜你喜欢
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-28
  • 2017-02-24
  • 2018-03-27
  • 1970-01-01
相关资源
最近更新 更多