【问题标题】:Haskell, list of natural numberHaskell,自然数列表
【发布时间】:2011-01-30 00:53:32
【问题描述】:

我是 Haskell 的绝对新手,但我试图了解它是如何工作的。

我想编写自己的惰性整数列表,例如 [1,2,3,4,5...]。

对于我写的列表

ones = 1 : ones

尝试后,效果很好:

*Main> take 10 ones
[1,1,1,1,1,1,1,1,1,1]

我怎样才能对增加整数做同样的事情?

我试过了,但确实失败了:

int  = 1 : head[ int + 1]

在那之后,我怎样才能制作一个将两个流相乘的方法?如:

mulstream s1 s2 = head[s1] * head[s2] : mulstream [tail s1] [tail s2]

【问题讨论】:

  • 您可能对 () 和 [] 之间的区别感到困惑,因为如果您将所有 [] 替换为 (),您的最后一个示例(对于无限列表)有效。

标签: list haskell stream lazy-evaluation


【解决方案1】:

int = 1 : head [ int + 1]不起作用的原因是:

  • head 返回单个元素,但: 的第二个参数需要是一个列表。
  • int + 1 尝试添加列表和数字,这是不可能的。

创建从 1 到无穷大的列表的最简单方法是 [1..]

要计算除 1 以外的步数,您可以使用 [firstElement, secondElement ..],例如创建所有正奇数的列表:[1, 3 ..]

要获得[x, f x, f (f x), f (f (f x)),...] 形式的无限列表,您可以使用iterate f x,例如iterate (*2) 1 将返回列表[1, 2, 4, 16,...]

要对两个列表的每对元素成对应用操作,请使用 zipWith:

mulstream s1 s2 = zipWith (*) s1 s2

为了使这个定义更简洁,您可以使用无点形式:

mulstream = zipWith (*)

【讨论】:

    【解决方案2】:

    对于自然数,您必须使用 map:

    num1 = 1 : map (+1) num1
    

    或理解:

    num2 = 1 : [x+1 | x <- num2]
    

    当然也可以:

    num3 = [1..]
    

    【讨论】:

    • 或者nat = 1 : map succ nat
    【解决方案3】:

    我不确定这是否是您要问的,但在我看来,您想构建一个递增自然数的列表,而不依赖任何其他列表。所以,通过这个令牌,你可以做类似的事情

    incr a = a : inrc (a+1)
    lst = inrc 1
    
    take 3 lst
    => [1,2,3]
    

    从技术上讲,这被称为累加函数(我相信),然后我们所做的就是使它的特殊情况易于与 'lst' 一起使用

    你可以从那里发疯,做这样的事情:

    lst = 1 : incr lst where incr a = (head a) + 1 : incr (tail a)
    
    take 3 lst
    => [1,2,3]
    

    等等,虽然这可能依赖于一些你还没有学过的东西(在哪里)——根据 OP 判断——但它仍然应该很容易阅读。

    哦,对了,然后是列表乘法。好吧,你可以使用上面提到的zipWith (*),或者你可以像这样重新发明轮子(这更有趣,相信我:)

    lmul a b = (head a * head b) : lmul (tail a) (tail b) 
    safemul a b  
      | null a || null b  =  []
      | otherwise
             = (head a * head b) : safemul (tail a) (tail b)
    

    safemul的原因,我相信,你可以通过函数lmul进行实验,但它与'tail'(以及'head')有关。问题是,lmul 中不会出现空列表、不匹配列表等情况,因此您要么必须将各种定义(lmul _ [] = [])拼凑在一起,要么使用守卫和或where 和依此类推...或坚持zipWith :)

    【讨论】:

      【解决方案4】:

      语言中有这样的语法:

      take 10 [1,2..]
      
      => [1,2,3,4,5,6,7,8,9,10]
      

      你甚至可以做不同的步伐:

      take 10 [1,3..]
      => [1,3,5,7,9,11,13,15,17,19]
      

      【讨论】:

        【解决方案5】:

        您可以定义一个不超过一定数量的列表,然后将第一个与第二个相加,方法是保持前者不变(依此类推),如下所示:

        naturals :: Integer -> [Integer]
        naturals n
          | n <= 0    = []
          | otherwise = nat n []
            where
              nat 1 a = (1:a)
              nat n a = nat (n-k) (nat k a)
                where
                  k = (n-1)
        
        sumOf :: [Integer] -> [Integer]
        sumOf l = sof l []
          where
            sof [] a = a
            sof (x:[]) a = (x:a)
            sof (x:y:zs) a = sof (x:a) (sof ((x+y):zs) a)
        

        由于它们都是一,因此您可以通过更改它们总和的顺序以任何您喜欢的方式递增它们,从左到右,到中间点等等。您可以使用以下方法测试最多一百个(或更多):

        (sumOf . naturals) 100
        

        编辑:请阅读 Will Ness 下面的 cmets。

        【讨论】:

        • nat n a = nat (n-k) (nat k a) where k = (n-1) == nat n a = nat (n-(n-1)) (nat (n-1) a) == nat n a = nat 1 (nat (n-1) a) == nat n a = 1 : nat (n-1) a。所以naturals n == take n (repeat 1).
        • == [ 1 | _ &lt;- [1..n] ]。取其部分总和(或前缀总和),通常用tail . scanl (+) 0 来完成。如果您想使用sum,可以使用map sum . tail . initsreverse . map sum . init . tails 完成,这仅适用于有限列表,但重复的summing 将使其成为二次方。 scanl (+) 是线性的。
        • 谢谢@WillNess。始终专注:-) 二次和线性是什么意思?与执行速度有关吗?未来我会努力变得更成熟(虽然这很难,因为我并不总是知道我要去哪里)。
        • 这是algorithmic complexity 中的标准术语。执行时间随着问题大小的平方增长是二次的,写成 O(n^2)(或者可能是 Theta(n^2))。而线性是线性的,~ n。我的个人资料简介中还有the first link。 :)
        • 至于复杂程度,我真的无法理解你的sof 函数是如何工作的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-01
        • 2012-04-15
        • 2019-06-01
        相关资源
        最近更新 更多