并查集解决重复员工问题
简介
工作一年多了,天天CRUD,终于以前学习的算法排上用场了。
背景
我们的系统在用户注册时没有校验身份id(身份证)和电话号码的唯一,可能使用相同的身份id或者电话号码创建多个账号,导致有些人开多个账号领多份钱。现在公司想降本增效,让我统计出各个市场存在相同身份id或者电话号码的账号信息。
输入
两张表已过滤本次统计不涉及的字段
ops_tab
(operator 是站点员工表)
staff_tab
(员工表,里面包含的信息更详细。staff包括operator和driver,因此ops_tab的数据是staff_tab的子集)
ops_tab没有PassportId,因此需要拿staff Id去staff_tab获取对应passport id。
另外各个市场判断账号是否来自于同一个人是有差别的,有些市场允许多个账号使用同一个电话号码。
市场 | 判断条件 |
---|---|
印度尼西亚ID | 1、PassportId 2、Phone Number |
马来西亚MY | 1、PassportId 2、Phone Number |
菲律宾PH | 1、PassportId 2、Phone Number |
新加坡SG | 1、PassportId 2、Phone Number |
泰国TH | 1、Phone Number |
台湾TW | 1、PassportId |
越南VN | 1、PassportId 2、Phone Number |
比如印度尼西亚就不允许Passport Id和电话号码重复,而泰国允许Passport Id重复而电话号码不能重复。
输出
输出excel,相同信息在同一个组,只统计存在重复信息的账号,不重复的不需要展示。
详细设计
流程图
golang实现
算法主体
func Run(opsList []*OpsTab, staffList []*StaffTab) {cid := strings.ToUpper(os.Getenv("CID"))fmt.Println(cid)if len(cid) == 0 {return}NewExcelListWithOpsAndStaff(opsList, staffList).Group(cid).Export(cid)
}type OpsTab struct {Id uint64 `gorm:"column:id" json:"id"`OpsId string `gorm:"column:ops_id" json:"ops_id"`OpsName string `gorm:"column:ops_name" json:"ops_name"`Phone string `gorm:"column:phone" json:"phone"`OpsStatus int `gorm:"column:ops_status" json:"ops_status"`StaffId string `gorm:"column:staff_id" json:"staff_id"`
}type StaffTab struct {StaffId string `gorm:"column:staff_id" json:"staff_id"`PassportId string `gorm:"column:passport_id" json:"passport_id"`
}// 根据staff_id将Ops_tab和staff_tab合并成一个结构体
type ExcelResultItem struct {OpsName stringOpsId stringStaffId stringPhone stringPassportId stringOpsStatus int
}type ExcelList []*ExcelResultItemfunc NewExcelListWithOpsAndStaff(opsList []*OpsTab, staffList []*StaffTab) *ExcelList {return (&ExcelList{}).toExcelResultItemList(opsList, staffList)
}func NewExcelList() *ExcelList {return &ExcelList{}
}func (e *ExcelList) Append(item *ExcelResultItem) {*e = append(*e, item)
}func (e *ExcelList) Size() int {return len(*e)
}func (e *ExcelList) toExcelResultItemList(opsList []*OpsTab, staffList []*StaffTab) *ExcelList {staffId2StaffMap := make(map[string]*StaffTab)for _, item := range staffList {staffId2StaffMap[item.StaffId] = item}for _, item := range opsList {passportId := ""if staff, ok := staffId2StaffMap[item.StaffId]; ok {passportId = staff.PassportId}*e = append(*e, &ExcelResultItem{OpsName: item.OpsName,OpsId: item.OpsId,StaffId: item.StaffId,Phone: item.Phone,PassportId: passportId,OpsStatus: item.OpsStatus,})}return e
}func (e *ExcelList) Group(cid string) *GroupedExcelList {excelItemList := *euf := NewPathCompressionUnionFind(len(excelItemList))for i := range excelItemList {for j := i + 1; j < len(excelItemList); j++ {if handler.Check(cid, excelItemList[i], excelItemList[j]) {uf.Union(i, j)}}}ufId2ExcelListMap := make(map[int]*ExcelList)for id, item := range excelItemList {root := uf.Find(id)if uf.GetSize(root) < 2 {continue}if _, ok := ufId2ExcelListMap[root]; !ok {ufId2ExcelListMap[root] = NewExcelList()}ufId2ExcelListMap[root].Append(item)}res := NewGroupedExcelList()for _, list := range ufId2ExcelListMap {res.Append(list)}return res
}type GroupedExcelList []*ExcelListfunc NewGroupedExcelList() *GroupedExcelList {return &GroupedExcelList{}
}func (g *GroupedExcelList) Append(list *ExcelList) {*g = append(*g, list)
}
校验是否重复(策略模式)
类似这个,实际简单一点
var handler = NewCheckWhetherSameStrategyHandler()type CheckWhetherSameStrategyHandler struct {region2StrategyMap map[string]CheckWhetherSameStrategy
}func NewCheckWhetherSameStrategyHandler() *CheckWhetherSameStrategyHandler {return &CheckWhetherSameStrategyHandler{region2StrategyMap: make(map[string]CheckWhetherSameStrategy),}
}func (c *CheckWhetherSameStrategyHandler) Register(cid string, strategy CheckWhetherSameStrategy) {c.region2StrategyMap[cid] = strategy
}func (c *CheckWhetherSameStrategyHandler) Check(cid string, ops1, ops2 *ExcelResultItem) bool {if _, ok := c.region2StrategyMap[cid]; !ok {panic(fmt.Sprintf("cid[%v] not exist", cid))}return c.region2StrategyMap[cid].Check(ops1, ops2)
}type CheckWhetherSameStrategy interface {Check(ops1, ops2 *ExcelResultItem) bool
}type CheckWhetherSameStrategyID struct{}func (c *CheckWhetherSameStrategyID) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId || len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}type CheckWhetherSameStrategyMY struct{}func (c *CheckWhetherSameStrategyMY) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId || len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}type CheckWhetherSameStrategyPH struct{}func (c *CheckWhetherSameStrategyPH) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId || len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}type CheckWhetherSameStrategySG struct{}func (c *CheckWhetherSameStrategySG) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId || len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}type CheckWhetherSameStrategyTH struct{}func (c *CheckWhetherSameStrategyTH) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}type CheckWhetherSameStrategyTW struct{}func (c *CheckWhetherSameStrategyTW) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId
}type CheckWhetherSameStrategyVN struct{}func (c *CheckWhetherSameStrategyVN) Check(ops1, ops2 *ExcelResultItem) bool {return len(ops1.PassportId) != 0 && ops1.PassportId == ops2.PassportId || len(ops1.Phone) != 0 && ops1.Phone == ops2.Phone
}func init() {handler.Register("ID", &CheckWhetherSameStrategyID{})handler.Register("MY", &CheckWhetherSameStrategyMY{})handler.Register("PH", &CheckWhetherSameStrategyPH{})handler.Register("SG", &CheckWhetherSameStrategySG{})handler.Register("TH", &CheckWhetherSameStrategyTH{})handler.Register("TW", &CheckWhetherSameStrategyTW{})handler.Register("VN", &CheckWhetherSameStrategyVN{})
}
golang Excel导出操作
func (g *GroupedExcelList) Export(cid string) {//创建excel文件xlsx := excelize.NewFile()sheet := fmt.Sprintf("same_field_ops_%v_excel", cid)//创建新表单index := xlsx.NewSheet(sheet)//第一行 各个字段的名称row := 1setFieldValue2Excel(xlsx, sheet, row, FieldNameGroup, FieldNameGroup)setFieldValue2Excel(xlsx, sheet, row, FieldNameOpsName, FieldNameOpsName)setFieldValue2Excel(xlsx, sheet, row, FieldNameOpsId, FieldNameOpsId)setFieldValue2Excel(xlsx, sheet, row, FieldNamePhoneNumber, FieldNamePhoneNumber)setFieldValue2Excel(xlsx, sheet, row, FieldNamePassportId, FieldNamePassportId)setFieldValue2Excel(xlsx, sheet, row, FieldNameStatus, FieldNameStatus)row++for groupId, listPtr := range *g {//Excel 一组list := *listPtrstartRow := rowrow++for _, item := range list {//Excel 一行setFieldValue2Excel(xlsx, sheet, row, FieldNameGroup, fmt.Sprintf("%v", groupId))setFieldValue2Excel(xlsx, sheet, row, FieldNameOpsName, item.OpsName)setFieldValue2Excel(xlsx, sheet, row, FieldNameOpsId, item.OpsId)setFieldValue2Excel(xlsx, sheet, row, FieldNamePhoneNumber, item.Phone)setFieldValue2Excel(xlsx, sheet, row, FieldNamePassportId, item.PassportId)setFieldValue2Excel(xlsx, sheet, row, FieldNameStatus, fmt.Sprintf("%v", item.OpsStatus))row++}//合并A列成一组xlsx.MergeCell(sheet, fmt.Sprintf("A%v", startRow), fmt.Sprintf("A%v", row - 1))}//设置默认打开的表单xlsx.SetActiveSheet(index)//保存文件到指定路径err := xlsx.SaveAs(fmt.Sprintf("./%v.xlsx", sheet))if err != nil {log.Fatal(err)}
}type FieldName = stringconst (FieldNameGroup FieldName = "Group"FieldNameOpsName FieldName = "Ops Name"FieldNameOpsId FieldName = "Ops Id"FieldNamePhoneNumber FieldName = "Phone Number"FieldNamePassportId FieldName = "Passport Id"FieldNameStatus FieldName = "Status"
)
//如图,表格上角的ABCDEF
var posMap = map[FieldName]string{FieldNameGroup: "A",FieldNameOpsName: "B",FieldNameOpsId: "C",FieldNamePhoneNumber: "D",FieldNamePassportId: "E",FieldNameStatus: "F",
}func setFieldValue2Excel(xlsx *excelize.File, sheet string, row int, key FieldName, val string) {if _, err := strconv.ParseInt(val, 10, 64); len(val) > 1 && err == nil {val = "'" + val}xlsx.SetCellValue(sheet, fmt.Sprintf("%v%v", posMap[key], row), val)
}
路径压缩并查集
type PathCompressionUnionFind struct {parents []intsize []int
}func NewPathCompressionUnionFind(len int) *PathCompressionUnionFind {parents := make([]int, len)size := make([]int, len)for i := range parents {parents[i] = isize[i] = 1}return &PathCompressionUnionFind{parents: parents,size: size,}
}func (t *PathCompressionUnionFind) Union(p, q int) {pRoot := t.Find(p)qRoot := t.Find(q)if pRoot == qRoot {return}t.parents[qRoot] = t.parents[pRoot]t.size[pRoot] += t.size[qRoot]
}func (t *PathCompressionUnionFind) Find(p int) int {for t.parents[p] != p {t.parents[p] = t.parents[t.parents[p]]p = t.parents[p]}return p
}func (t *PathCompressionUnionFind) GetSize(p int) int {if p < 0 || p >= len(t.parents) {panic("Union Find Array out of bounds")}return t.size[t.Find(p)]
}
复杂度
时间复杂度
O(N^2)
空间复杂度
O(N)
其他
从k8s pod上连接数据库并下载表数据
我们线上数据数据量约200万,从公司提供的平台查会被限制单词1000条数据,因此下载ops_tab和staff_tab的数据需要写个脚本在k8s上执行。
本地(MAC)
安装交叉编译环境
brew install FiloSottile/musl-cross/musl-cross
切换到main函数所在包,执行编译
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS="-static" go build
压缩并拆分为多个文件方便上传
tar cjf - $your_name$ |split -b 5m - $your_name$.tar.bz2.
在k8s pod上
安装上传插件
apt install -y lrzsz
安装解压插件
apt install -y bzip2
上传文件
rz
给文件赋执行权限
chmod +x $your_name$
运行
./$your_name$
并查集解决重复员工问题相关推荐
- 数据结构之并查集:并查集解决案例, Python——21
并查集解决案例畅通工程 案例问题介绍: 某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇.省政府"畅通工程"的目标是使全省任何两个城镇间都可以实现交通 ...
- 堆的应用--并查集解决“擒贼先擒王”问题(JAVA)
现在有10个强盗. 1号强盗与2号强盗是同伙. 3号强盗与4号强盗是同伙. 5号强盗与2号强盗是同伙. 4号强盗与6号强盗是同伙. 2号强盗与6号强盗是同伙. 8号强盗与7号强盗是同伙. 9号强盗与7 ...
- 算到怀疑人生!如何用并查集解决朋友圈个数问题?
作者 | channingbreeze 责编 | 郭芮 小史是一个应届生,虽然学的是电子专业,但是自己业余时间看了很多互联网与编程方面的书,一心想进BAT互联网公司. 今天小史去了一家社交小巨头公司 ...
- 并查集解决朋友圈问题
引入 最近在网上看到了这样一道面试题: 假如已知有n个人和m对好友关系(存于数组r)如果两个人是直接或间接的好友(好友的好友的好友-),则认为他们属于同一个朋友圈,请写程序求出这n个人里一共有多少个朋 ...
- 关于 并查集(union find) 算法基本原理 以及 其 在分布式图场景的应用
二月的最后一篇水文-想写一些有意思的东西. 文章目录 环检测在图数据结构中的应用 深度/广度优先 检测环 并查集数据结构 (Union-Find) 基本概念 初始化 合并 union 查找祖先 优化1 ...
- 【高级数据结构】并查集
目录 A.AcWing 1250. 格子游戏 B.AcWing 1252. 搭配购买 C.AcWing 237. 程序自动分析 D.AcWing 239. 奇偶游戏 E.AcWing 238. 银河英 ...
- SDUTOJ [2801] 并查集模板
英语四六级系列(一) Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描述 众所周知,英语四六级考试有几套不同的试卷,而且,为了防 ...
- 并查集路径压缩_并查集(UnionFind)技巧总结
什么是并查集 在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题.有一个联合-查找算法(Union-find Algorithm)定义了两个用 ...
- How Many Answers Are Wrong HDU - 3038(带权并查集)
TT and FF are - friends. Uh- very very good friends -________-b FF is a bad boy, he is always wooing ...
最新文章
- Android自动化测试框架
- 服务器邮箱备份文件在哪里,如何轻松将数据文件备份到电子邮箱?
- 声音匹配_如何调节人声音色方法如下
- RabbitMQ消息应答------ack机制
- Simulink仿真 离散系统仿真
- 利用redis保存验证码并设置过期时间
- Halcon 基本算子释义
- DBI接口和DPI接口的区别
- mysql 锁表 for update,MySQL中select * for update锁表的问题(转)
- mysql generator 命令_Mybatis使用命令生成逆向工程的方法
- Java程序性能优化- 让你的Java程序更快、更稳定pdf
- Vista home版连Win7旗舰版的远程桌面,提示“您的凭据不工作”的解决方法
- 点金软件测试自学,徐文明短线点金相关公式
- Android 一篇文章轻松搞懂什么是Callback回调
- 内部收益率计算公式用计算机,内部收益率计算器
- OneNote for win10 无法加载笔记本
- 企业网站开发需要注意什么事项?
- Docker三剑客详解
- linux自动备份系统快照,我的 Linux 时光机——Snapper系统级自动备份工具
- 2021年度上海公务员考试公告(11月05日开启)
热门文章
- WebView 拦截广告 简单实现
- 网易滑块识别-通用滑块识别
- java createfile,Java Filer.createSourceFile方法代碼示例
- Java 12位uuid_java如何生成12位永远不重复的数字
- 与世界对话丨预康可瘦品牌发布暨全国招商会隆重举行
- 树莓派3b+,4b新手入门到手开箱第一次使用之十大步骤
- 01 什么是数据结构
- fs/binfmt_aout.c:270: error: 'SEGMENT_SIZE' undeclared
- 系统分析师-资料总结-中
- 键盘布局及各按键功能介绍(超超超详细的哟~)