检查两个字符串是不是在 Python 中包含相同的单词集

Posted

技术标签:

【中文标题】检查两个字符串是不是在 Python 中包含相同的单词集【英文标题】:Check if two strings contain the same set of words in Python检查两个字符串是否在 Python 中包含相同的单词集 【发布时间】:2019-11-07 17:33:47 【问题描述】:

我正在尝试比较两个句子,看看它们是否包含相同的单词集。 例如:比较“今天是个好日子”和“今天是个好日子”应该返回 true 我现在正在使用集合模块中的计数器功能

from collections import Counter


vocab = 
for line in file_ob:
    flag = 0
    for sentence in vocab:
        if Counter(sentence.split(" ")) == Counter(line.split(" ")):
            vocab[sentence]+=1
            flag = 1
            break
        if flag==0:
            vocab[line]=1

它似乎可以正常运行几行,但我的文本文件有超过 1000 行,而且它永远不会完成执行。有没有其他更有效的方法可以帮助我计算整个文件的结果? 编辑:

我只需要一个 Counter 方法的替代品,来替代它。并且在实施中没有任何变化。

【问题讨论】:

需要区分重复词吗? to to 是否应该匹配 to to to 如果不是,将单词列表转成set,测试两组是否相等。 我还能用什么来代替套装? 您能否展示一下循环的实际结构,以便我们可以看到vocab 是如何生成的?这将有助于提供好的答案。 感谢指出,编辑代码! 【参考方案1】:

你真的不需要使用两个循环。

正确使用字典的方法

假设你有一个dict

my_dict = 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 5, 'g': 6

你的代码基本上相当于:

for (key, value) in my_dict.items():
    if key == 'c':
        print(value)
        break
#=> 3

dict(和setCounter,...)的全部意义在于能够直接获得所需的值:

my_dict['c']
#=> 3

如果您的 dict 有 1000 个值,则第一个示例平均比第二个示例慢 500 倍。这是我在Reddit 上找到的一个简单描述:

字典就像一个神奇的外套检查室。你把外套递过去 得到一张票。每当你把那张票还给你时,你会立即得到 你的大衣。你可以有很多外套,但你仍然可以得到你的外套 马上回来。大衣里面有很多魔法 检查房间,但你并不在乎,只要你得到你的外套 马上回来。

重构代码

你只需要在"Today is a good day!""Is today a good day?"之间找到一个共同的签名。一种方法是提取单词,将它们转换为小写,对它们进行排序并连接它们。重要的是输出应该是不可变的(例如tuplestringfrozenset)。这样,它可以直接在集合、计数器或字典中使用,而无需遍历每个键。

from collections import Counter

sentences = ["Today is a good day", 'a b c', 'a a b c', 'c b a', "Is today a good day"]

vocab = Counter()
for sentence in sentences:
    sorted_words = ' '.join(sorted(sentence.lower().split(" ")))
    vocab[sorted_words] += 1

vocab
#=> # Counter('a day good is today': 2, 'a b c': 2, 'a a b c': 1)

甚至更短:

from collections import Counter

sentences = ["Today is a good day", 'a b c', 'a a b c', 'c b a', "Is today a good day"]

def sorted_words(sentence):
    return ' '.join(sorted(sentence.lower().split(" ")))

vocab = Counter(sorted_words(sentence) for sentence in sentences)
# Counter('a day good is today': 2, 'a b c': 2, 'a a b c': 1)

这段代码应该比你之前尝试过的要快得多。

另一种选择

如果要将原始句子保留在列表中,可以使用setdefault

sentences = ["Today is a good day", 'a b c', 'a a b c', 'c b a', "Is today a good day"]

def sorted_words(sentence):
    return ' '.join(sorted(sentence.lower().split(" ")))

vocab = 
for sentence in sentences:
    vocab.setdefault(sorted_words(sentence), []).append(sentence)

vocab

#=> 'a day good is today': ['Today is a good day', 'Is today a good day'],
# 'a b c': ['a b c', 'c b a'],
# 'a a b c': ['a a b c']

【讨论】:

这实际上工作得非常快。但是您能否详细说明如何使上述代码更快。只需更改计数器并使用其他东西。用户定义或内置函数 当我创建一个以字符串为键的字典时,我失去了单词的顺序。是的,我能够计算出类似句子的数量,但我失去了原来的顺序 @TheLastCoder:这就是我写“更复杂的例子”的原因。无论如何,在“另一种选择”中有一个较短的版本。 我了解字典的工作原理。我想要的是文本中已经存在的字典键,其计数等于相似字符串的数量(相似的意思是具有相同的单词集) @TheLastCoder:例如"Today is a good day" 的密钥是什么样的?【参考方案2】:

