从素数生成看Haskell的简洁性

2022-01-14 16:47:31 浏览数 (2)

最近有空就在看Haskell,真是越看越觉得这个语言有意思。在知乎(原回答@阅千人而惜知己的)找到了一份很有意思的求素数代码,非常简洁,我觉得很能体现这个语言的特点。

代码语言:javascript复制
primes = sieve [2..]
sieve (p:xs) = p : sieve [x| x <- xs , x `mod` p /=0 ]

其实本质思想就是Eratosthenes筛法。核心函数就是sieve,大致处理过程是这样:读入一个列表,并取出第一个元素p。然后筛选出不能被p整除的剩余数字,递归求解。这里提及一下,[2..]是Haskell列表的一个神奇的特性,即支持无限列表。这个Haskell的lazy特性有很大的关系。类似的算法在CPP中可以这么表示:

代码语言:javascript复制
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中,又能怎么写呢?

代码语言:javascript复制
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是极其类似的,看代码:

代码语言:javascript复制
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总是能用短小精悍的代码在众多语言中脱颖而出。比如斐波那契数列的生成:

代码语言:javascript复制
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的一大重要动力。

0 人点赞