尴尬了!外包同事参加部门聚餐,刚到包间门口,就听主管说:都自己人哈,以后有啥脏活累活都给外包,招他们本就是服务咱的,不要不好意思
刚看到个贴子,说外包同事去参加部门聚餐,刚到包间门口,就听见主管说:以后脏活累活都给外包,招他们本来就是服务我们的。人家当场愣住,气到都不想进去。
我觉得这事吧,难受的不只是那句玩笑,而是直接把人分成“自己人”和“外包人”。身份是外包,只是合同形式,不代表谁就低一等,更不该成了干脏活累活的默认理由。
网友们的回帖,我看大致三种:有人骂主管没素质,有人劝外包赶紧跑路,也有人说忍忍反正都要打工。
我的看法是,委屈可以记住,但别只生气,趁早评估这家公司值不值得留下,悄悄练好本事,把选择权拿回自己手里。
算法题:交替打印字符串
我昨天晚上十一点多还在公司楼下吃着盒饭,结果我们组那个小李突然问我一句:哥,面试老爱考那个“交替打印字符串”,你是怎么写的?我筷子一放,唉,这不就是典型的算法+多线程综合题嘛,正好跟你唠一唠。
先把题目说清楚哈,不然一会儿说着说着就跑偏了。大部分版本长这样:给你两个字符串,比如 "abc" 和 "123",要求输出 a1b2c3,也就是“一个我,一个你”这么交替着来。如果放到多线程里,就是开两个线程,一个负责打印字母,一个负责打印数字,要保证它俩轮流输出,不能乱序。
先来个最朴素的单线程写法,别一上来就上多线程,把自己绕晕了。
大概思路其实特别生活化:就像两个人夹菜,一个左手一个右手,大家轮流来,如果有一方菜夹完了,就剩下另一方自己吃。翻成代码就是用两个下标往前走:
defmerge_alternate(s1: str, s2: str) -> str:
i, j = 0, 0
turn = 0# 0 表示轮到 s1,1 表示轮到 s2
res = []
# 当两个字符串至少有一个还没走完,就继续
while i < len(s1) or j < len(s2):
if turn == 0and i < len(s1):
res.append(s1[i])
i += 1
elif turn == 1and j < len(s2):
res.append(s2[j])
j += 1
# 轮换“发球权”
turn = 1 - turn
# 万一某一边已经没字符了,就不要一直等它
if i >= len(s1) and j < len(s2):
# s1 结束了,后面全是 s2
res.append(s2[j:])
break
if j >= len(s2) and i < len(s1):
# s2 结束了,后面全是 s1
res.append(s1[i:])
break
return"".join(res)
if __name__ == "__main__":
print(merge_alternate("abc", "123")) # a1b2c3
print(merge_alternate("abcd", "12")) # a1b2cd
print(merge_alternate("ab", "12345")) # a1b2345
你看这个函数,几件小事儿: 第一,它保证尽量交替,所以用一个 turn 标记现在轮到谁; 第二,它考虑了长度不一样的情况,哪一边先用完,就把另一边剩下的全塞到结果里; 第三,复杂度特别老实,时间 O(n+m),空间也是 O(n+m),面试官问起来你也能说得过去。
等等我先喝口水……好,继续说多线程的版本,这个才是很多人翻车的地方。
多线程场景你可以脑补成:一个线程专门打印字母,一个线程专门打印数字,它俩共用同一个“麦克风”(stdout),必须轮流说话。那就得有个“主持人”维持秩序,这个主持人在 Python 里一般就是 Condition 或者 Event 之类的同步原语。
下面这个例子简单点,只考虑两个字符串长度相等的情况,比如 "abc" 和 "123",就先搞清楚交替这件事本身:
import threading
s1 = "abc"
s2 = "123"
cond = threading.Condition()
turn = "s1"# 先让 s1 打头阵
defprint_s1():
global turn
with cond:
for ch in s1:
# 如果现在不是自己说话,就等着
while turn != "s1":
cond.wait()
print(ch, end="", flush=True)
# 说完把话筒递给 s2
turn = "s2"
cond.notify()
defprint_s2():
global turn
with cond:
for ch in s2:
while turn != "s2":
cond.wait()
print(ch, end="", flush=True)
turn = "s1"
cond.notify()
if __name__ == "__main__":
t1 = threading.Thread(target=print_s1)
t2 = threading.Thread(target=print_s2)
t1.start()
t2.start()
t1.join()
t2.join()
print() # 换个行,输出好看点
这段代码你们细品一下,它有几个小点很像现实里的排队场景:
两个线程共用一个 Condition,相当于在同一个门口排队。turn这个变量就像一个小牌子:现在轮到谁,谁才能说话。每次 while turn != "我"的时候就wait(),等别人说完。自己说完之后把 turn改成对方,然后notify()把对方叫醒。
很多人第一次写会把 while 写成 if,这个小细节在多线程里很致命。因为 wait() 被唤醒不一定是你想象的那个“正确时刻”,有可能是虚假唤醒,所以要用 while 再检查一遍条件,这就是所谓防“假醒”。
那有人肯定要问了,长度不一样怎么办?比如 "abcd" 和 "12",这要在多线程里写严谨就有点啰嗦了,你得考虑其中一个线程先跑完之后,最后再通知一下另一个线程别一直白等着。面试的时候一般有两种玩法:
要么直接说:我们先只讨论长度相等的简化版本,思路是通的; 要么就回到刚才那个单线程函数,真正业务里用它就够了,多线程更多是个“同步机制展示题”。
我一般会这么跟面试官聊:单线程版本负责“业务上正确 + 覆盖各种边界”,多线程版本负责“展示我对锁、条件变量这些工具的理解”,两手都要抓,一边写完一边嘴上还能把设计思路说清楚,基本就加分了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB