我有一个字符串列表,我只想保留最独特的字符串。这是我的实现方式(循环可能有问题),
def filter_descriptions(descriptions):
MAX_SIMILAR_ALLOWED = 0.6 #40% unique and 60% similar
i = 0
while i < len(descriptions):
print("Processing {}/{}...".format(i + 1, len(descriptions)))
desc_to_evaluate = descriptions[i]
j = i + 1
while j < len(descriptions):
similarity_ratio = SequenceMatcher(None, desc_to_evaluate, descriptions[j]).ratio()
if similarity_ratio > MAX_SIMILAR_ALLOWED:
del descriptions[j]
j += 1
i += 1
return descriptions
请注意,列表中可能包含大约 11万个项,这就是为什么我每次迭代都会缩短列表。
任何人都可以找出当前实施的问题吗?
编辑1:
当前结果“太相似”。 filter_descriptions
函数返回了16个项目(从约110K个项目的列表中)。当我尝试以下操作时,
SequenceMatcher(None, descriptions[0], descriptions[1]).ratio()
该比率为0.99,而SequenceMatcher(None, descriptions[1], descriptions[2]).ratio()
为0.98。但是使用SequenceMatcher(None, descriptions[0], descriptions[15]).ratio()
时约为0.65(更好)
我希望这会有所帮助。
答案 0 :(得分:2)
如果您颠倒了逻辑,则可以不必原地修改列表,而仍然可以减少所需的比较次数。也就是说,从一个空的输出/唯一列表开始,遍历您的描述,看看是否可以添加每个列表。因此,对于第一个描述,您可以立即添加它,因为它不能与空白列表中的任何内容相似。与所有其他描述相反,仅需要将第二个描述与第一个描述进行比较。以后的迭代一旦找到与之相似的先前描述,就会使其短路(并丢弃候选描述)。即。
import operator
def unique(items, compare=operator.eq):
# compare is a function that returns True if its two arguments are deemed similar to
# each other and False otherwise.
unique_items = []
for item in items:
if not any(compare(item, uniq) for uniq in unique_items):
# any will stop as soon as compare(item, uniq) returns True
# you could also use `if all(not compare(item, uniq) ...` if you prefer
unique_items.append(item)
return unique_items
示例:
assert unique([2,3,4,5,1,2,3,3,2,1]) == [2, 3, 4, 5, 1]
# note that order is preserved
assert unique([1, 2, 0, 3, 4, 5], compare=(lambda x, y: abs(x - y) <= 1))) == [1, 3, 5]
# using a custom comparison function we can exclude items that are too similar to previous
# items. Here 2 and 0 are excluded because they are too close to 1 which was accepted
# as unique first. Change the order of 3 and 4, and then 5 would also be excluded.
使用您的代码,您的比较功能将如下所示:
MAX_SIMILAR_ALLOWED = 0.6 #40% unique and 60% similar
def description_cmp(candidate_desc, unique_desc):
# use unique_desc as first arg as this keeps the argument order the same as with your filter
# function where the first description is the one that is retained if the two descriptions
# are deemed to be too similar
similarity_ratio = SequenceMatcher(None, unique_desc, candidate_desc).ratio()
return similarity_ratio > MAX_SIMILAR_ALLOWED
def filter_descriptions(descriptions):
# This would be the new definition of your filter_descriptions function
return unique(descriptions, compare=descriptions_cmp)
比较次数应该完全相同。也就是说,在您的实现中,第一个元素与所有其他元素进行比较,第二个元素仅与被认为与第一个元素不相似的元素进行比较,依此类推。在此实现中,最初不会将第一个项目与任何项目进行比较,但是必须将所有其他项目与它进行比较才能允许将其添加到唯一列表中。只有认为与第一项不相似的项才会与第二个唯一项进行比较,依此类推。
unique
实现将减少复制,因为仅在后备阵列空间不足时才需要复制唯一列表。而使用del
语句时,每次使用时都必须复制列表的一部分(将所有后续项移动到新的正确位置)。但是,这可能对性能的影响可以忽略不计,因为瓶颈可能是序列匹配器中的比率计算。
答案 1 :(得分:1)
逻辑问题是,每次从数组中删除项目时,索引都会重新排列,并在两者之间跳过一个字符串。例如:
假设这是数组: 说明:[“ A”,“ A”,“ A”,“ B”,“ C”]
项目1:
i=0 -------------0
description[i]="A"
j=i+1 -------------1
description[j]="A"
similarity_ratio>0.6
del description[j]
现在,数组重新索引如下: 描述:[“ A”,“ A”,“ B”,“ C”]。下一步是:
j=j+1 ------------1+1= 2
说明[2] =“ B”
您已跳过Description [1] =“ A”
要解决此问题: 替换
j+=1
使用
j=i+1
(如果已删除)。否则就进行普通的j = j + 1迭代
答案 2 :(得分:0)
从列表中删除一项时,j
的值不应更改(因为在下一次迭代中该位置将出现其他列表项)。每次删除项目时,j=i+1
都会重新开始迭代(这不是所希望的)。现在,更新的代码仅在else条件下递增j
。
def filter_descriptions(descriptions):
MAX_SIMILAR_ALLOWED = 0.6 #40% unique and 60% similar
i = 0
while i < len(descriptions):
print("Processing {}/{}...".format(i + 1, len(descriptions)))
desc_to_evaluate = descriptions[i]
j = i + 1
while j < len(descriptions):
similarity_ratio = SequenceMatcher(None, desc_to_evaluate, descriptions[j]).ratio()
if similarity_ratio > MAX_SIMILAR_ALLOWED:
del descriptions[j]
else:
j += 1
i += 1
return descriptions