6

I'm trying to create a function that drops every n'th element from a string.

dropEvery :: String -> Int -> String
dropEvery str n = map (\(char, indx) -> if indx `mod` n /= 0 then char else ' ') (zip str [1..])

Right now it simply replaces every n'th element with a space, but what am I supposed to put after the "else" if I want it to return an "empty char". I understand that such a thing doesn't exist in Haskell so the question is - how am I supposed to tell Haskell to not return anything and just move on to the next char?

3 Answers 3

7

You can't do this with just map, by definition it can't change the length of the collection it's applied to. However, you can get this to work without too many changes by switching to concatMap. This function requires the your function that you're mapping return a list, then it concatenates all the results together. All you'd need to do is

dropEvery str n =
    concatMap (\(char, indx) -> if indx `mod` n /= 0 then [char] else []) (zip str [1..])
Sign up to request clarification or add additional context in comments.

2 Comments

I did attempt to put "[]" after "else", but of course ended up getting type errors. concatMap is a good idea.
@RaulŠpilev The problem comes from assuming that there is something called an "empty Char". This would be like an "empty Int" or an "empty Bool", you can have an Int that is 0, but not one that doesn't have a value at all (not counting undefined). In order to have emptiness, you need a container. Strings are containers, since they're simply a list of Chars, so by having a function (Char, Int) -> [Char], you can use concatMap over a [(Char, Int)] to get out a [Char]`.
5

map preserves the structure of the list, while your operations modifies it by removing elements. This means you can't use map, but you can use mapMaybe which allows you to provide a function which returns Nothing for elements you want to remove from the output:

import Data.Maybe (mapMaybe)
dropEvery str n = mapMaybe (\(char, indx) -> if indx `mod` n /= 0 then Just(char) else Nothing) (zip str [1..])

2 Comments

I'd have to run some tests to be sure but this might be a more efficient solution than mine
This is pretty much exactly what I was looking for. I was thinking about using "Nothing" and "Just (char)", but wasn't sure how without getting "Couldn't match expected type" errors."mapMaybe" solves this. Also, now I understand how the map function works much better.Thanks.
3

You can do this without mod. I'm going to flip the order of the arguments to make this more idiomatic.

dropEvery :: Int -> [a] -> [a]
dropEvery n xs = map fst . filter ((/= n) . snd) $ zip xs (cycle [1..n])

If speed is critical, it would likely be most efficient to use this technique with explicit recursion or foldr. Something like this:

dropEvery n xs = foldr go (`seq` []) xs n where
  go _ r 1 = r n
  go x r k = x : r (k - 1)

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.