【问题标题】:How to zip multiple lists in Haskell?如何在 Haskell 中压缩多个列表?
【发布时间】:2011-01-28 22:06:00
【问题描述】:

在 python 中zip 函数接受任意数量的列表并将它们压缩在一起。

>>> l1 = [1,2,3]
>>> l2 = [5,6,7]
>>> l3 = [7,4,8]
>>> zip(l1,l2,l3)
[(1, 5, 7), (2, 6, 4), (3, 7, 8)]
>>> 

我如何zip haskell 中的多个列表一起使用?

【问题讨论】:

  • 是的,zip3 用于压缩 3 个列表。

标签: list haskell


【解决方案1】:

使用Applicative Notation 可以实现 zip 的泛化。由于新类型的包装/展开,使用起来有点不愉快,但如果你正在做一些不能用 zipWithn 来完成相当小的 n 的事情,你可能已经处于足够高的抽象级别,其中符号无论如何都没有痛苦。

类型为ZipList a,其应用实例将列表压缩在一起。例如:

(+) <$> ZipList [1,2] <*> ZipList [3,4] == ZipList [4,6]

这可以推广到使用偏应用的任意数量和类型的函数:

(+) <$> ZipList [1,2]  :: ZipList (Int -> Int)

看看 (+) 是如何在这里部分应用的?

如果你不喜欢到处添加 ZipList 和 getZipList,你可以很容易地重新创建符号:

(<$>) :: (a -> b) -> [a] -> [b]
(<$>) = map

(<*>) :: [a -> b] -> [a] -> [b]
(<*>) = zipWith ($)

那么zipWith f a b c d ... 的符号是:

f <$> a <*> b <*> c <*> d <*> ...

应用符号是一种非常强大且通用的技术,其范围比广义压缩要广泛得多。有关应用符号的更多信息,请参阅Typeclassopedia

【讨论】:

  • 为了帮助解决新类型包装/解包问题,请参阅新类型包hackage.haskell.org/package/newtype
  • Typeclassopedia 链接给出 404。
  • 那么我将如何压缩[ZipList] 呢?正确的类型很难......
  • @Sebastian,使用Data.Functor.Compose。你会压缩Compose ZipList ZipList as
  • 所以如果我想使用多维 ZipLists,我需要做的就是使用 Compose 组合 ZipLists?
【解决方案2】:

您可以转置列表列表:

>>> import Data.List
>>> transpose [l1,l2,l3]
[[1,5,7],[2,6,4],[3,7,8]]

【讨论】:

  • 只有在所有参数都具有相同类型时才有效,这也是实现通用 zipN 似乎如此困难的原因。
  • 行为不同:转置 [[1,2,3],[4,5,6,7]] => [[1,4],[2,5],[3 ,6],[7]]。 zip 不包括最后一个元素。但从那个陷阱来看,它是赢家!绝对加入我的工具箱。
  • 我写了这个函数来避免在列表长度不匹配的情况下出现不同的行为:zipLists list = takeWhile (\x -&gt; length x == length list) $ transpose list
【解决方案3】:

Haskell 中似乎还有 zip3 (doc) 和 zip4 (doc) 函数。但是由于强大的类型系统,zipn 似乎很复杂。 Here is a good discussion我在研究过程中发现的。

【讨论】:

  • 你的意思是它在 haskell 中是不可能的吗??
  • 我不知道。也许其他人知道一个技巧。但我认为机会很小,因为邮件列表上没有一个好的答案,而标准库已经实现了所有那些 zip2、zip3 等函数......
  • Data.List 上升到zip7。可以说,一个元组中的 7 个项目对于一个易于理解、可维护的程序来说已经太多了。
【解决方案4】:

GHC also supports parallel list comprehensions:

