14

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?

1
  • yes, g n m = map take m . takeWhile (not.null) . unfoldr (Just . splitAt (n+m)) and call it as g 3 5 "yourstring". import Data.List for the unfoldr. Commented Aug 17, 2014 at 13:49

8 Answers 8

18

Here is my take:

deleteAt idx xs = lft ++ rgt
  where (lft, (_:rgt)) = splitAt idx xs
Sign up to request clarification or add additional context in comments.

Comments

17

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..]
    

1 Comment

+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.
6

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
strip (a:b:c:d:e:xs) = a:b:c:d:e:[]
strip xs = xs

2 Comments

I like the second version, but it's not generalizabe. Which is unfortunate, because it's possibly the clearest solution here.
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.
5

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

1 Comment

Concat function is needed to final result.
2

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)

Comments

2

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> 

Comments

1

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]

Comments

1
myRemove = map snd . filter fst . zip (cycle $ (replicate 5 True) ++ (replicate 3 False))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.