尝试类似

set(sentence.split(" ")) == set(line.split(" "))

比较 set 对象比比较 counter 更快。 set 和 counter 对象基本上都是 set,但是当您使用 counter 对象进行比较时,它必须同时比较键和值,而 set 只需要比较键。 感谢 EricBarmar 的投入。

您的完整代码如下所示

from collections import Counter
vocab = a dictionary of around 1000 sentences as keys
for line in file_ob:
    for sentence in vocab:
        if set(sentence.split(" ")) == set(line.split(" ")):
            vocab[sentence]+=1

【讨论】:

setdictcounter 之间并没有太大区别。集合基本上是一个字典,其中的值被忽略。使用带有计数器的 O(1)O(n) 解决方案比使用带有集合的 O(n**2) 好得多。 对不起,为了简单起见,我提出了这个问题。在我的实际代码中,词汇是在 for 循环中生成的。基本上我是从一个文本文件生成 ngram 并确保没有两个 ngram 具有相同的单词集。将它们转换为 set 确实有效,但仍然很慢。我想知道是否有更快的选择 @EricDuminil 有什么可以代替套装的吗? @EricDuminil 计数器解决方案必须同时比较键和值,集合只需比较键。它们都是 O(n)。 @TheLastCoder:集合和计数器非常好。您只需要找到正确的键并按照它们应该使用的方式使用集合:而不是遍历每个键。【参考方案3】:

在您的代码中,您可以在内部循环之外提取 Counter 构造,而不是为每一对重新计算每个构造 - 这应该通过与每个字符串的平均令牌数成比例的因子来改进算法。

from collections import Counter
vocab = a dictionary of around 1000 sentences as keys

vocab_counter = k: Counter(k.split(" ")) for k in vocab.keys() 

for line in file_obj:
    line_counter = Counter(line.split(" "))
    for sentence in vocab:
        if vocab_counter[sentence] == line_counter:
            vocab[sentence]+=1

可以通过将计数器用作字典的索引来进行进一步的改进,这样您就可以将匹配句子的线性搜索替换为查找。 frozendict 包可能很有用,因此您可以将字典用作另一个字典的键。

【讨论】:

对不起,为了简单起见,我提出了这个问题。在我的实际代码中,词汇是在 for 循环中生成的。基本上我是从一个文本文件生成 ngram 并确保没有两个 ngram 具有相同的单词集。将它们转换为 set 确实有效,但仍然很慢。我想知道是否有更快的选择【参考方案4】:

要考虑重复/多个单词,您的相等比较可能是:

def hash_sentence(s):                                                                                                                                                                                                                                         
    return hash(''.join(sorted(s.split())))                                                                                                                                                                                                                   

a = 'today is a good day'                                                                                                                                                                                                                                     
b = 'is today a good day'                                                                                                                                                                                                                                     
c = 'today is a good day is a good day'                                                                                                                                                                                                                       

hash_sentence(a) == hash_sentence(b)  # True
hash_sentence(a) == hash_sentence(c)  # False

另外,请注意,在您的实现中,每个句子都被计算 n 次 (for sentence in vocab:)。

【讨论】:

为了简单起见,我提出了这个问题。在我的实际代码中,词汇是在 for 循环中生成的。基本上我是从一个文本文件生成 ngram 并确保没有两个 ngram 具有相同的单词集。将它们转换为 set 确实有效,但仍然很慢。我想知道是否有更快的选择 这可能是要走的路。然后可以通过哈希对句子进行分组,直接得到相似的句子。 这是一个优雅的解决方案。让我使用 timeit 功能并回复您!让我看看是 set 函数还是这个实现更快 这很有趣...如果您愿意,请尝试对排序序列进行元组而不是加入字符串... 实际上,根据您的输入数据,您甚至可以省略.split() 并直接对字符串进行排序——为了散列的目的。

以上是关于检查两个字符串是不是在 Python 中包含相同的单词集的主要内容,如果未能解决你的问题,请参考以下文章

javascript 如何检查两个数组是否在JavaScript中包含相同的元素?

javascript 如何检查两个数组是否在JavaScript中包含相同的元素?

能够在 ASP.NET 中包含文件,但不检查它是不是存在

在 JSP 中包含/导入文件之前,如何检查文件是不是存在?

如何防止在包含的文件中包含两次相同的文件?

在不到一秒的时间内执行一个字谜检查器算法