一段奇葩的1024代码

2023-11-02 17:23:34 浏览数 (1)

10月24号那天,也就是传说中的1024程序员节,我翻开日历的时候,看到一段代码:

说实话,我一下子还真没看出这段代码是在干啥。

不过很明显是一段js代码,于是我就拍照、识别、修正后,放到浏览器的控制台里运行了一下:

原来是输出1024四个字符画。

出于好奇,我仔细研究了一番,算是弄清楚这代码是怎么画出字符来的。

为了便于理解,我转了一份python版。你们要不先试试看,能看明白吗?

接下来我就逐行解读一下,讲透里面的每一个知识点。

Python代码和原版js代码原理是一样的,只是语法和调用的函数不同。

代码最后一行是整个程序核心代码的入口:

代码语言:javascript复制
list(map(R,map(int, str(1<<10))))

这行代码,可以分解成几步来看:

代码语言:javascript复制
>>> 1 << 10
1024

这是一个向左移位的运算位,左移1位,就相当于乘以2,1左移10位,就是1乘以2的10次方,也就是大家熟悉的 1024。

代码语言:javascript复制
>>> str(1<<10)
'1024'

然后转成字符串。

代码语言:javascript复制
>>> list(map(int, str(1<<10)))
[1, 0, 2, 4]

map函数是用指定函数对一个序列做映射,得到一个新的序列。

比如这里映射函数是int,序列是字符串,那就是把字符串里每个字符单独转成整数,组成新的序列。

python3里map的返回值是一个迭代器,想查看的需要遍历或转成列表。

代码这里没有转,因为map的外面还有一层map,外层map的映射函数就是整个代码的主体:函数R

于是就很容易看出这行代码做的事情了:就是把 1 0 2 4 四个数字,分别作为参数来调用函数R。

拿其中一个数字来测试就会发现,函数R所做的事情就是绘制出参数对应的字符画:

再来看函数R内部:

代码语言:javascript复制
B = list(map(lambda i: i=='1', bin(n)[2:].zfill(4)))[::-1]

同样,我们来分解这行复杂的代码:

代码语言:javascript复制
>>> bin(2)
'0b10'

bin是将整数转成二进制,以2为例,bin(2)就是0b10。

代码语言:javascript复制
>>> bin(2)[2:]
'10'

切片去掉前面2个字符得到数字部分。

代码语言:javascript复制
>>> bin(2)[2:].zfill(4)
'0010'

zfill是用0填充字符至指定位数。

接下来又是map,这次的映射函数是通过lambda自定义的函数,效果是返回参数是否为字符串1:

代码语言:javascript复制
>>> list(map(lambda i: i=='1', bin(2)[2:].zfill(4)))
[False, False, True, False]

于是刚才的0010就会变成 False False True False 组成的列表。

转成列表后,再通过步长为-1的切片操作将列表逆序翻转,得到的结果赋值给B:

代码语言:javascript复制
>>> list(map(lambda i: i=='1', bin(2)[2:].zfill(4)))[::-1]
[False, True, False, False]

遍历下 1 0 2 4 可以看到对应B的不同结果:

接下来是一个for循环,里面用到了代码开头定义的M和L。

M是一串字符,L又是一个map映射。

代码语言:javascript复制
L = list(map(lambda i: ord(i) - 97, M))

映射函数里,ord是一个用来将字符转成ascii码的函数,而小写字母a的ascii码就是97:

代码语言:javascript复制
>>> ord('a')
97

所以这个函数就是计算一个字符在字母表中的序号。而L就是将M中每个字符转成数字序号。

这里其实是在故意绕弯子,把原本可以直接写出来一组数字,伪装成一个字符串。

而这个for循环,根据M的长度进行遍历,再按索引去L中取值的操作,也完全可以简化成直接对L中的元素进行遍历:

代码语言:javascript复制
for l in L:
    print(l)

在每次循环中,都去调用了函数F,那这个函数又是在做什么呢?

它也是一个lambda匿名函数,如果写成这样,或许更好理解:

代码语言:javascript复制
def F(a,b,c,d,t):
    x = [a, b, c, d, not a, not c, not d, 
        not a and not b, not a and not c, not b and not c, 
        not a and not b and not c, a or c, a or b, b or c, 
        a or b or c
    ]
    return x[t]

函数里用 a b c d 4个参数的不同逻辑组合,定义了一个很长的bool值列表,然后再根据最后一个参数t,决定返回列表中的第几个元素,结果要么是True,要么是False。

代码在这里用了解包的方式传递参数。给了*B,就相当于给了4个参数:

代码语言:javascript复制
>>> print(*B)
False True False False

后面的 and-or 是一种约等于if-else的逻辑,如果and前面的值为True,则返回and后面的值,否则返回or后面的值。

所以,如果函数F的结果是True,前面定义好的字符串D就加上一个参数对应的字符,否则加一个空格。

下面一行也是一个and-or,效果是每隔5个字符串加一个换行。

最后把字符串D输出。

看下 len(M),M的长度,也就是循环的次数:

代码语言:javascript复制
>>> len(M)
35

是35。每5个换一行,那就是一个5x7的矩阵,对比下输出的字符画,很明显看出,就是这里在绘制字符画。

而函数F的作用就是计算每个位置上,应该是画数字,还是留白。

在代码中加上了一点输出,让这个过程更加直观一些

函数R里面会根据当前参数生成对应的参数序列B,再用这些参数和遍历L中的数字来调用函数F,依次计算出字符画上的35个格子应该如何绘制,最后绘制出结果。

至此,程序的逻辑我们已经清楚了。

但,还有点问题。

这其中的字符串M和函数F中的列表是怎么来的?

为什么用它们就能绘制出数字图案?

除了1024外的其他数字,也可以同样画出来吗?

测试下用0~9分别调用函数R的结果,发现只有1024是对的,说明这段代码仅针对这4个字符。

我们把 1 0 2 4 分别对应的4组 a b c d,代入到函数F的列表中,计算满足每一个条件的所有可能数字,就会发现列表中的每一项分别对应这4个数字的不同组合:

而对于字符画中的35个位置,每一个分别是哪种组合,就在L里记录下对应的索引序号:

这样就得到了这35个数字,再反推一下就有了字符串M:

好了,你现在是不是对这个代码已经完全理解了呢?

那么最后留一个问题,如果我想要输出520这三个字符画,需要怎么修改呢?

作者:Crossin的编程教室

0 人点赞