【问题标题】:Take elements on even positions from the list从列表中获取偶数位置的元素
【发布时间】:2019-12-22 07:47:07
【问题描述】:

问题:使用折叠,从列表中取出偶数位置的元素:

GHCi> evenOnly [1..10]
 [2,4,6,8,10]
GHCi> evenOnly ['a'..'z']
 "bdfhjlnprtvxz"

evenOnly :: [a] -> [a]
evenOnly = undefined

我首先决定获取0-es 和1-s 的交替列表:[0,1,0,1..]

Prelude> let g = iterate (\x -> (x + 1) `mod` 2) 0
Prelude> take 10 $ g
[0,1,0,1,0,1,0,1,0,1]

然后将其与原始列表压缩,得到一对列表:[(x1, 0), (x2,1), (x3,0) .. (xn, ?)]:

Prelude> zip g [1,2,3,4,5]
[(0,1),(1,2),(0,3),(1,4),(0,5)]

之后,foldr 具有过滤功能的对列表和一个空列表作为初始化值。

所以我认为这会起作用:

evenOnly :: [a] -> [a]
evenOnly xs = let g = iterate (\x -> (x + 1) `mod` 2) 0
  in
  foldr (\ (x, n) s -> if n == 1 then x : s else s) [] . (zip g xs)

但它给出了一个我不明白的错误:

foldr.hs:44:59: error:
    • Couldn't match expected type ‘a0 -> t0 (a1, Integer)’
                  with actual type ‘[(Integer, a)]’
    • Possible cause: ‘zip’ is applied to too many arguments
      In the second argument of ‘(.)’, namely ‘(zip g xs)’
      In the expression:
        foldr (\ (x, n) s -> if n == 1 then x : s else s) [] . (zip g xs)
      In the expression:
        let g = iterate (\ x -> (x + 1) `mod` 2) 0
        in
          foldr (\ (x, n) s -> if n == 1 then x : s else s) [] . (zip g xs)
    • Relevant bindings include
        xs :: [a] (bound at foldr.hs:42:10)
        evenOnly :: [a] -> [a] (bound at foldr.hs:42:1)

我认为我的想法是正确的,我只是在语法上做错了。

【问题讨论】:

    标签: list haskell syntax fold type-mismatch


    【解决方案1】:

    一个人不应该计算任何不需要计算的东西。选择是位置的,它是预先知道的。计算模数,与布尔值比较,都是多余的工作。

    相反,先做this,然后再做that,然后继续这样切换; 按照要求使用foldr

    evenly :: [t] -> [t]
    evenly xs = foldr c z xs f g
       where
       c x r f g = f x (r g f)
    

    接下来我们根据使用方式完成定义:

       z _ _  = []
       f _ xs = xs
       g x xs = x : xs
    

    【讨论】:

    • 这让我想起了带有协同折叠的 zip 编码。如果我们在这里采用其他基于 zip 的解决方案——尤其是 Willem 回答中的最后一个,——将 zip 编码为切换折叠、内联、融合和简化所有内容,我们可能最终会得到(相当于)这个代码。
    • 您传递下一个要使用的函数的模式,而不是受布尔启发的this answer of mine
    • @JosephSible 很高兴听到!会看看。 :)
    【解决方案2】:

    模式匹配是一种完全合理的方法:

    evenOnly :: [a] -> [a]
    evenOnly (_ : a : as) = a : evenOnly as
    evenOnly _ = []
    

    另一种选择是使用列表推导:

    evenOnly as = [a | (a, True) <- zip as (cycle [False, True])]
    

    如果与其他列表处理函数融合,列表理解版本可能会更高效。

    【讨论】:

      【解决方案3】:

      你可以把上面的zip加上一串数字,比如:

      evenOnly :: [a] -> [a]
      evenOnly = foldr (\(c, x) -> if c then (x:) else id) [] . zip (cycle [False, True])

      这里cycle [False, True] 因此生成[False, True, False, True, …] 的无限列表。在foldr 中,我们检查源自cycle [False, True] 的对应值c。如果是True,那么我们在列表前面加上x,否则我们只是用id传递递归调用的结果。

      或者我们可以省略这个,并使用:

      evenOnly :: [a] -> [a]
      evenOnly = foldr (uncurry ($)) [] . zip (cycle [const id, (:)])

      【讨论】:

      • @DanielWagner:感谢您的建议,已更新:)
      • @DanielWagner:这不应该是foldl吗?由于foldr 向相反的方向移动,因此奇数长度的列表与偶数长度的列表会有不同的行为?
      • 是的,我删除了我的评论,因为你说的方式和懒惰的特点都是错误的。
      【解决方案4】:

      (.) 是函数组合,但 zip g xs 是列表而不是函数。您可以直接将结果列表作为foldr 的参数应用。请注意,gxs 的参数顺序错误:

      evenOnly :: [a] -> [a]
      evenOnly xs = let g = iterate (\x -> (x + 1) `mod` 2) 0
        in
        foldr (\ (x, n) s -> if n == 1 then x : s else s) [] (zip xs g)
      

      【讨论】:

        猜你喜欢
        • 2018-09-25
        • 1970-01-01
        • 2021-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-03
        • 2012-09-08
        • 1970-01-01
        相关资源
        最近更新 更多