使用列表解析重写zipWith函数

时间:2015-11-13 00:54:31

标签: list haskell zip list-comprehension winghci

我已经使用递归重写了zipWith函数,现在我尝试使用列表理解来重写它。我遇到了很多绑定错误,我知道我的第二行不正确。这是我的函数,就像使用递归的zipWith一样:

zipW :: (a -> b -> c) -> [a] -> [b] -> [c] 
zipW _ [] _ = []  
zipW _ _ [] = []  
zipW f (x:xs) (y:ys) = f x y : zipW f xs ys

这是我尝试将其重写为列表理解:

zipW2 :: (a -> b -> c) -> [a] -> [b] -> [c]
zipW2 f xs ys = [f x y | (x, y) <- zipW2 f xs ys]

我不确定如何更正第二个语句,以便它像zipWith一样工作,并允许我选择运算符。

2 个答案:

答案 0 :(得分:2)

您需要Parallel List Comprehensions分机:

{-# LANGUAGE ParallelListComp #-}

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f xs ys = [f x y | x <- xs | y <- ys] 

答案 1 :(得分:0)

原始zipWith有三种情况:

  1. 当第一个列表为空时
  2. 当第二个清单为空时
  3. 当两个列表都为空时
  4. 第三种情况在参数的尾部递归调用zipWith,再次进行案例分析。

    在你的定义中,你只有一个案例 - 列表理解,所以任何递归调用都会回到那个。如果没有案例分析,你可以永远循环:

    >>> let myZipWith f xs ys = [ f x y | (x,y) <- myZipWith f xs ys ]
    >>> myZipWith (,) [] []
    ^CInterrupted.
    

    此外,因为你在递归调用中使用f但是要求递归输出是一对,你要放置f x y生成一对的隐式要求:< / p>

    >>> :t myZipWith 
    myZipWith :: (t2 -> t3 -> (t2, t3)) -> t -> t1 -> [(t2, t3)]
    

    解决方案是不递归,而是直接考虑每一对。

    你可以use behzad.nouri's solution of enabling the ParallelListComp language extension

    >>> :set -XParallelListComp
    >>> let myZipWith f xs ys = [ f x y | x <- xs | y <- ys ]
    >>> myZipWith (+) [1,2,4] [0,10,20]
    [1,12,24]
    

    ParallelListComp在列表推导法律语法中生成第二个(以及后面的)垂直管道符(|),并使用早期列表并行(类似zip)逐步执行这些列表。

    很高兴知道这与正常列表理解有何不同,您可以用逗号分隔每个列表。使用逗号进行嵌套迭代,在结果列表中展平:

    >>> let notZipWith f xs ys = [ f x y | x <- xs, y <- ys ]
    >>> notZipWith (+) [1,2,4] [0,10,20]
    [1,11,21,2,12,22,4,14,24]
    

    Using the ParallelListComp extension is really just syntatical sugar for the original zipWith,所以你可能会认为这是作弊。

    你也可以依靠原来的zip

    >>> let myZipWith f xs ys = [ f x y | (x,y) <- zip xs ys ]
    >>> myZipWith (+) [1,2,4] [0,10,20]
    [1,12,24]
    

    但由于zip被定义为zipWith (,),因此也可能作弊。

    另一种方法是使用索引:

    >>> let myZipWith f xs ys = [ f x y | i <- [0..min (length xs) (length ys) - 1], let x = xs !! i, let y = ys !! i ]
    >>> myZipWith (+) [1,2,4] [0,10,20]
    [1,12,24]
    

    但这会非常低效,因为!!是一个线性时间操作,使myZipWith成为二次,而zipWith是线性的:

    >>> :set +s
    >>> last $ myZipWith (+) (replicate 10000000 1) (replicate 10000000 2)
    3
    (4.80 secs, 3282337752 bytes)
    >>> last $ zipWith (+) (replicate 10000000 1) (replicate 10000000 2)
    3
    (0.40 secs, 2161935928 bytes)
    

    我确定还有其他不好的方法可以创建具有列表理解力的zipWith的等价物,但我并不十分相信 em 好的方式,即使是上面的方式。