将三个列表组合成Haskell中的3元组列表

时间:2017-09-18 01:34:30

标签: list haskell tuples

我正在尝试编写一个函数,它将三个列表作为参数,并连续创建一个列表,每个列表都有一个三元组。

我给出的示例如下:zip3Lists [1, 2, 3] [4, 5, 6] ['a', 'b', 'c']会产生[(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]

到目前为止我所拥有的是:

zipThree [] [] [] = []
zipThree [] [] [x] = [x]
zipThree [] [x] [] = [x]
zipThree [x] [] [] = [x]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs

它给了我这个错误:

haskell1.hs:32:33: error:
    • Occurs check: cannot construct the infinite type: c ~ (c, c, c)
      Expected type: [c]
        Actual type: [(c, c, c)]
    • In the expression: (x, y, z) : zipThree xs ys zs
      In an equation for ‘zipThree’:
          zipThree (x : xs) (y : ys) (z : zs) = (x, y, z) : zipThree xs ys zs
    • Relevant bindings include
        zs :: [c] (bound at haskell1.hs:32:27)
        z :: c (bound at haskell1.hs:32:25)
        ys :: [c] (bound at haskell1.hs:32:20)
        y :: c (bound at haskell1.hs:32:18)
        xs :: [c] (bound at haskell1.hs:32:13)
        x :: c (bound at haskell1.hs:32:11)
        (Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)

3 个答案:

答案 0 :(得分:6)

首先让我们添加一个类型签名。从问题来看,似乎以下类型签名是合适的: zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]

这需要3个列表(包含可能不同类型的对象),然后生成三元组列表。

您处理空列表案例: zipThree [] [] [] = []

然后出现问题。正如评论中所述,您可以看到列表具有不同的长度,但会提供不同类型的输出。

我将为每行旁边的类型添加注释,以便您可以看到:

zipThree [] [] [x] = [x] :: [c]
zipThree [] [x] [] = [x] :: [b]
zipThree [x] [] [] = [x] :: [a]

这些不适合具有[(a, b, c)]类型的其他两种情况。

您在评论中提到,您只需假设长度相同,因此只需删除这些情况就足够了。这给出了:

zipThree [] [] [] = []
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs

为您提供的输入([(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')])提供了正确的输出([1, 2, 3] [4, 5, 6] ['a', 'b', 'c'])。

这个功能当然会在列表长度不同的输入上失败。停止直接错误并允许您处理问题的一种方法是将结果包装在Maybe。

首先我们需要将类型更改为: zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]

Maybe数据类型可以是包含在Just aNothing中的值。

对于空列表,我们只想给出空列表: zipThree [] [] [] = Just []

当然,您可能认为下一个案例应该是: zipThree (x:xs) (y:ys) (z:zs) = Just $ (x, y, z) : zipThree xs ys zs

但这不起作用。不要忘记zipThree xs ys zs现在有Maybe [(a, b, c)]类型,而(x, y, z)有类型(a, b, c),所以我们无法将其添加到列表中。

我们需要做的是检查zipThree xs ys zs的结果,如果它在递归期间的某个时刻失败,那么它将是Nothing所以我们只想再次传递Nothing 。如果成功并给了我们Just as,那么我们想要将(x, y, z)添加到该列表中。我们可以使用case of检查哪个案例相关:

zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
    Nothing -> Nothing
    Just as -> Just $ (x, y, z) : as

如果在递归过程中某些列表为空而其他列表不为空,我们将知道列表长度不同。这与我们目前[] [] [](x:xs) (y:ys) (z:zs)的模式都不匹配,因此我们需要最后一个捕获所有案例,以便向我们提供Nothing并防止错误:

zipThree _ _ _ = Nothing

这给出了最终定义:

zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]
zipThree [] [] [] = Just []
zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
    Nothing -> Nothing
    Just as -> Just $ (x, y, z) : as
zipThree _ _ _ = Nothing

示例的结果是:

zipThree [1, 2, 3] [4, 5, 6] ['a', 'b', 'c', 'd'] = Nothing

zipThree [1, 2, 3] [4, 5, 6] ['a', 'b', 'c'] = Just [(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]

希望这有帮助,请随时要求澄清:)

编辑:正如评论中所建议的那样,如果列表的长度不同,以下定义将会停止:

zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
zipThree _ _ _ = []

zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
    Nothing -> Just [(x, y, z)] -- Change is here
    Just as -> Just $ (x, y, z) : as
zipThree _ _ _ = Nothing

P.S。感谢那个在编辑中添加了遗失的人。

答案 1 :(得分:0)

ZipList模块中定义了这种Control.Applicative类型,实际上完全符合这项工作。

ZipList类型来自List类型,如

newtype ZipList a = ZipList { getZipList :: [a] }
                    deriving ( Show, Eq, Ord, Read, Functor, Foldable
                             , Generic, Generic1)

与普通List不同,它的Applicative实例不适用于组合,而是对齐相应的元素(如压缩)。因此名称为ZipList。这是Applicative

ZipList个实例
instance Applicative ZipList where
    pure x = ZipList (repeat x)
    liftA2 f (ZipList xs) (ZipList ys) = ZipList (zipWith f xs ys)

zipList的优点是我们无限期地链接许多列表以进行压缩。因此,当zipWith7不足时,您仍然可以使用ZipList。所以这是代码;

import Control.Applicative

zip'mAll :: [Int] -> [Int] -> String -> [(Int,Int,Char)]
zip'mAll xs ys cs = getZipList $ (,,) <$> ZipList xs <*> ZipList ys <*> ZipList cs

*Main> zip'mAll [1,2,3] [4,5,6] "abc"
[(1,4,'a'),(2,5,'b'),(3,6,'c')]

答案 2 :(得分:0)

首先,我们需要一个类型签名,如James Burton所述,他也列出了一个合适的签名:

zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]

基本上,这种类型的签名表示,给定三个任何类型a,b或c的列表,应生成一个类型为(a,b,c)的三值元组列表。

如果我们忽略处理无效情况(空列表,可变长度列表)的需要,我们接下来需要实现一个有效的情况,从给定的列表中产生正确的元组。你的陈述

zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs 

有效。因此,到目前为止,我们有:

zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs 

当您为无效列表引入案例时,会出现问题:

zipThree [] [] [x] = [x]
zipThree [] [x] [] = [x]
zipThree [x] [] [] = [x]

当其中一个案例匹配时,由于类型为[x],尝试绑定的类型无效,其中类型为(x,y,z)。

在再次递归访问函数之前,您可以详尽地尝试匹配基本案例。但是,您也可以简单地声明案例

zipThree _ _ _ = []

之后,将以无效输入结束递归。

完全放下这个,我们留下:

zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
zipThree _ _ _                = []


这个实现的好处在于当任何列表为空时递归结束,从而阻止不均匀列表的缩短,例如。

zipThree [1, 2, 3] [4, 5, 6] [7, 8]

会产生

[(1, 4, 7), (2, 5, 8)]
祝你好运!