Reputation:
let's say i have a list like this:
["Questions", "that", "may", "already", "have", "your", "correct", "answer"]
and want to have this:
[("Questions","that"),("may","already"),("have","your"),("correct","answer")]
can this be done ? or is it a bad Haskell practice ?
Upvotes: 2
Views: 776
Reputation: 36391
A simple pattern matching could do the trick :
f [] = []
f (x:y:xs) = (x,y):f(xs)
It means that an empty list gives an empty list, and that a list of a least two elements returns you a list with a couple of these two elements and then application of the same reasoning with what follows...
Upvotes: 1
Reputation: 20405
Using chunk
from Data.List.Split
you can get the desired result of pairing every two consecutive items in a list, namely for the given list named by xs
,
import Data.List.Split
map (\ys -> (ys!!0, ys!!1)) $ chunk 2 xs
This solution assumes the given list has an even number of items.
Upvotes: 0
Reputation: 15163
For a simple method (that fails for a odd number of elements) you can use
combine :: [a] -> [(a, a)]
combine (x1:x2:xs) = (x1,x2):combine xs
combine (_:_) = error "Odd number of elements"
combine [] = []
Or you could use some complex method like in an other answer that I don't really want to understand.
More generic:
map2 :: (a -> a -> b) -> [a] -> [b]
map2 f (x1:x2:xs) = (f x1 x2) : map2 f xs
map2 _ (_:_) = error "Odd number of elements"
map2 _ [] = []
Upvotes: 4
Reputation: 77404
Here is one way to do it, with the help of a helper function that lets you drop every second element from your target list, and then just use zip
. This may not have your desired behavior when the list is of odd length since that's not yet defined in the question.
-- This is just from ghci
let my_list = ["Questions", "that", "may", "already", "have", "your", "correct", "answer"]
let dropEvery [] _ = []
let dropEvery list count = (take (count-1) list) ++ dropEvery (drop count list) count
zip (dropEvery my_list 2) $ dropEvery (tail my_list) 2
[("Questions","that"),("may","already"),("have","your"),("correct","answer")
The helper function is taken from question #6 from 99 Questions., where there are many other implementations of the same idea, probably many with better recursion optimization properties.
To understand dropEvery
, it's good to remember what take
and drop
each do. take k some_list
takes the first k
entries of some_list
. Meanwhile drop k some_list
drops the first k
entries.
If we want to drop every Nth element, it means we want to keep each run of (N-1) elements, then drop one, then do the same thing again until we are done.
The first part of dropEvery
does this: it take
s the first count-1
entries, which it will then concatenate to whatever it gets from the rest of the list.
After that, it says drop count
(forget about the N-1 you kept, and also the 1 (in the Nth spot) that you had wanted to drop all along) -- and after these are dropped, you can just recursively apply the same logic to whatever is leftover.
Using ++
in this manner can be quite expensive in Haskell, so from a performance point of view this is not so great, but it was one of the shorter implementations available at that 99 questions page.
Here's a function to do it all in one shot, which is maybe a bit more readable:
byTwos :: [a] -> [(a,a)]
byTwos [] = []
byTwos xs = zip firsts seconds
where enumerated = zip xs [1..]
firsts = [fst x | x <- enumerated, odd $ snd x]
seconds = [fst x | x <- enumerated, even $ snd x]
In this case, I started out by saying this problem will be easy to solve with zip
if I just already had the list of odd-indexed elements and the list of even-indexed elements. So let me just write that down, and then worry about getting them in some where
clause.
In the where
clause, I say first zip xs [1..]
which will make [("Questions", 1), ("that", 2), ...]
and so on.
Side note: recall that fst
takes the first element of a tuple, and snd
takes the second element.
Then firsts
says take the first element of all these values if the second element is odd -- these will serve as "firsts" in the final output tuples from zip
.
seconds
says do the same thing, but only if the second element is even -- these will serve as "seconds" in the final output tuples from zip
.
In case the list has odd length, firsts
will be one element longer than seconds
and so the final zip
means that the final element of the list will simply be dropped, and the result will be the same as though you called the function on the front of the list (all but final element).
Upvotes: 2