Python技术迷

尴尬了!外包同事参加部门聚餐,刚到包间门口,就听主管说:都自己人哈,以后有啥脏活累活都给外包,招他们本就是服务咱的,不要不好意思

刚看到个贴子,说外包同事去参加部门聚餐,刚到包间门口,就听见主管说:以后脏活累活都给外包,招他们本来就是服务我们的。人家当场愣住,气到都不想进去。

Image

我觉得这事吧,难受的不只是那句玩笑,而是直接把人分成“自己人”和“外包人”。身份是外包,只是合同形式,不代表谁就低一等,更不该成了干脏活累活的默认理由。

网友们的回帖,我看大致三种:有人骂主管没素质,有人劝外包赶紧跑路,也有人说忍忍反正都要打工。

我的看法是,委屈可以记住,但别只生气,趁早评估这家公司值不值得留下,悄悄练好本事,把选择权拿回自己手里。

算法题:交替打印字符串

我昨天晚上十一点多还在公司楼下吃着盒饭,结果我们组那个小李突然问我一句:哥,面试老爱考那个“交替打印字符串”,你是怎么写的?我筷子一放,唉,这不就是典型的算法+多线程综合题嘛,正好跟你唠一唠。

先把题目说清楚哈,不然一会儿说着说着就跑偏了。大部分版本长这样:给你两个字符串,比如 "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()  # 换个行,输出好看点

这段代码你们细品一下,它有几个小点很像现实里的排队场景:

  1. 两个线程共用一个 Condition,相当于在同一个门口排队。
  2. turn 这个变量就像一个小牌子:现在轮到谁,谁才能说话。
  3. 每次 while turn != "我" 的时候就 wait(),等别人说完。
  4. 自己说完之后把 turn 改成对方,然后 notify() 把对方叫醒。

很多人第一次写会把 while 写成 if,这个小细节在多线程里很致命。因为 wait() 被唤醒不一定是你想象的那个“正确时刻”,有可能是虚假唤醒,所以要用 while 再检查一遍条件,这就是所谓防“假醒”。

那有人肯定要问了,长度不一样怎么办?比如 "abcd" 和 "12",这要在多线程里写严谨就有点啰嗦了,你得考虑其中一个线程先跑完之后,最后再通知一下另一个线程别一直白等着。面试的时候一般有两种玩法:

  • 要么直接说:我们先只讨论长度相等的简化版本,思路是通的;
  • 要么就回到刚才那个单线程函数,真正业务里用它就够了,多线程更多是个“同步机制展示题”。

我一般会这么跟面试官聊:单线程版本负责“业务上正确 + 覆盖各种边界”,多线程版本负责“展示我对锁、条件变量这些工具的理解”,两手都要抓,一边写完一边嘴上还能把设计思路说清楚,基本就加分了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB