检查两个字符串是不是在 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
(和set
,Counter
,...)的全部意义在于能够直接获得所需的值:
my_dict['c']
#=> 3
如果您的 dict 有 1000 个值,则第一个示例平均比第二个示例慢 500 倍。这是我在Reddit 上找到的一个简单描述:
字典就像一个神奇的外套检查室。你把外套递过去 得到一张票。每当你把那张票还给你时,你会立即得到 你的大衣。你可以有很多外套,但你仍然可以得到你的外套 马上回来。大衣里面有很多魔法 检查房间,但你并不在乎,只要你得到你的外套 马上回来。
重构代码
你只需要在"Today is a good day!"
和"Is today a good day?"
之间找到一个共同的签名。一种方法是提取单词,将它们转换为小写,对它们进行排序并连接它们。重要的是输出应该是不可变的(例如tuple
、string
、frozenset
)。这样,它可以直接在集合、计数器或字典中使用,而无需遍历每个键。
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 只需要比较键。 感谢 Eric 和 Barmar 的投入。
您的完整代码如下所示
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
【讨论】:
set
、dict
和 counter
之间并没有太大区别。集合基本上是一个字典,其中的值被忽略。使用带有计数器的 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中包含相同的元素?