【问题标题】:How to map the differences between two strings?如何映射两个字符串之间的差异?
【发布时间】:2018-09-27 22:04:59
【问题描述】:

我遇到了以下问题,想知道什么是解决它的优雅方法。 假设我们有两个字符串:

string1 = "I love to eat $(fruit)"
string2 = "I love to eat apples"

这些字符串之间的唯一区别是$(fruit)apples。 所以,我可以找到水果是苹果,并且可以返回一个dict{fruit:apples}

另一个例子是:

string1 = "I have $(food1), $(food2), $(food3) for lunch"
string2 = "I have rice, soup, vegetables for lunch"

我想要dict{food1:rice, food2:soup, food3:vegetables} 作为结果。

谁知道如何实现它?

编辑

我认为我需要更强大的功能。

ex.
string1 = "I want to go to $(place)"
string2 = "I want to go to North America"

result: {place : North America}

ex.
string1 = "I won $(index)place in the competition"
string2 = "I won firstplace in the competition"

result: {index : first}

规则将是映射字符串的不同部分并使其成为字典

所以我猜所有使用 str.split() 或尝试拆分字符串的答案都行不通。没有规定将哪些字符用作字符串中的分隔符。

【问题讨论】:

  • 不确定它是否会对某些答案产生影响,但我认为您需要处理具有复合名称的食物。即“蛤蜊浓汤”
  • @JLPeyret 你是对的,我不希望将字符串分成不同的部分,因为空格并不总是分隔符。

标签: python regex string python-3.x


【解决方案1】:

我认为这可以通过基于正则表达式的拆分干净地完成。这也应该处理标点符号和其他特殊字符(在空间上分割是不够的)。

import re

p = re.compile(r'[^\w$()]+')
mapping = {
    x[2:-1]: y for x, y in zip(p.split(string1), p.split(string2)) if x != y}

对于您的示例,这将返回

{'fruit': 'apple'}

{'food1': 'rice', 'food2': 'soup', 'food3': 'vegetable'}

【讨论】:

  • 根据使用的插值变量类型,这可能不起作用。例如,如果使用$(food-1),它将不会返回正确的结果。你能谈谈这个解决方案的局限性吗?
  • @Ruzihm 公平点,除非令牌是字母数字,否则这通常不起作用。
  • 这真是太好了。非常简单,但对简单的情况很有效,而且效率很高。我的解决方案更通用,仍然需要进行一些调整以使其健壮,并且在任何情况下都可能要慢得多...
【解决方案2】:

一种解决方案是将$(name) 替换为(?P<name>.*) 并将其用作正则表达式:

def make_regex(text):
    replaced = re.sub(r'\$\((\w+)\)', r'(?P<\1>.*)', text)
    return re.compile(replaced)

def find_mappings(mapper, text):
    return make_regex(mapper).match(text).groupdict()

示例用法:

>>> string1 = "I have $(food1), $(food2), $(food3) for lunch"
>>> string2 = "I have rice, soup, vegetable for lunch"
>>> string3 = "I have rice rice rice, soup, vegetable for lunch"
>>> make_regex(string1).pattern
'I have (?P<food1>.*), (?P<food2>.*), (?P<food3>.*) for lunch'
>>> find_mappings(string1, string2)
{'food1': 'rice', 'food3': 'vegetable', 'food2': 'soup'}
>>> find_mappings(string1, string3)
{'food1': 'rice rice rice', 'food3': 'vegetable', 'food2': 'soup'}

请注意,这可以处理非字母数字标记(请参阅 food1rice rice rice)。显然,这可能会进行大量的回溯,并且可能会很慢。您可以调整 .* 正则表达式以尝试根据您对“令牌”的期望使其更快。


对于生产就绪代码,您需要re.escape (?P&lt;name&gt;.*) 组之外的部分。做起来有点痛苦,因为您必须“拆分”该字符串并在每个部分上调用re.escape,将它们放在一起并调用re.compile


自从我的回答被接受后,我想包含一个更强大的正则表达式版本:

