最近有空就在看Haskell,真是越看越觉得这个语言有意思。在知乎(原回答@阅千人而惜知己的)找到了一份很有意思的求素数代码,非常简洁,我觉得很能体现这个语言的特点。
primes = sieve [2..] sieve (p:xs) = p : sieve [x| x <- xs , x `mod` p /=0 ]
其实本质思想就是Eratosthenes筛法。核心函数就是sieve,大致处理过程是这样:读入一个列表,并取出第一个元素p。然后筛选出不能被p整除的剩余数字,递归求解。这里提及一下,[2..]
是Haskell列表的一个神奇的特性,即支持无限列表。这个Haskell的lazy特性有很大的关系。类似的算法在CPP中可以这么表示:
bool primes[maxn]; for (int i = 2; i < sqrt(maxn+0.5); i++) { if (!primes[i]) { for (int j = i + i; j < maxn; j += i) primes[j]++; } }
需要注意的是,这段代码的结果并不是一个内容为2-maxn内素数的数组,而是记录2-maxn间的数字是不是素数的一个布尔数组。那么,如果是放在同样具有列表解析的Python中,又能怎么写呢?
primes = [x for x in range(2, maxn) if x not in [j for i in range(2, int(math.sqrt(maxn + 0.5))) for j in range(i + i, maxn, i)]]
这段代码体现了python列表解析的一个特点——很难看懂。不过其算法本质还是和CPP版本相同。
百度的时候还发现了大牛廖雪峰的另一种操作,即采用generator的形式构造一个序列并filter。这种lazy的处理方法和Haskell是极其类似的,看代码:
def _odd_iter(): # 构造偶数序列 n = 1 while True: n = n + 2 yield n def _not_divisible(n): return lambda x: x % n > 0 def get_primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一个数 yield n it = filter(_not_divisible(n), it) # 构造新序列
看来看去,似乎Haskell的版本真的很简单舒服。的确,在处理诸如递归这种问题上,FP总是能用短小精悍的代码在众多语言中脱颖而出。比如斐波那契数列的生成:
fibonaccis = 1 : 1 : zipWith (+) fibonaccis (tail fibonaccis)
fibonacci !! 12
输出233,结果正确。这段代码也是Haskell简洁性的高度体现。其中,tail想到与后移整个数列,之后通过zipWith函数的处理将两个数列相加,以此来达到F(n)=F(n-1)+F(n-2)的效果。我们可以试验下,比如:zipWith (+) [1,1,2] (tail [1,1,2])
的结果是[2,3]。所以大致就是一个移动数组并叠加的过程。
虽然说这样高度精简的代码由于不直观,并不太适合在实际的项目中使用,况且其他语言的稍长的代码甚至可能在效率上更优,但这仍不影响Haskell表现其独有的简洁及优雅的魅力。而这也正是我们研究FP的一大重要动力。
评论