user2878641
user2878641

Reputation:

List to tuple in Haskell

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

Answers (4)

Jean-Baptiste Yunès
Jean-Baptiste Yunès

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

elm
elm

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

Johannes Kuhn
Johannes Kuhn

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 [] = []

Live demo

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

ely
ely

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 takes 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

Related Questions