Elegant way to mask-merge two strings in Haskell

Viewed 107

For example if we have two strings "abc" and "1234" i want result "abc4" (the first string masks second). (If we draw them vertical it's like a wave comes from left and tuch chars)

             "a"  "1"            "a"
 wave ->     "b"  "2"   result   "b" 
             "c"  "3"            "c"
                  "4"            "4"  

I start with this solution with Haskell

slice from to xs = take (to - from + 1) (drop from xs)
merge l1 l2 = if length l2 > length l1 
                then l1 ++ slice (length l1) (length l2) l2 
                else l1

Can you please provide some more elegant\compact solutions.

2 Answers

You just need a special merge function

> let merge [] ys = ys
|     merge xs [] = xs
|     merge (x:xs) (y:ys) = x : merge xs ys

or using drop

> let merge2 x y = x ++ drop (length x) y

You want kind of a "zipLongest", and transpose is kind of like that:

maskMerge1 :: [b] -> [b] -> [b]
maskMerge1 as bs  =  map head $ transpose [as,bs]
              -- or:
              --        head <$> transpose [as,bs]

This is pretty compact and elegant (with big thanks to @leftaroundabout for the comments!).

Looking from above,

    [ "abc" ,        [ ['a' ,'b' ,'c'     ] ,
      "1234" ]         ['1' ,'2' ,'3' ,'4'] ]
    ----------      --------------------------
                     [  "a1","b2","c3","4"  ]   -- transpose
    ----------      --------------------------
      "abc4"         [  'a' ,'b' ,'c' ,'4'  ]   -- map head

The code with length in the other answer also works, and even for an infinite x despite calling the dreaded length on it, but it will retain the whole of x in memory because of calling the length on it.

Related