咨询:同一联系人关联电话与邮箱合并的算法实现方案
解决思路与实现代码
这个问题的核心是识别通过电话/邮箱关联的连通记录组——只要两条记录共享电话或邮箱(哪怕是间接共享),就属于同一组,需要合并。这类问题用图论中的「连通分量」算法最适合,具体步骤如下:
步骤说明
- 按姓名分组:不同姓名的记录肯定属于不同联系人,先拆分处理。
- 构建关联图:对每个姓名组,把所有电话、邮箱都作为图的节点,每条原始记录对应一条「电话-邮箱」的边(表示两者关联)。
- 提取连通分量:找出图中所有互相连通的节点集合,每个集合就是一个需要合并的记录组。
- 映射并合并:把每条原始记录匹配到对应的连通分量,再按姓名+连通分量分组,拼接去重后的电话和邮箱。
具体代码实现
library(tidyverse) library(igraph) # 原始数据 contact <- tribble( ~name, ~phone, ~email, 'John', 123, 'john_abc@gmail.com', 'John', 456, 'john_abc@gmail.com', 'John', 456, 'john_xyz@gmail.com', 'John', 789, 'john_pqr@gmail.com' ) contact_combined <- contact %>% # 按姓名分组处理 group_by(name) %>% group_modify(function(data, .) { # 1. 构建边列表:每条记录的电话和邮箱作为一条边 edges <- data %>% mutate(phone = as.character(phone)) %>% select(phone, email) %>% rename(from = phone, to = email) # 2. 创建图并提取连通分量 graph <- graph_from_data_frame(edges, directed = FALSE) components <- components(graph)$membership # 3. 把每个节点(电话/邮箱)映射到对应的分量ID node_component <- enframe(components, name = "node", value = "component_id") # 4. 给每条原始记录匹配分量ID:通过电话或邮箱匹配 data_with_component <- data %>% mutate(phone = as.character(phone)) %>% left_join(node_component, by = c("phone" = "node")) %>% left_join(node_component, by = c("email" = "node"), suffix = c("", "_email")) %>% # 取电话或邮箱对应的分量ID(两者必然属于同一分量) mutate(component_id = coalesce(component_id, component_id_email)) %>% select(-component_id_email) # 5. 按分量ID分组,拼接去重后的电话和邮箱 data_with_component %>% group_by(component_id) %>% summarise( phone = str_c(unique(phone), collapse = ";"), email = str_c(unique(email), collapse = ";"), .groups = "drop" ) }) %>% ungroup() %>% select(name, phone, email) # 查看结果 contact_combined
运行后会得到期望的输出:
# A tibble: 2 × 3 name phone email <chr> <chr> <chr> 1 John 123;456 john_abc@gmail.com;john_xyz@gmail.com 2 John 789 john_pqr@gmail.com
关键逻辑解释
- 图的构建:把电话和邮箱视为节点,每条记录的关联关系就是边,这样所有间接关联的节点(比如123→abc邮箱→456→xyz邮箱)会被归为同一个连通分量。
- 连通分量匹配:每条记录通过电话或邮箱找到所属的分量ID,确保所有关联记录都被分到同一组。
- 去重拼接:分组后对电话和邮箱去重再拼接,避免重复值。
内容的提问来源于stack exchange,提问作者msunij
相关产品推荐
相关产品推荐

