0

I'm trying to write a function which adds single characters from a string to a list of strings, for instance

combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]

I've tried this:

combine :: String -> [String] -> [String]
combine (y:ys) (x:xs) =
[x:y, combine ys xs]
3
  • What should happen if the string and the list of strings differ in length? Commented Oct 18, 2013 at 16:12
  • In what way does your current code not work? Commented Oct 18, 2013 at 16:24
  • It should be ... = x ++ [y] : combine ys xs Commented Oct 18, 2013 at 16:27

3 Answers 3

2

A simple one would be

 combine :: [Char] -> [String] -> [String]
 combine [] _ = []
 combine _ [] = []
 combine (c:cs) (x:xs) = x ++ [c] : combine cs xs

Or even more simply using zipWith

 combine :: [Char] -> [String] -> [String]
 combine = zipWith (\c x -> x ++ [c])

I had to do a bit extra to get this to work. I'll break it down for you.

First, I specified the type of the function as [Char] -> [String] -> [String]. I could have used String for the first argument, but what you're operating on conceptually is a list of characters and a list of strings, not a string and a list of strings.

Next, I had to specify the edge cases for this function. What happens when either argument is the empty list []? The easy answer is to just end the computation then, so we can write

combine [] _ = []
combine _ [] = []

Here the _ is matching anything, but throwing it away because it isn't used in the return value.

Next, for the actual body of the function We want to take the first character and the first string, then append that character to the end of the string:

combine (c:cs) (x:xs) = x ++ [c]

But this doesn't do anything with cs or xs, the rest of our lists (and won't even compile with the type signature above). We need to keep going, and since we're generating a list, this is normally done with the prepend operator :

combine (c:cs) (x:xs) = x ++ [c] : combine cs xs

However, this is such a common pattern that there is a helper function called zipWith that handles the edge cases for us. It's type signature is

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

It walks down both input lists simultaneously, passing the corresponding elements into the provided function. Since the function we want to apply is \c x -> x ++ [c] (turned into a lambda function), we can drop it in to zipWith as

combine cs xs = zipWith (\c x -> x ++ [c]) cs xs

But Haskell will let us drop arguments when possible, so we can eta reduce this to

combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])

And that's it!

Sign up to request clarification or add additional context in comments.

Comments

2

When you want to combine lists element by element, it is usually a zip you are looking at. In this case, you know exactly how you want to combine the elements – that makes it a zipWith.

zipWith takes a "combining function" and then creates a function that combines two lists using said combining function. Let's call your "combining" function append, because it adds a characters to the end of a string. You can define it like this:

append char string = string ++ [char]

Do you see how this works? For example,

append 'e' "nic" = "nice"

or

append '!' "Hello" = "Hello!"

Now that we have that, recall that zipWith takes a "combining function" and then creates a function that combines two lists using that function. So your function is then easily implemented as

combine = zipWith append

and it will do append on each of the elements in order in the lists you supply, like so:

combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]

Comments

0

You are close. There are a couple issues with what you have.

y has type Char, and x has type String which is an alias for [Char]. This means that you can add y to the top of a list with y : x, but you can't add y to the end of a list using the same : operator. Instead, you make y into a list and join the lists.

x ++ [y]

There must also be a base case, or this recursion will continue until it has no elements in either list and crash. In this case, we likely don't have anything we want to add.

combine [] [] = []

Finally, once we create the element y ++ [x] we want to add it to the top of the rest of the items we have computed. So we use : to cons it to our list.

combine :: String -> [String] -> [String]
combine [] [] = []
combine (x : xs) (y : ys) = (y ++ [x]) : (combine xs ys)

One note about this code, if there is ever a point where the number of characters in your string is different from the number of strings in you list, then this will crash. You can handle that case in a number of ways, bheklilr's answer addresses this.

kqr's answer also works perfectly and is probably the best one to use in practice.

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.