【问题标题】:Combining three lists into a list of 3-tuples in Haskell在 Haskell 中将三个列表组合成一个三元组列表
【发布时间】:2017-09-18 01:34:30
【问题描述】:

我正在尝试编写一个函数,该函数将三个列表作为参数,并从每个列表中连续创建一个包含三元组的列表。

给我的例子是这样的: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)

【问题讨论】:

  • 您期望zipThree [1, 2, 3] [4, 5, 6] [‘a’, ‘b’, ‘c’, 'd'] 的结果是什么?长度为 3 或 4 的列表?你的实现是做什么的?有意义吗?
  • 我们假设每个列表的长度相同,因此不处理您提到的情况
  • @Vic 但是您有不同长度列表的明确案例,因此您正在处理它。问题是:您是否按照预期的方式处理它? (听起来不,如果你不打算处理它!)
  • 我的印象是这是一个与作业相关的问题,这很好,但是对于以后来这个问题的其他人来说,这个功能是内置的,称为zip3。一般情况下,当您想要组合 n 列表时,可以使用 ZipList 应用函子来解决。

标签: list haskell tuples


【解决方案1】:

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

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

您可以很好地处理空列表的情况: zipThree [] [] [] = []

然后问题就出现了。如 cmets 中所述,您有不同长度但给出不同类型输出的列表的情况。

我将在每一行旁边注释类型,以便您查看:

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

这些不适合其他两个类型为 [(a, b, c)] 的案例。

您在 cmets 中提到,您只是假设长度相同,因此只需删除这些案例就足够了。这给出了:

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

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

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

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

Maybe 数据类型可以是包装在 Just so 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')].

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

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

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

附:感谢在编辑中添加缺少的 Just 的人。

【讨论】:

  • 我认为提供一个在不均匀列表上停止的实现也是值得的,就像标准库中的zipWith3 那样。
  • @dfeuer 好建议,我已将定义添加到答案中。
【解决方案2】:

Control.Applicative 模块中定义了这个 ZipList 类型,实际上正是为这项工作考虑的。

ZipList 类型派生自List 类型之类的

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

与普通的Lists 不同,它的Applicative 实例不适用于组合,而是一对一地处理相应的元素,例如压缩。因此名称为ZipList。这是ZipListApplicative 实例

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')]

【讨论】:

    【解决方案3】:

    首先,我们需要一个类型签名,正如 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)]
    

    祝你好运!

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 2022-06-15
    相关资源
    最近更新 更多