def make_regex(text):
    regex = ''.join(map(extract_and_escape, re.split(r'\$\(', text)))
    return re.compile(regex)

def extract_and_escape(partial_text):
    m = re.match(r'(\w+)\)', partial_text)
    if m:
        group_name = m.group(1)
        return ('(?P<%s>.*)' % group_name) + re.escape(partial_text[len(group_name)+1:])
    return re.escape(partial_text)

这可以避免文本包含特殊正则表达式字符(例如I have $(food1) and it costs $$$)时出现的问题。第一个解决方案最终会将$$$ 视为$ 锚点的三倍(这将失败),这个强大的解决方案可以避开它们。

【讨论】:

    【解决方案3】:

    我想这可以解决问题。

    s_1 = 'I had $(food_1), $(food_2) and $(food_3) for lunch'
    s_2 = 'I had rice, meat and vegetable for lunch'
    
    result = {}
    for elem1, elem2 in zip(s_1.split(), s_2.split()):
        if elem1.startswith('$'):
            result[elem1.strip(',')[2:-1]] = elem2
    print result
    # {'food_3': 'vegetable', 'food_2': 'meat', 'food_1': 'rice,'}
    

    【讨论】:

    • 我也是这么想的。我也喜欢不惜一切代价避免使用正则表达式,所以 +1 ;)
    【解决方案4】:

    如果您不想使用正则表达式:

    string1 = "I have $(food1), $(food2), $(food3) for lunch"
    string2 = "I have rice, soup, vegetable for lunch"
    trans_table = str.maketrans({'$': '', '(': '', ')': '', ',': ''})
    {
        substr1.translate(trans_table): substr2.translate(trans_table)
        for substr1, substr2 in zip(string1.split(),string2.split())
        if substr1 != substr2
    }
    

    输出:

    {'food1': 'rice', 'food2': 'soup', 'food3': 'vegetable'}
    

    或者,一些更灵活的东西:

    def substr_parser(substr, chars_to_ignore='$(),'):
        trans_table = str.maketrans({char: '' for char in chars_to_ignore})
        substr = substr.translate(trans_table)
        # More handling here
        return substr
    
    {
        substr_parser(substr1): substr_parser(substr2)
        for substr1, substr2 in zip(string1.split(),string2.split())
        if substr1 != substr2
    }
    

    与上面的输出相同。

    【讨论】:

      【解决方案5】:

      你可以使用re:

      import re
      def get_dict(a, b):
        keys, values = re.findall('(?<=\$\().*?(?=\))', a), re.findall(re.sub('\$\(.*?\)', '(\w+)', a), b)
        return dict(zip(keys, values if not isinstance(_values[0], tuple) else _values[0]))
      
      d = [["I love to eat $(fruit)", "I love to eat apple"], ["I have $(food1), $(food2), $(food3) for lunch", "I have rice, soup, vegetable for lunch"]]
      results = [get_dict(*i) for i in d]
      

      输出:

      [{'fruit': 'apple'}, {'food3': 'vegetable', 'food2': 'soup', 'food1': 'rice'}]
      

      【讨论】:

        【解决方案6】:

        你可以这样做:

        >>> dict((x.strip('$(),'),y.strip(',')) for x,y in zip(string1.split(), string2.split()) if x!=y)
        {'food1': 'rice', 'food2': 'soup', 'food3': 'vegetable'}
        

        或者使用正则表达式:

        >>> import re 
        >>> dict((x, y) for x,y in zip(re.findall(r'\w+', string1), re.findall(r'\w+', string2)) if x!=y)
        {'food1': 'rice', 'food2': 'soup', 'food3': 'vegetable'}
        

        【讨论】:

          【解决方案7】:

          zipdictionary comprehension 结合使用效果很好,在这里我们可以zip 两个列表,只取不相等的对。

          l = [*zip(s1.split(),s2.split())]
          d = {i[0].strip('$(),'): i[1] for i in l if i[0] != i[1] }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多