如何比较两个字符串的差异?

5

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

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"

我希望得到一个字典{food1:米饭,food2:汤,food3:蔬菜}作为结果。
有没有好的想法来实现它?
编辑:
我认为需要使函数更加强大。
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()或尝试拆分字符串的所有答案都不会起作用。没有规定字符串中要使用哪些字符作为分隔符。


1
不确定是否会对某些答案产生影响,但我假设您需要处理由多个单词组成的食品名称。例如,“蛤蜊浓汤”。 - JL Peyret
@JLPeyret 您是正确的,我不希望将字符串拆分为不同的部分,因为空格并不总是一个分隔符。 - Billy
7个回答

5
我认为可以使用基于正则表达式的分割来实现这个操作。这种方法也可以处理标点符号和其它特殊字符(在以空格为分隔符的情况下不足以处理)。
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'}

1
根据使用的插值变量类型,这可能无法正常工作。例如,如果使用 $(food-1),它将不会返回正确的结果。您能详细说明此解决方案的限制吗? - Ruzihm
1
@Ruzihm 公正的观点,除非令牌是字母数字,否则大多数情况下都不起作用。 - cs95
这真的很棒。非常简单,但对于简单情况非常有效,并且相当高效。我的解决方案更加通用,仍需要进行一些微调才能使其更加健壮,并且在任何情况下可能都要慢得多... - Giacomo Alzetta

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

1
我也是这么想的。我几乎总是尽量避免使用正则表达式,所以加一分 ;) - Matt Messersmith

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)。显然,这可能会导致大量回溯并且速度较慢。您可以调整 .* 正则表达式以尝试根据您对“标记”的期望使其更快。

对于生产环境的代码,您需要对 (?P<name>.*) 组外的部分进行 re.escape 处理。这样做有点麻烦,因为您需要“拆分”该字符串并在每个段落上调用 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 $$$)。第一种解决方案会将$$$视为三个$锚点(失败),而这种健壮的解决方案会对它们进行转义。

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

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
}

与上述输出相同。


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

0

zip 结合 字典推导式 在这里非常有效,我们可以将两个列表进行 zip 操作,然后只取不相等的元素对。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接