在Haskell中使用替代元素合并两个列表

时间:2015-12-24 20:39:16

标签: haskell

我是新的Haskell并尝试练习我的Haskell技能。

我想实现一个合并两个列表的函数,以便每个列表中的项都可以替代。

["a", "b", "c"]
["1", "2", "3"]

合并后。

["a", "1", "b", "2", "c", "3"]

这是我的Haskell代码

mergeList::[String]->[String]->[String]
mergeList [] [] = []
mergeList  _ [] = []
mergeList [] _  = []
mergeList (x:xs) (y:ys) = x:y:mergeList xs ys

只要两个列表的长度相同,代码就可以正常工作。

但是我想对两个列表的长度进行一些错误检查。

如果两个列表的长度不同,那么我想报告一个错误。

mergeList::[String]->[String]->[String]
mergeList l r | length l /= length r = error "report an error"
              | otherwise = 

我如何完成其​​他部分?

5 个答案:

答案 0 :(得分:8)

不鼓励使用error。在Haskell中,我们喜欢为所有可能的输入定义函数,以便类型系统可以指导我们编写正确的程序。如果您的函数对于不同长度的列表无效,则一种方法是使用Maybe

mergeList :: [a] -> [a] -> Maybe [a]
mergeList [] [] = Just []
mergeList (x:xs) (y:ys) =
    case mergeList xs ys of
        Just merged -> Just (x:y:merged)
        Nothing     -> Nothing
mergeList _ _ = Nothing

有更简洁的方式来写这个,但我住在一楼附近。

现在,mergeList的用户需要定义在传递两种不同长度的列表时要执行的操作。

答案 1 :(得分:2)

最简单的方法可能只是事先进行检查,然后将主要工作路由到辅助功能:

mergeList::[String]->[String]->[String]
mergeList xs ys
    | length xs /= length ys = error "Lists must be the same length" 
    | otherwise go xs ys
    where
        go [] [] = []
        go  _ [] = []
        go [] _  = []
        go (x:xs) (y:ys) = x:y:go xs ys

检查在main函数中完成,如果列表的大小相同,则将它们交给执行主处理的内部函数。请注意go仅仅是您原来的功能;它只是在包装函数中内化。您可以使用比go更具描述性的名称;我试图保持简短。

自从我编写Haskell以来已经有一段时间了,所以我为任何语法问题道歉。

您还可以通过将签名更改为:

来概括该功能
mergeList::[a]->[a]->[a]

由于您没有在函数中执行任何特定于字符串的操作。

答案 2 :(得分:2)

如果其中一个列表为空,则在模式匹配中引发错误。 所以你会有

mergeList (x: xs) (y: ys) = x: y: mergeList xs ys
mergeList []      []      = []
mergeList _       _       = error "your error"  -- falling through to this pattern means that exactly one of the lists is empty

检查2个列表的长度是否相等有点多余,因为“长度”具有O(n)复杂度,这意味着您将遍历列表两次(在长度相等的情况下)。

正如luqui正确指出的那样,使用Maybe类型有更优雅的方法来解决问题。当你了解仿函数(并且Maybe是类Functor的一个实例)时,你可以写:

mergeList :: [a] -> [a] -> Maybe [a]
mergeList (x: xs) (y: ys) = fmap (\rest -> x: y: rest) $ mergeList xs ys
mergeList []      []      = Just []
mergeList _       _       = Nothing

答案 3 :(得分:1)

你的功能可能是这样的:

otherwise = reverse $ foldl (\a (x,y) -> x:y:a) [] (x `zip` y)

示例:

Prelude> let y = ["a", "b", "c"]
Prelude> let x = ["1", "2", "3"]
Prelude> reverse $ foldl (\a (x,y) -> x:y:a) [] (x `zip` y)
["a","1","b","2","c","3"]

或带有foldr的elliptic00解决方案,避免使用reverse

 otherwise = foldr ((x, y) a -> x:y:a) [] (x `zip` y) 

答案 4 :(得分:0)

luqui指出,我们希望在Haskell中保持安全,避免error。但是等到我们到达至少一个清单结束才产生任何东西之前就会抛弃任何懒惰的希望。一种选择是向调用者抛出错误处理。我们可以假装这样的列表:

import Data.Bifunctor
import Data.Bifunctor.Fix
import Data.Bifunctor.Tannen

data ListF l a = NilF | ConsF a l

instance Bifunctor ListF where
  bimap _ _ NilF = NilF
  bimap f g (ConsF a l) = ConsF (f a) (g l)

{-instance Bifoldable ListF where ...

instance Bitraversable ListF where ... -}

type List = Fix ListF

但我们可以添加错误处理:

type EList e = Fix (Tannen (Either e) ListF)

EList e ae类型的错误,NilFConsF的错误a和另一个EList e a。< / p> 然而,这种组合方法最终变得非常冗长,令人困惑,而且效率低下。实践中更好的方法是使用完全自定义的列表类型:

data EList' e a = Error e | Nil | Cons a (EList' e a)