问题描述
你能想出一个更快的算法来解决这个问题吗?或者改进代码?
问题:
我有两个客户 ID:
- ID1(例如电话号码)
- ID2(例如电子邮件地址)
用户有时会更改其 ID1,有时会更改 ID2。怎么能 我找到了唯一身份用户?
示例:
ID1 = [7,7,8,9]
ID2 = [a,b,c]
预期结果:
ID3 = [Anna,Anna,Paul]
现实世界的场景大约有。每个列表 600 000 个项目。
这里已经有了一个 SQL 的想法:How can I match Employee IDs from 2 columns and group them into an array?
我从一个对 TypeScript 有这个想法的朋友那里得到了帮助:https://stackblitz.com/edit/typescript-leet-rewkmh?file=index.ts
我的第二个朋友帮我写了一些伪代码,我能够创建这个:
迄今为止最快(不再工作)的代码:
ID1 = [7,9]
ID2 = ["a","b","c"]
def timeit_function(ID1,ID2):
def find_user_addresses():
phone_i = []
email_i = []
tmp1 = [ID1[0]]
tmp2 = []
tmp_index = []
while len(tmp1) != 0 or len(tmp2) != 0:
while len(tmp1) != 0:
tmp_index = []
for index,value in enumerate(ID1):
if value == tmp1[0]:
tmp2.append(ID2[index])
tmp_index.insert(-1,index)
for i in tmp_index:
del ID1[i]
del ID2[i]
tmp1 = list(dict.fromkeys(tmp1))
phone_i.append(tmp1.pop(0))
while len(tmp2) != 0:
tmp_index = []
for index,value in enumerate(ID2):
if value == tmp2[0]:
tmp1.append(ID1[index])
tmp_index.insert(0,index)
for i in tmp_index:
del ID1[i]
del ID2[i]
tmp2 = list(dict.fromkeys(tmp2))
email_i.append(tmp2.pop(0))
return phone_i,email_i
users = {}
i = 0
while len(ID1) != 0:
phone_i,email_i = find_user_addresses()
users[i] = [phone_i,email_i]
i += 1
return users
输出:
{0: [[7,8],['a','b']],1: [[9],['c']]}
含义:{User_0: [[phone1,phone2],[email1,email2]],User_1: [phone3,email3]}
排名
排名 | 用户名 | %timeit | 唯一身份用户 | 正确的输出? |
---|---|---|---|---|
1. | 扎卡里·万斯 | 每个循环 32 ms ± 1.9 ms(平均值 ± 标准偏差,7 次运行,每次 10 次循环) | 1408 | 是的 |
2. | 伊格里尼斯 | 每个循环 5.54 秒 ± 81.7 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个) | 1408 | 是的 |
(3.) | dkapitan | 每个循环 8 秒 ± 106 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个) | 3606 | 没有 |
(4.) | thenarfer | 每个循环 2.34 µs ± 3.25 µs(平均值 ± 标准偏差,7 次运行,每个循环 1 个) | 1494 | 没有 |
代码使用两个列表 here 运行(加载需要一些时间)。
解决方法
这个想法很简单:
- 对于每个条目
-
扫描所有现有用户,
- 如果条目的任何维度与之前的用户匹配,则扩展其属性集,并将其添加到合并列表中
-
如果与现有的任何人都不匹配,则创建一个新用户,
-
否则将不同的用户分组
-
这应该很快,因为它只扫描列表一次并且不需要递归。
ID1 = [7,7,8,9]
ID2 = ["a","b","c"]
user = {}
new_user_idx = 0
for i in range(len(ID1)):
merge = [] # this is a list of users that should be merged
for k in user:
# find if any feature is already found in previous user
if ID1[i] in user[k][0]:
user[k][1].add(ID2[i])
merge.append(k)
if ID2[i] in user[k][1]:
user[k][0].add(ID1[i])
merge.append(k)
if not merge:
# we have to create a new user
user[new_user_idx] = (set([ID1[i]]),set([ID2[i]]))
new_user_idx += 1
elif len(merge)>1:
# merging existing users
for el in set(merge[1:]):
if el==merge[0]: continue # skip unnecessary merge
user[merge[0]][0].update(user[el][0]) # copy attributes
user[merge[0]][1].update(user[el][1])
user.pop(el) # delete merged user
print(user)
{0: ({7,8},{'a','b'}),1: ({9},{'c'})}
,
用套装试试:
%%timeit
ID1 = [7,"c"]
users = {0: {'phone': set([ID1[0]]),'email': set([ID2[0]])}}
for index,id1 in enumerate(ID1):
id2 = ID2[index]
# iterate over found list of users
i = 0
n_users = max(users.keys())
while i <= n_users:
if any([(id1 in users[i]['phone']),(id2 in users[i]['email'])]):
users[i]['phone'].add(id1)
users[i]['email'].add(id2)
break
i += 1
# add new user if not found
if i > n_users:
users[i] = {'phone': set([id1]),'email': set([id2])}
,
不要使用包含 5 个元素的列表运行 timeit。这不是评估竞争者的有效方法。使用更大的列表(1000+)来测试性能,否则你实际上不会得到你想要的快速程序。
我会毫不犹豫地使用 O(N) 最坏情况算法(不依赖于用户数量)。
我会指出其他算法的性能在很大程度上取决于典型用户拥有的 ID 数量。这个专门用于为每个用户提供大量 ID。
ID1 = [7,"c"]
from collections import defaultdict
id1_to_id2 = defaultdict(set)
id2_to_id1 = defaultdict(set)
for id1,id2 in zip(ID1,ID2):
id1_to_id2[id1].add(id2)
id2_to_id1[id2].add(id1)
id1_to_user = {}
users = {}
for id1 in id1_to_id2:
if id1 in id1_to_user:
continue # Already processed
# Find all id1 and id2 for this user,using 'floodfill'
id1s = {id1}
id2s = set()
id1_queue = [id1]
while len(id1_queue) > 0:
old_id2s = id2s.copy()
for id1 in id1_queue:
id2s.update(id1_to_id2[id1]) # try using id2_queue = set().union(id1_to_id2[id1] for id1 in id1_queue)-id2s; id2s |= id2_queue as well in case it's faster
id2_queue = id2s - old_id2s
id1_queue = []
old_id1s = id1s.copy()
for id2 in id2_queue:
id1s.update(id2_to_id1[id2])
id2_queue = []
id1_queue = id1s - old_id1s
user = len(users)
users[user] = [id1s,id2s]
for id1 in id1s:
id1_to_user[id1] = user
print(users)