{-# LANGUAGE ParallelListComp #-}

[(x,y) | x <- [1..3]
       | y <- ['a'..'c']
       ]

==> [(1,'a'),(2,'b'),(3,'c')]

我刚刚测试了最多 26 个并行变量,这对于所有实际用途来说应该足够了。

虽然它有点 hacky(而且不标准),所以如果你写的是严肃的东西,ZipList 可能是更好的选择。

【讨论】:

  • this 和普通列表理解的主要区别在于 x 不再在 y 的理解范围内,对吧?
【解决方案5】:

我认为这可能是建议的最不优雅的解决方案,但为了完整起见,应该添加模板 Haskell 应该可以实现这样的事情。

这实际上在我认为是原始模板 Haskell 论文中有所涵盖(在文本中搜索 zipn): http://research.microsoft.com/en-us/um/people/simonpj/Papers/meta-haskell/meta-haskell.pdf

但我认为该代码实际上从未起作用,请参见: http://www.haskell.org/pipermail/template-haskell/2003-July/000126.html (没有实现模式切片)。

这在 2003 年尚未实施,但今天仍未实施: http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/template-haskell.html(不支持模式切片)

但是有一个使用模板 haskell 的 zipWithN 实现: http://www.haskell.org/haskellwiki/Template_Haskell#zipWithN

我已经验证它可以与这个测试程序一起工作:

{-# LANGUAGE TemplateHaskell #-}
import Zipn

main = do
    let l1 = [1,2,3]
    let l2 = [5,6,7]
    let l3 = [7,4,8]
    print $ $(zipWithN 3) (,,) l1 l2 l3

在 Zipn 模块中,我粘贴了 zipn,只是为了清楚起见重命名了 zipWithN(记得在顶部添加 pragma TemplateHaskell)。请注意,N 实际上在这里被硬编码了两次,因为我必须将(,,) 作为“with”函数。您必须根据 N 更改逗号的数量。

(,,) 代表\a b c -&gt; (a,b,c)

我猜想具有良好 Template Haskell 技能的人(我现在不是这种情况)可以使用 Template Haskell 制作一个直接的 zipN。

【讨论】:

    【解决方案6】:

    这不是微不足道的,但它是可行的。见this blog post。我不知道这是否被做成了一些图书馆。

    这里是another version,比较简单。这个实际上可以在这里剪切粘贴:

    {-# LANGUAGE MultiParamTypeClasses
               , FunctionalDependencies
               , FlexibleInstances
               , UndecidableInstances
               #-}
    
    -- |
    -- Module      :  Data.List.ZipWithN
    -- Copyright   :  Copyright (c) 2009 wren ng thornton
    -- License     :  BSD3
    -- Maintainer  :  wren@community.haskell.org
    -- Stability   :  experimental
    -- Portability :  non-portable (MPTCs, FunDeps,...)
    --
    -- Provides a polyvariadic 'map'/'zipWith' like the @map@ in Scheme.
    -- For more details on this style of type hackery, see:
    --
    --    * Chung-chieh Shan, /A polyvariadic function of a non-regular/
    --      /type (Int->)^N ([]^N e)->.../
    --      <http://okmij.org/ftp/Haskell/polyvariadic.html#polyvartype-fn>
    ----------------------------------------------------------------
    module Data.List.ZipWithN (ZipWithN(), zipWithN) where
    
    -- | This class provides the necessary polymorphism. It is only
    -- exported for the sake of giving type signatures.
    --
    -- Because we can't do functor composition without a lot of noise
    -- from newtype wrappers, we use @gr@ and @kr@ to precompose the
    -- direct/list functor with the reader functor and the return type.
    class ZipWithN a gr kr | kr -> gr a where
        _zipWithN :: [a -> gr] -> [a] -> kr
    
    instance ZipWithN a b [b] where
        _zipWithN = zipWith ($)
    
    instance ZipWithN b gr kr => ZipWithN a (b -> gr) ([b] -> kr) where
        _zipWithN = (_zipWithN .) . zipWith ($)
    
    
    -- | Polyadic version of 'map'/'zipWith'. The given type signature
    -- isn't terribly helpful or intuitive. The /real/ type signature
    -- is:
    --
    -- > zipWithN :: {forall a}^N. ({a->}^N  r) -> ({[a]->}^N  r)
    --
    -- Note that the @a@ type variables are meta and so are independent
    -- from one another, despite being correlated in N across all
    -- repetitions.
    zipWithN :: (ZipWithN a gr kr) => (a -> gr) -> [a] -> kr
    zipWithN = _zipWithN . repeat
    

    如果您刚刚开始学习 Haskell,请推迟一段时间了解它 :)

    【讨论】:

    • 我一个字都听不懂。我只有非常基本的 Haskell 知识。您能否从该帖子中提取该功能并将其发布在此处?
    【解决方案7】:

    概括压缩实际上很容易。您只需为ZipList 编写Applicative 组合子的专用版本:

    z :: [a -> b] -> [a] -> [b]
    z = zipWith ($)
    
    infixl 4 `z`
    

    现在您可以压缩任意数量的列表:

    f <$> xs `z` ys `z` zs
    

    或者:

    repeat f `z` xs `z` ys `z` zs
    

    【讨论】:

      【解决方案8】:

      如果您的所有数据都属于同一类型,您可以这样做:

      import Data.List (transpose)
      
      zipAllWith :: ([a] -> b) -> [[a]] -> [b]
      zipAllWith _ []  = []
      zipAllWith f xss = map f . transpose $ xss
      
      zipAll = zipAllWith id
      

      例子:

      > zipAll [[1, 2, 3], [4, 5, 6], [7, 8]]
      [[1,4,7],[2,5,8],[3,6]]
      

      【讨论】:

        【解决方案9】:

        对于特定数量的列表,您可以这样:

        > let l1 = [1,2,3]
        > let l2 = "abc"
        > let l3 = [10.0, 11.0, 12.0]
        > let l4 = [True, False, False]
        
        > [ (e1,e2,e3,e4) | (((e1,e2),e3),e4) <- zip (zip (zip l1 l2) l3) l4 ]
        [(1,'a',10.0,True),(2,'b',11.0,False),(3,'c',12.0,False)]
        

        它不是一个通用函数,而是一种可以应用于不同数量列表的模式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-07-31
          • 1970-01-01
          • 2011-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-09-30
          相关资源
          最近更新 更多