如何映射两个字符串之间的差异?

时间:2018-09-27 22:05:00

标签: python regex string python-3.x

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

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()或尝试拆分字符串的所有答案都将不起作用。没有规则说明将什么字符用作字符串中的分隔符。

7 个答案:

答案 0 :(得分:5)

我认为这可以通过基于正则表达式的拆分完全完成。这也应该处理标点符号和其他特殊字符(空格分隔不足)。

Uncaught TypeError: a.insertBefore is not a function

对于您的示例,此返回

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'}

答案 1 :(得分:1)

我想这可以解决问题。

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,'}

答案 2 :(得分:1)

一种解决方案是将$(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<name>.*)部分。要做些麻烦,因为您必须“分割”该字符串并在每个片段上调用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 :(得分:0)

您可以这样做:

>>> 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'}

答案 4 :(得分:0)

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

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 :(得分:0)

您可以使用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 :(得分:0)

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

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