tags:

views:

242

answers:

7

I'm new in haskell and I'm looking for some standard functions to work with lists by indexes.

My exact problem is that i want to remove 3 elements after every 5. If its not clear enough here is illustration:

OOOOOXXXOOOOOXXX...

I know how to write huge function with many parameters, but is there any clever way to do this?

+4  A: 

You can count your elements easily:

strip' (x:xs) n | n == 7 = strip' xs 0
                | n >= 5 = strip' xs (n+1)
                | n < 5 = x : strip' xs (n+1)
strip l = strip' l 0

Though open-coding looks shorter:

strip (a:b:c:d:e:_:_:_:xs) = a:b:c:d:e:strip xs
dottedmag
I like the second version, but it's not generalizabe. Which is unfortunate, because it's possibly the clearest solution here.
Chris Lutz
I like the pattern-matching solution. Simple as it can possibly be. Though you might need some more to match lists of less than eight elements. Can never remember how that works.
Samir Talwar
A: 

Here is my solution:

remElements step num=rem' step num
    where rem' _ _ []=[]
          rem' s n (x:xs)
              |s>0 = x:rem' (s-1) num xs
              |n==0 = x:rem' (step-1) num xs
              |otherwise= rem' 0 (n-1) xs

example:

*Main> remElements 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]
hiena
+1  A: 

the take and drop functions may be able to help you here.

drop, take :: Int -> [a] -> [a]

from these we could construct a function to do one step.

takeNdropM :: Int -> Int -> [a] -> ([a], [a])
takeNdropM n m list = (take n list, drop (n+m) list)

and then we can use this to reduce our problem

takeEveryNafterEveryM :: Int -> Int -> [a] -> [a]
takeEveryNafterEveryM n m [] = []
takeEveryNafterEveryM n m list = taken ++ takeEveryNafterEveryM n m rest
    where
        (taken, rest) = takeNdropM n m list

*Main> takeEveryNafterEveryM 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]


since this is not a primitive form of recursion, it is harder to express this as a simple fold.

so a new folding function could be defined to fit your needs

splitReduce :: ([a] -> ([a], [a])) -> [a] -> [a]
splitReduce f []   = []
splitReduce f list = left ++ splitReduce f right
    where
        (left, right) = f list

then the definition of takeEveryNafterEveryM is simply

takeEveryNafterEveryM2 n m = splitReduce (takeNdropM 5 3)
barkmadley
+8  A: 

Two completely different approaches

  1. You can use List.splitAt together with drop:

    import Data.List (splitAt)
    f :: [a] -> [a]
    f [] = []
    f xs = let (h, t) = splitAt 5 xs in h ++ f (drop 3 t)
    

    Now f [1..12] yields [1,2,3,4,5,9,10,11,12]. Note that this function can be expressed more elegantly using uncurry and Control.Arrow.second:

    import Data.List (splitAt)
    import Control.Arrow (second)
    f :: [a] -> [a]
    f [] = []
    f xs = uncurry (++) $ second (f . drop 3) $ splitAt 5 xs
    

    Since we're using Control.Arrow anyway, we can opt to drop splitAt and instead call in the help of Control.Arrow.(&&&), combined with take:

    import Control.Arrow ((&&&))
    f :: [a] -> [a]
    f [] = []
    f xs = uncurry (++) $ (take 5 &&& (f . drop 8)) xs
    

    But now it's clear that an even shorter solution is the following:

    f :: [a] -> [a] 
    f [] = []
    f xs = take 5 xs ++ (f . drop 8) xs
    

    As Chris Lutz notes, this solution can then be generalized as follows:

    nofm :: Int -> Int -> [a] -> [a]
    nofm _ _ [] = []
    nofm n m xs = take n xs ++ (nofm n m . drop m) xs
    

    Now nofm 5 8 yields the required function. Note that a solution with splitAt may still be more efficient!

  2. Apply some mathematics using map, snd, filter, mod and zip:

    f :: [a] -> [a]
    f = map snd . filter (\(i, _) -> i `mod` 8 < (5 :: Int)) . zip [0..]
    

    The idea here is that we pair each element in the list with its index, a natural number i. We then remove those elements for which i % 8 > 4. The general version of this solution is:

    nofm :: Int -> Int -> [a] -> [a]
    nofm n m = map snd . filter (\(i, _) -> i `mod` m < n) . zip [0..]
    
Stephan202
+1 solution two is easily generalizable to `NofM :: Int -> Int -> [a] -> [a]` taking two arguments to put in place of 8 and 5, respectively. It also has the advantage of being quite clearly named.
Chris Lutz
+1  A: 

This is my solution. It's a lot like @barkmadley's answer, using only take and drop, but with less clutter in my opinion:

takedrop :: Int -> Int -> [a] -> [a]
takedrop _ _ [] = []
takedrop n m l  = take n l ++ takedrop n m (drop (n + m) l)

Not sure if it'll win any awards for speed or cleverness, but I think it's pretty clear and concise, and it certainly works:

*Main> takedrop 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]
*Main>
Chris Lutz
+1  A: 
myRemove = map snd . filter fst . zip (cycle $ (replicate 5 True) ++ (replicate 3 False))
Long
+4  A: 

Since nobody did a version with "unfoldr", here is my take:

drop3after5 lst = concat $ unfoldr chunk lst
  where
    chunk [] = Nothing
    chunk lst = Just (take 5 lst, drop (5+3) lst)

Seems to be the shortest thus far

ADEpt
Concat function is needed to final result.
qba
Yes, indeed, thank you
ADEpt