2010-06-10 54 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