并查集解决重复员工问题

news2024/12/24 9:32:19

简介

工作一年多了,天天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。

另外各个市场判断账号是否来自于同一个人是有差别的,有些市场允许多个账号使用同一个电话号码。

市场判断条件
印度尼西亚ID1、PassportId 2、Phone Number
马来西亚MY1、PassportId 2、Phone Number
菲律宾PH1、PassportId 2、Phone Number
新加坡SG1、PassportId 2、Phone Number
泰国TH1、Phone Number
台湾TW1、PassportId
越南VN1、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    string
	OpsId      string
	StaffId    string
	Phone      string
	PassportId string
	OpsStatus  int
}

type ExcelList []*ExcelResultItem

func 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 := *e
	uf := 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 []*ExcelList

func NewGroupedExcelList() *GroupedExcelList {
	return &GroupedExcelList{}
}

func (g *GroupedExcelList) Append(list *ExcelList) {
	*g = append(*g, list)
}

校验是否重复(策略模式)

检查是否重复策略模式UML
类似这个,实际简单一点

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 := 1
	setFieldValue2Excel(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 := *listPtr
		startRow := row
		row++
		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 = string

const (
	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 []int
	size    []int
}

func NewPathCompressionUnionFind(len int) *PathCompressionUnionFind {
	parents := make([]int, len)
	size := make([]int, len)
	for i := range parents {
		parents[i] = i
		size[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$

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试者推荐 |【Redis面试专题】「常见问答系列」透析Redis常见技术相关的问题1~10题(进阶)

&#x1f4da; 前提回顾 首先如果没有阅读【面试者推荐 |【Redis面试专题】「常见问答系列」透析Redis常见技术相关的问题1~10题&#xff08;基础&#xff09; 】&#xff0c;简易先去看看基础10题&#xff0c;因为循序渐进才是正道&#xff0c;哈哈。 &#x1f4da; 1. Redis…

WebRTC源码之RTCPReceiver源码分析

WebRTC源码之RTCPReceiver源码分析 WebRTC源码之RTCPReceiver源码分析WebRTC源码之RTCPReceiver源码分析前言一、 RTCP接受数据的流程的堆栈信息的1、网络io 线程读取数据2、 线程切换的代码3、 线程切换 gcc二、 RTCPReceiver::IncomingPacket方法读取RTCP数据的格式1、 Parse…

【PyTorch深度学习项目实战100例】—— 基于DPCNN实现电商情感分析任务 | 第79例

前言 大家好,我是阿光。 本专栏整理了《PyTorch深度学习项目实战100例》,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPy…

随机种子 3407 is all you need

文 | 天于刀刀你最常用的随机种子是哪个&#xff1f;在刀刀的团队里&#xff0c;关于随机种子的设置主要分化为两派~玄学派&#xff0c;可能设置为自己的纪念日&#xff0c;又或者是星座预测中的本月幸运数字&#xff1b;以及&#xff0c;自然派&#xff0c;随机种子是啥其实无…

Java项目:springboot健身房管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目为后台管理系统&#xff1b; 主要功能如下&#xff1a; 管理员登录模块 会员管理模块 教练管理模块 课程管理模块 器材管理模块 物品遗失…

一些常见的移动端适配方案,你了解吗?

前言 移动端设备的尺寸很多&#xff0c;而 UI 设计稿一般只会基于一个尺寸&#xff08;一般是 375px 或 750px &#xff09;进行设计。 目前移动端适配方案有多种&#xff0c;本文将介绍一些具有代表性的适配方案。 媒体查询 media CSS3 中的媒体查询属性 media 分别为不同…

量子计算(十七):量子计算机硬件

文章目录 量子计算机硬件 一、量子芯片支持系统 二、量子计算机控制系统 量子计算机硬件 量子计算机的核心——量子芯片&#xff0c;具有多种不同的呈现形式。绝大多数量子芯片&#xff0c;名副其实地&#xff0c;是一块芯片&#xff0c;由集成在基片表面的电路结构构建出包…

关于 Camera 开始 Tuning 时的一些注意事项

1、问题背景&#xff1a; 最近有调试一个体感游戏机上带 Camera 的项目&#xff0c;原定搭配 ov13855 这颗 sensor, 但由于各种各样的问题&#xff0c;导致做了很多无用功&#xff0c;且各种延期。 本文主要总结下此次项目遇到的问题&#xff0c;及产品开始 tuning 时的一些注意…

【折腾服务器 4】ESXi 中 Ubuntu 安装 NPS 客户端 ( NPC )

Catch Up 书接上回&#xff0c;上一章中&#xff0c;群晖已经能定期给 Windows 物理机服务器做备份了&#xff0c;但是依然无法从外网访问服务器上的内容&#xff0c;本篇讲述如何在 Ubuntu 中安装 NPS 客户端&#xff0c;也就是所谓的 NPC ( Client )。 Chapter 1 准备一个 …

C#语言实例源码系列-实现FTP下载文件

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

SpringSecurity(十)【CSRF 漏洞保护】

十、CSRF 漏洞保护 简介 CSRF&#xff08;Cross-Site Request Forgery 跨站请求伪造&#xff09;&#xff0c;也可称为一键式攻击&#xff08;one-click-attack&#xff09;通常缩写为 CSRF 或者 XSRF。CSRF 攻击是一种挟持用户在当前已登录的浏览器上&#xff0c;发送恶意请求…

Python绘制地磁场

文章目录简介磁场绘制简介 为国际参考磁场对Python的封装&#xff0c;可通过经纬高度以及时间来计算地磁场强度&#xff0c;使用方法简单粗暴&#xff0c;如下 import pyIGRF pyIGRF.igrf_value(lat, lon, alt, date)参数含义为 lat 纬度lon 经度alt 海拔date 日期&#xff…

vuejs中组件的两种不同的编写风格-选项式API及组合式API

前言随着vue3的逐渐稳定,以及周边生态的完善,现在vue3已经成为默认的使用方式了的所以,对于一个前端开发者,Vue2与Vue3都得要会,在vue3中新增很多东西,比如:Fragment,Teleport,Suspense,也去掉了vue2中一些特性,比如:移除keyCode支持作为v-on的修饰符等在编程风格上也有一些区别…

Java项目:springBoot+Vue汽车销售管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目基于spring boot以及Vue开发&#xff0c;为前后端分离的项目。针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等…

【Python百日进阶-数据分析】Day138 - plotly甘特图:px.timeline()

文章目录一、语法二、参数三、返回值四、实例4.1 带有 plotly.express 的甘特图和时间表4.1.1 普通甘特图4.1.2 px.timeline 的离散颜色4.1.3 px.timeline 的连续颜色4.1.4 同一水平线上有多个条4.1.5 Dash中使用甘特图一、语法 甘特图是一种条形图&#xff0c;用于说明项目进…

【C++高阶数据结构】并查集

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

Linux之top命令详解

Linux之top命令详解 一、简单介绍 top是Linux性能分析工具&#xff0c;显示系统占用资源情况&#xff0c;和windows的任务管理器一样。top动态显示进程暂用资源情况&#xff0c;top对系统处理器的状态监视&#xff0c;它将显示CPU任务列表&#xff0c;按照CPU使用、内存使用和…

You are not allowed to create a user with GRANT

8.0之后的mysql不支持授权的时候就进行用户创建&#xff0c;所以创建之后才能授权; USE mysqlSELECT USER, PASSWORD, HOST FROM USER;SELECT USER ,grant_priv FROM USERCREATE USER zjy IDENTIFIED BY 123456; #host默认是%GRANT ALL PRIVILEGES ON *.* TO zjy% MySql-Ser…

【正点原子I.MX6U-MINI移植篇】rootfs移植过程详解(三)

Linux三巨头己经完成了2个了&#xff0c;就剩最后一个rootfs&#xff08;根文件系统&#xff09;了&#xff0c;根文件系统的组成以及如何构建根文件系统是Liux移植的最后一步&#xff0c;根文件系统构建好以后就意味着我们己经拥有了一个完整的、可以运行的最小系统。以后我们…

智慧工地车辆未冲洗抓拍系统 opencv+yolo

智慧工地车辆未冲洗抓拍系统利用opencvyolo网络深度学习架构模型对现场画面中车辆的冲洗情况实现智能识别。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。OpenCV-Python是OpenCV的Python API&#xff0c;结合了OpenCV CAPI和Python语言的最佳特性。O…