无意中看到一种叫 look-and-say 的数列,很有意思,有点儿 Fibonacci 的感觉。数列如下:

1, 11, 21, 1211, 111221, 312211, 13112221, 1113213211, …

从第二位开始,每个数字都是对前一个数的计数结果的描述。比如 11 表示前面的 1 有一个 1,而 21 表示前面的 11 有两个 1,1121 表示 21 有一个 2 和一个 1,依次类推,可以无穷循环出新的结果。(除了 22 这个数字,因为会一直重复 22 本身)

后来查了查才发现原来 Leetcode 也有这道题,不过名字叫做 count-and-say。基本就是给 n 然后求此数列的第 n 项结果。

想了想思路并不难,无非是对一个数字的所有位数循环再计数就好了,不过写的时候很犯难,竟然还写出了一个无比冗长的 for 循环加 if 面条代码。这让我意识到了自己对于循环的认识有多么不够深刻。

之所以犯难,其实是不知道怎样把几种逻辑合并在一起,如果粗暴地列举所有分支大概是这样:

def find_next(num: str):
	result = ''
	cur, count = num[0], 1
    size = len(num)
    for i in range(1, size):
        if num[i] == cur and i == size - 1:
            # count 加一
            # 更新 result
        elif num[i] == cur:
            # count 加一
        elif i == size - 1:
            # 更新 result
            # 更新 cur&count
            # 更新 result
        else:
            # 更新 result
            # 更新 cur&count
    # 还有一种情况就是并没有进入 for 循环
    # 那么也需要更新 result

直接按照这些分支把代码填满肯定很难看,所以要挑选合并。看起来只有第二个分支不需要更新 result,于是我决定把它单拎出来:如果 num[i]==cur 那么 count 加一;否则就更新 result 以及 cur&count. 那么 i==size-1 的时候怎么办呢,既要保存当前结果又可能需要记录新的结果,一筹莫展。不过后来还是发现了巧妙的写法:

def find_next(num: str):
    result = ''
    cur, count = num[0], 1
    for char in num[1:]:
        if char == cur:
            count += 1
            continue
        result += f'{count}{cur}'
        cur = char
        count = 1
        
    return f'{result}{count}{cur}'

优点在于结合了 count&cur 放在返回值里面,通过这两个变量做缓存,就不需要单独考虑循环到最后一位和没有进入循环的情况了,于是 for 循环也可以直接遍历元素值而不是下标。

我正觉得这段写得精彩,却又看到了使用 while 的解法。什么,为什么会是 while 呢,明明容器长度是已知的,要用 for 才对啊。先看代码:

def find_next(num: str):
    result = ''
    start = cur = 0
    size = len(num)
    while cur < size:
        while cur < size and num[cur] == num[start]:
            cur += 1
        result += str(cur - start) + num[start]
        start = cur
    
    return result

虽然我觉得 while 循环是比 for 的可读性要差的,但不得不承认这里的嵌套 while 写法更好,关键在于更符合思考的过程。如果是手动来数的话,也是整体一个大循环,然后对重复的数字不断加一,直到有变化,就先记录前面的结果再继续数新的数字。反而在上面巧妙的 for 循环中,是不太容易想到返回时把 result, count, cur 三者组合到一起的。

而且这里的 while 代码也很简洁。既复用了内部循环的 cur += 1,又节省了单独的 count 变量来计数。

所以大概可能也许是,虽然已知元素的总数,但是在更符合抽象逻辑的情况下用 while 会更好吧。

说来自己写循环的时候总会遇到几个惯性 bug:

  • 下标越界或者下标类型不是 int
  • 遍历的过程中修改更新容器本身
  • 写 while 不注意退出条件导致无限循环

不知道有没有更智能的编辑器(插件)能自动识别循环代码中的 bug 呢 - -.


References