刚看到个贴子,说有网友爆料:同事跳槽成功,工资直接涨了8000,结果入职第一天,老板一句话先压上来——挖你的人把你夸上天了,接下来就看你配不配得上。

网友回帖里也挺分裂的,有人替这位同事紧张,觉得这是提前PUA;也有人说,给这么多钱,本来就该有更高期待。
我觉得这事吧,说到底挺现实。涨薪不是白给的,就像你买了贵票坐高铁,车厢肯定不可能再是绿皮的待遇。
老板那句话不好听,但潜台词很清楚:工资高了,容错率就低了。怕的不是要求高,而是自己心里没数,还按老工资的节奏干活。
不过话说回来,跳槽拿高薪也不是原罪,这是市场对能力的定价。关键在于,别被“被夸上天”这件事绑架,稳住心态,把事一件件干好,比纠结那句话更重要。
昨天晚上十一点多,我在公司楼下等外卖,手里还捏着半杯凉了的美式,我们组那个小李突然在群里丢了个问题:“哥,推荐关系那块怎么快速算出一个用户的最终推荐人啊?我那 SQL 写得要死要活的。” 我当时一眼就觉得,这玩意儿用算法和 Go 写个小工具,比在数据库里各种 JOIN 香多了,跟之前我写数据库压测那篇的感觉完全不一样
先把场景说人话一点啊。就想象你做了个拉新活动,每个用户注册的时候可以填“推荐人ID”,于是会出现这种关系:
那问题来了: 给你一个 userId,比方说 1003,你要算出它整个推荐链,以及它最上面的“老祖宗推荐人”(第一个没有上级的人)。
现实里这个需求很多的,比如: (1)结算奖励的时候,只给最顶层那一层发钱。 (2)风控的时候,要看这个人往上追溯是不是挂在某个黑名单用户下面。
再顺带说一句,有些系统里推荐链还会有脏数据,比如 A 推荐 B,B 推荐 C,C 又推荐回 A,直接形成一个环,这种如果你不防一下,代码一跑就死循环了。
我们先别着急写 Go,先想一下这个关系怎么存比较顺手。
最直接的办法:用一个 map 来存“谁的推荐人是谁”:
map[int]int{1001: 1000,1002: 1001,1003: 1002,}key 是用户 ID,value 是它的推荐人 ID。 如果一个用户没有推荐人,就约定 value 是 0 或者 -1。
那查一个用户的最终推荐人,其实就是一路往上走:
这个过程就像你在公司一路找上级领导,“你领导是谁”“你领导的领导是谁”,最后走到老板那里。
但刚才说了,有两个坑要注意: (1)可能出现环,要有 visited 做一次简单的“防止自转死循环”。 (2)同一个用户可能会被查很多次,老是从下面往上爬性能会有点亏,可以做个“路径压缩”,查过一次之后,把“最终推荐人”缓存起来,这个思路跟并查集挺像的。
直接上代码,先写一个最常用的:给定一个 userId,返回它的整条推荐链和最终推荐人。
package mainimport ("errors""fmt")// FindRefChain 返回:推荐链(从自己到顶层)、最终推荐人IDfuncFindRefChain(userID int, refMap map[int]int, cache map[int]int)([]int, int, error) {if cache == nil { cache = make(map[int]int) } path := make([]int, 0) visited := make(map[int]bool) cur := userIDfor {// 检测环if visited[cur] {returnnil, 0, errors.New("detect cycle in referral chain") } visited[cur] = true path = append(path, cur)// 看看有没有算过最终推荐人,直接用缓存if root, ok := cache[cur]; ok {// 把整条 path 都压缩到 rootfor _, u := range path { cache[u] = root }return path, root, nil } parent, ok := refMap[cur]if !ok || parent == 0 {// 说明 cur 已经是最顶层for _, u := range path { cache[u] = cur }return path, cur, nil } cur = parent }}funcmain() {// 模拟一份推荐关系表:user -> referrer refMap := map[int]int{1001: 1000,1002: 1001,1003: 1002,1004: 1002,2001: 0, // 自然注册,没有推荐人 } cache := make(map[int]int)// 查 1003 的推荐链if path, root, err := FindRefChain(1003, refMap, cache); err != nil { fmt.Println("error:", err) } else { fmt.Println("1003 path:", path, "root:", root) }// 再查 1004,会复用缓存,几乎 O(1)if path, root, err := FindRefChain(1004, refMap, cache); err != nil { fmt.Println("error:", err) } else { fmt.Println("1004 path:", path, "root:", root) }// 故意构造一个环试试 refMap[3001] = 3002 refMap[3002] = 3003 refMap[3003] = 3001if _, _, err := FindRefChain(3001, refMap, cache); err != nil { fmt.Println("3001 error:", err) }}顺便说一句,如果你还有一个需求,是“查某个大佬下面所有被他间接或直接推荐的用户”,那你就可以再建一份反向索引:
children := map[int][]intchildren[1001] = []int{1002, 1004} 这种,然后用一个小 DFS/BFS,把整个子树扫一遍就行了。这个思路我之前排查 TCP 那个 1024 字节卡包的 bug 时也用过,都是把复杂的东西拆成好走的小路径,一段一段地看
行,我外卖到了,先写到这。要是你们后面想把这个推荐链查找做成一个小服务,比如加缓存、加 HTTP 接口、顺便埋点监控什么的,可以再聊一版“线上可落地版”的实现。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
🔥虎哥私藏精品🔥
虎哥作为一名老码农,整理了全网最全《GO后端开发资料合集》。总量高达650GB。