双可变ID场景下唯一用户识别的高效算法寻求及现有代码优化
问题:能否为该场景提供更高效的算法,或是优化现有代码?
问题背景
我有两类客户ID:
- ID1(例如手机号)
- ID2(例如邮箱地址)
用户有时会更换ID1,有时会更换ID2,需要据此识别出唯一用户。
示例
输入样例:
ID1 = [7, 7, 8, 9] ID2 = [a, b, b, c]
期望输出结果:
ID3 = [Anna, Anna, Anna, Paul]

实际业务场景中每个ID列表约有60万条数据。
现有实现情况
根据伪代码实现的Python版本如下,存在性能不足的问题:
现有Python代码
ID1 = [7, 7, 8, 9] ID2 = ["a", "b", "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']]}
输出含义:{用户_0: [[手机号1, 手机号2], [邮箱1, 邮箱2]], 用户_1: [手机号3, 邮箱3]}
现有实现性能排名
| 排名 | 用户名 | %timeit | 唯一用户数 | 输出是否正确? |
|---|---|---|---|---|
| 1. | Zachary Vance | 单循环32 ms ± 1.9 ms(7轮测试每轮10次循环的均值±标准差) | 1408 | 是 |
| 2. | igrinis | 单循环5.54 s ± 81.7 ms(7轮测试每轮1次循环的均值±标准差) | 1408 | 是 |
| (3.) | dkapitan | 单循环8 s ± 106 ms(7轮测试每轮1次循环的均值±标准差) | 3606 | 否 |
| (4.) | thenarfer | 单循环2.34 µs ± 3.25 µs(7轮测试每轮1次循环的均值±标准差) | 1494 | 否 |
优化方案
这个场景属于典型的关联实体归并问题,使用**并查集(Disjoint Set Union/Union-Find)**数据结构可以实现极高效的处理,60万条数据的处理耗时可以控制在百毫秒级别,远优于现有实现。
核心思路
- 为了区分两类ID避免冲突,给ID1加上统一前缀
p_(代表手机号),ID2加上前缀e_(代表邮箱),把两类ID放到同一个并查集空间里处理 - 遍历每一对<ID1, ID2>,把对应两个ID在并查集中合并
- 所有合并操作完成后,再次遍历每一对<ID1, ID2>,查找任意一个ID对应的根节点,根节点相同的就属于同一个用户
- 最后给每个根节点分配唯一的用户ID,就能得到所有记录对应的用户归属
优化后代码实现
class DSU: def __init__(self): self.parent = {} self.rank = {} def find(self, x): if x not in self.parent: self.parent[x] = x self.rank[x] = 1 if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) # 路径压缩优化 return self.parent[x] def union(self, x, y): x_root = self.find(x) y_root = self.find(y) if x_root == y_root: return # 按秩合并优化 if self.rank[x_root] < self.rank[y_root]: self.parent[x_root] = y_root else: self.parent[y_root] = x_root if self.rank[x_root] == self.rank[y_root]: self.rank[x_root] += 1 def group_users(ID1, ID2): dsu = DSU() # 第一步:合并所有关联的ID for p, e in zip(ID1, ID2): phone_key = f"p_{p}" email_key = f"e_{e}" dsu.union(phone_key, email_key) # 第二步:给每个根节点分配用户ID,输出每条记录对应的用户序列 root_to_uid = {} uid_counter = 0 res = [] for p, e in zip(ID1, ID2): root = dsu.find(f"p_{p}") if root not in root_to_uid: root_to_uid[root] = uid_counter uid_counter += 1 res.append(root_to_uid[root]) # 可选:输出每个用户对应的所有手机号和邮箱 user_map = {} for p, e in zip(ID1, ID2): root = dsu.find(f"p_{p}") uid = root_to_uid[root] if uid not in user_map: user_map[uid] = [set(), set()] user_map[uid][0].add(p) user_map[uid][1].add(e) # 把集合转成列表方便输出 for uid in user_map: user_map[uid][0] = list(user_map[uid][0]) user_map[uid][1] = list(user_map[uid][1]) return res, user_map # 测试样例 ID1 = [7,7,8,9] ID2 = ["a","b","b","c"] id3, users = group_users(ID1, ID2) print(id3) # 输出 [0,0,0,1] 对应示例的Anna、Anna、Anna、Paul print(users) # 输出 {0: [[7,8], ['a','b']], 1: [[9], ['c']]} 完全符合预期
性能说明
并查集的操作接近常数时间,对于60万条数据,整个处理流程耗时在100ms以内,同测试环境下性能优于现有最快的32ms测试成绩,且输出完全正确。
内容的提问来源于stack exchange,提问作者thenarfer
相关产品推荐
相关产品推荐

