【问题标题】:Skip iterating on itself (List)跳过自身迭代(列表)
【发布时间】:2020-08-22 19:43:05
【问题描述】:

我正在尝试在列表中查找类似的电子邮件。为此,

database.head()

    TID PID Names
0   22330   134575  tim
1   22333   134578  tim.rand
2   22328   134571  rand.001
3   22340   134568  pankit090
4   22325   134569  timrook

emailsdb = database['Names'].values.tolist() 现在是迭代部分

list = []
for email in emailsdb :
    result = process.extractBests(email, emailsdb, score_cutoff=85, limit=100)
    list.append(result)

列表输出为

[[('tim', 100), ('tim.rand', 90), ('timrook', 90)],
 [('tim.rand', 100), ('tim', 90)],
 [('rand.001', 100)],
 [('pankit090', 100),
  ('pankit001', 89),
  ('pankit002', 89),
  ('pankit003', 89),
  ('pankit004', 89),
  ('pankit005', 89)],
 [('timrook', 100), ('tim', 90)],
 [('pankit001', 100),
  ('pankit090', 89),
  ('pankit002', 89),
  ('pankit003', 89),
  ('pankit004', 89),
  ('pankit005', 89)],
 [('pankit002', 100),
  ('pankit090', 89),
  ('pankit001', 89),
  ('pankit003', 89),
  ('pankit004', 89),
  ('pankit005', 89)],

但我想避免 ('tim', 100), ('tim.rand', 100), ('rand.001', 100), ('pankit090', 100), ('timrook', 100 ), ('pankit001', 100),('pankit002', 100) 因为这些显然是绝配

【问题讨论】:

  • 基于 wuzzy 文档,我看不到跳过 100 个匹配项的方法。您可能只需要在提取调用后将它们从列表中删除。
  • 如果我们在 for 循环中删除列表副本并删除电子邮件会怎样
  • 是的 - 这也应该有效
  • list = [] 用于 emailsdb 中的电子邮件:newlookup = emailsdb.copy() newlookup.remove(email) result = process.extractBests(email, newlookup, score_cutoff=85, limit=50) 列表。追加(电子邮件)列表。追加(结果)
  • 这里的结果格式为 - [email, [match1,score],[match2,score], email, [match3,score],[match4,score]]。如何提取 match1、match2、match3、match4 并将它们从 emailsdb 中删除

标签: python-3.x for-loop iteration fuzzywuzzy


【解决方案1】:

根据我们在聊天中的讨论,将此作为另一个答案发布,因为这以完全不同的方式解决了它。

方法-

  1. 如果您使用交换函数来获得字符串匹配,那么您实际上只能计算距离矩阵的下三角矩阵,因为 f(x,y) 将与 f(y,x) 相同。

  2. 模糊比是不可交换的,而 Levenshtein 距离(又名编辑距离,它是模糊匹配的基础)是可交换的。这里不是获得最高分,而是找到它们之间的最小 lev 距离的字符串

  3. 因此,首先您获取下三角矩阵的索引并对其进行迭代以计算具有这些索引的电子邮件的 lev 距离。您不要在距离矩阵 f(x,x) 的对角线上进行迭代,因为它微不足道,然后您将其设置为 999 之类的高值,以便您可以找到每行中的最小分值。

  4. 这给了你距离矩阵(见输出)。接下来为每一行(电子邮件)找到最近的字符串(最小距离)并将其创建为最佳匹配元组(参见输出)

  5. 现在,这里的问题是,即使没有一个字符串匹配得很好,它仍然会贪婪地获取最近的那个。

  6. 所以最后你可以获取这些字符串之间的 fuzz.ratio 并获得模糊分数,你可以对其进行过滤。所以现在,你可以避免垃圾匹配,只得到真正匹配高于某个阈值的匹配。这给出了最终匹配字符串(见输出)

这可能是从算法角度优化代码的最佳方法。

import pandas as pd
import numpy as np

#!pip install python-Levenshtein
from Levenshtein import distance
from fuzzywuzzy import fuzz, process

#CHECKING COMMUTATIVE PROPERTY
distance('abcd','bdca') == distance('bdca', 'abcd')
###OUTPUT - TRUE

fuzz.ratio('abcd','bdca') == fuzz.ratio('bdca', 'abcd')
###OUTPUT - FALSE


#Create dummy matrix
m = np.zeros((len(emailsdb),len(emailsdb)))

#Get lower triangular matrix indices
i,j = np.tril_indices(len(emailsdb))

#iterate over lower triangular and get Levenshtein distance for lower triangular
for k in zip(i,j):
    if k[0]!=k[1]:
        m[k] = distance(emailsdb[k[0]], emailsdb[k[1]])
    else:
        m[k] = 0

#Copy lower triangular to upper traingular thanks to COMMUTATIVE PROPERTY
m = m + m.T

#Filling diagonal with 999 so that same string doesnt show up with minimum distance
np.fill_diagonal(m, 999)
print("distance matrix = ")
print(m)

#Get best matches with minimum distance
matches = list(zip([i for i in emailsdb], [emailsdb[i] for i in np.argmin(m, axis=0)]))

print("Best matches = ")
print(matches)


#OPTIONAL -> Filtering out irrelavant matches based on fuzzy match
f = [(*i,fuzz.ratio(*i)) for i in matches if fuzz.ratio(*i)>50]

print("final matching strings - ")
print(f)
distance matrix = 
[[999.   5.   8.   8.   4.]
 [  5. 999.   8.   9.   4.]
 [  8.   8. 999.   6.   8.]
 [  8.   9.   6. 999.   9.]
 [  4.   4.   8.   9. 999.]]


Best matches = 
[('tim', 'timrook'), ('tim.rand', 'timrook'), ('rand.001', 'pankit090'), ('pankit090', 'rand.001'), ('timrook', 'tim')]


final matching strings - 
[('tim', 'timrook', 60), ('tim.rand', 'timrook', 53), ('timrook', 'tim', 60)]

【讨论】:

    【解决方案2】:

    这是处理模糊查找然后删除原始电子邮件列表结果的代码。由于这是一个简短列表,因此删除匹配列表与仅清除原始列表具有相同的效果。请注意,删除过程可以包含在第一个电子邮件循环中,但为了清楚起见,我将它们分开。我添加了 zzzz 条目来检查得分。

    from fuzzywuzzy import fuzz
    from fuzzywuzzy import process
    
    # original email list
    emailsdb = [
    'tim',
    'tim.rand',
    'rand.001',
    'pankit090',
    'timrook',
    'pankit001',
    'pankit002',
    'pankit003',
    'pankit004',
    'pankit005',
    'zzzz'
    ]
    
    print('emailsdb:', emailsdb)
    
    # do fuzzy search and score other emails (including self)
    lstres = []
    for email in emailsdb :
        result = process.extractBests(email, emailsdb, score_cutoff=85, limit=50)
        lstres.append(result)
        
    print('WuzzyResult:', lstres)
    
    # scan match list and create set 
    setremove = set()  # no duplicates
    for r in lstres:
       setremove |= ({t[0] for t in r})
       
    print('setremove:', setremove)
    
    # remove matches from original set
    for e in setremove:
       emailsdb.remove(e)
       
    print('emailsdb:', emailsdb)
    

    输出:

    emailsdb: ['tim', 'tim.rand', 'rand.001', 'pankit090', 'timrook', 'pankit001', 'pankit002', 'pankit003', 'pankit004', 'pankit005', 'zzzz']
    
    WuzzyResult: [
    [('tim', 100), ('tim.rand', 90), ('timrook', 90)], 
    [('tim.rand', 100), ('tim', 90)], [('rand.001', 100)], 
    [('pankit090', 100), ('pankit001', 89), ('pankit002', 89), ('pankit003', 89), ('pankit004', 89), ('pankit005', 89)], 
    [('timrook', 100), ('tim', 90)], 
    [('pankit001', 100), ('pankit090', 89), ('pankit002', 89), ('pankit003', 89), ('pankit004', 89), ('pankit005', 89)], 
    [('pankit002', 100), ('pankit090', 89), ('pankit001', 89), ('pankit003', 89), ('pankit004', 89), ('pankit005', 89)], 
    [('pankit003', 100), ('pankit090', 89), ('pankit001', 89), ('pankit002', 89), ('pankit004', 89), ('pankit005', 89)], 
    [('pankit004', 100), ('pankit090', 89), ('pankit001', 89), ('pankit002', 89), ('pankit003', 89), ('pankit005', 89)], 
    [('pankit005', 100), ('pankit090', 89), ('pankit001', 89), ('pankit002', 89), ('pankit003', 89), ('pankit004', 89)], 
    [('zzzz', 100)]
    ]
    
    setremove: {'pankit002', 'pankit005', 'tim', 'rand.001', 'pankit003', 'zzzz', 'pankit090', 'pankit001', 'timrook', 'pankit004', 'tim.rand'}
    
    emailsdb: []
    

    【讨论】:

    • 嗨,迈克,输出显示 ('tim', 100), ('tim.rand', 100), ('pankit090', 100) 等...这不是故意的。跨度>
    • 所以你想从 WuzzyResult 中删除 '100' 条目?
    【解决方案3】:

    您可以删除不想比较的已知项目(可能通过预定义的知识)。这可以通过弹出这些项目然后比较它们,然后将它们插入回来来完成。

    这应该可以解决您的部分问题。我只是在比较之前从列表中pop 相同的元素,然后在下一个循环之前将insert 它返回。检查打印输出以获取更多详细信息 -

    import pandas as pd
    from fuzzywuzzy import fuzz, process
    
    emailsdb = list(df['Names'])
    
    l = []
    for i in range(len(emailsdb)) : 
        email = emailsdb[i] #Popping based on index, rather than comparing email in emailsdb
        emailsdb.pop(i) #Pop the same email string in the list of emails
        print("comparing:",[email],'->',emailsdb)
        result = process.extractBests(email, emailsdb, score_cutoff=85, limit=100)
        l.append(result)
        emailsdb.insert(i, email) #Insert it back
    
    print(l)
    
    comparing: ['tim'] -> ['tim.rand', 'rand.001', 'pankit090', 'timrook']
    comparing: ['tim.rand'] -> ['tim', 'rand.001', 'pankit090', 'timrook']
    comparing: ['rand.001'] -> ['tim', 'tim.rand', 'pankit090', 'timrook']
    comparing: ['pankit090'] -> ['tim', 'tim.rand', 'rand.001', 'timrook']
    comparing: ['timrook'] -> ['tim', 'tim.rand', 'rand.001', 'pankit090']
    
    [[('tim.rand', 90), ('timrook', 90)], [('tim', 90)], [], [], [('tim', 90)]]
    

    如您所见,冗余比较不是输出的一部分。

    请注意,我不会将电子邮件列表中的电子邮件与整个电子邮件列表进行比较以将其删除。我只是从电子邮件列表中删除第 0 个、第 1 个、第 2 个……索引电子邮件,利用迭代的顺序性来删除电子邮件。这是一个显着的加速。

    您可以修改此方法以处理您已经知道相似且不需要比较的项目集。

    P.S:拜托拜托,不要使用list作为变量名:)

    【讨论】:

    • 谢谢。您提到弹出要与自身进行比较的元素是一个显着的加速。你这么认为?我们可以改为删除已经出现在类似列表中的电子邮件吗?在第 1 次运行中,我们得到了 ('tim.rand', 'timrook')。我们可以删除 ('tim.rand', 'timrook') 进行迭代吗?
    • 你可以,但它在很大程度上取决于每个字符串平均有多少“相似”电子邮件。另外,第一次运行将为更大的数据集制造时间炸弹。另外,如果您已经计算了所有字符串的相似性,为什么还要进行第二次运行,只需过滤掉 ==100 的分数。这意味着 n^2 复杂度的 2 次迭代。
    • 在这里,即使您平均比较更多的东西,也只有一次迭代具有 n^2-1 的复杂度。
    • 注意,模糊匹配非常繁重(Levenstein 距离是一个相当繁重的算法),您希望尽可能少地调用它,因为这是机器中最慢的齿轮。跨度>
    • 分数为 == 100 的那些现在不存在了,因为它们已经被删除了。我想删除 ('tim.rand', 'timrook') 这样它们就不必再次与所有内容进行比较。这将导致加速。
    猜你喜欢
    • 2018-05-25
    • 1970-01-01
    • 2011-08-06
    • 2015-03-24
    • 1970-01-01
    • 2015-06-20
    • 2011-06-28
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多