2010-06-10 57 views
11

有几次我发现自己希望在Haskell中使用zip,它将填充添加到较短的列表中,而不是截断较长的填充。这很容易写。 (Monoid对我的作品在这里,但你也可以只通过在要用于填充的元素。)试图定义zipPad3使用Haskell中的填充进行压缩

zipPad :: (Monoid a, Monoid b) => [a] -> [b] -> [(a, b)] 
zipPad xs [] = zip xs (repeat mempty) 
zipPad [] ys = zip (repeat mempty) ys 
zipPad (x:xs) (y:ys) = (x, y) : zipPad xs ys 

这种方法得到丑陋。我输入了以下内容,然后意识到,当然,这是行不通的:

zipPad3 :: (Monoid a, Monoid b, Monoid c) => [a] -> [b] -> [c] -> [(a, b, c)] 
zipPad3 xs [] [] = zip3 xs (repeat mempty) (repeat mempty) 
zipPad3 [] ys [] = zip3 (repeat mempty) ys (repeat mempty) 
zipPad3 [] [] zs = zip3 (repeat mempty) (repeat mempty) zs 
zipPad3 xs ys [] = zip3 xs ys (repeat mempty) 
zipPad3 xs [] zs = zip3 xs (repeat mempty) zs 
zipPad3 [] ys zs = zip3 (repeat mempty) ys zs 
zipPad3 (x:xs) (y:ys) (z:zs) = (x, y, z) : zipPad3 xs ys zs 

在这一点上我被骗了,只是用来length挑选最长的名单和垫等。

我可以忽略一个更优雅的方式来做到这一点,或者像zipPad3这样的东西已经定义在某个地方?

回答

19

自定义headtail函数(在我的示例中,名称为nextrest)如何?

import Data.Monoid 

zipPad :: (Monoid a, Monoid b) => [a] -> [b] -> [(a,b)] 
zipPad [] [] = [] 
zipPad xs ys = (next xs, next ys) : zipPad (rest xs) (rest ys) 

zipPad3 :: (Monoid a, Monoid b, Monoid c) => [a] -> [b] -> [c] -> [(a,b,c)] 
zipPad3 [] [] [] = [] 
zipPad3 xs ys zs = (next xs, next ys, next zs) : zipPad3 (rest xs) (rest ys) (rest zs) 

next :: (Monoid a) => [a] -> a 
next [] = mempty 
next xs = head xs 

rest :: (Monoid a) => [a] -> [a] 
rest [] = [] 
rest xs = tail xs 

测试片段:

instance Monoid Int where 
    mempty = 0 
    mappend = (+) 

main = do 
    print $ zipPad [1,2,3,4 :: Int] [1,2 :: Int] 
    print $ zipPad3 [1,2,3,4 :: Int] [9 :: Int] [1,2 :: Int] 

其输出:

[(1,1),(2,2),(3,0),(4,0)] 
[(1,9,1),(2,0,2),(3,0,0),(4,0,0)] 
11

这种模式出现了不少。一个解决方案,我从Paul Chiusano了解到如下:

data OneOrBoth a b = OneL a | OneR b | Both a b 

class Align f where 
    align :: (OneOrBoth a b -> c) -> f a -> f b -> f c 

instance Align [] where 
    align f []  []  = [] 
    align f (x:xs) []  = f (OneL x) : align f xs [] 
    align f []  (y:ys) = f (OneR y) : align f [] ys 
    align f (x:xs) (y:ys) = f (Both x y) : align f xs ys 

liftAlign2 f a b = align t 
    where t (OneL l) = f l b 
     t (OneR r) = f a r 
     t (Both l r) = f l r 

zipPad a b = liftAlign2 (,) a b 

liftAlign3 f a b c xs ys = align t (zipPad a b xs ys) 
    where t (OneL (x,y)) = f x y c 
     t (OneR r)  = f a b r 
     t (Both (x,y) r) = f x y r 

zipPad3 a b c = liftAlign3 (,,) a b c 

在ghci中一个小测试:

*Main> zipPad3 ["foo", "bar", "baz"] [2, 4, 6, 8] [True, False] "" 0 False 
[("foo",2,True),("bar",4,False),("baz",6,False),("",8,False)] 
3

有些时候你希望能够应用不同的功能,无论是尾部,而不仅仅是倍供应mempty或手动零,以及:

zipWithTail :: (a -> a -> a) -> [a] -> [a] -> [a] 
zipWithTail f (a:as) (b:bs) = f a b : zipWithTails f as bs 
zipWithTail f [] bs = bs 
zipWithTail f as _ = as 

zipWithTails :: (a -> c) -> (b -> c) -> (a -> b -> c) -> [a] -> [b] -> [c] 
zipWithTails l r f (a:as) (b:bs) = f a b : zipWithTails l r f as bs 
zipWithTails _ r _ [] bs = fmap r bs 
zipWithTails l _ _ as _ = fmap l as 

我用的是前者,当我做这样的事情zipWithTail (+) 和前者当我需要做类似zipWithTail (*b) (a*) (\da db -> a*db+b*da)之类的事情时,因为前者可以比将缺省提供给函数更有效,后者稍微有点。但是,如果您只想制作一个更简洁的版本,您可以转向mapAccumL,但它没有更清晰,而且++可能很昂贵。

zipPad as bs = done $ mapAccumL go as bs 
    where go (a:as) b = (as,(a,b)) 
      go [] b = ([],(mempty,b)) 
      done (cs, both) = both ++ fmap (\x -> (x, mempty)) cs 
4

更简单的方法是使用Maybe。我会用爱德华的 来说明更一般的表述:

import Data.Maybe 
import Control.Applicative 

zipWithTails l r f as bs = catMaybes . takeWhile isJust $ 
    zipWith fMaybe (extend as) (extend bs) 
    where 
    extend xs = map Just xs ++ repeat Nothing 
    fMaybe a b = liftA2 f a b <|> fmap l a <|> fmap r b