Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming a List of 2-Tuples in Haskell

Tags:

haskell

(Background: Trying to learn Haskell, very new to functional programming. Typically used to Python.)

Suppose I have a list of 2-tuples, a histogram:

let h = [(1,2),(3,5),(4,6),(5,3),(6,7),(7,4),(8,6),(9,1)]

In imperative terms, I want to change the second term of each pair to be the sum of all the previous second pairs. In Python, the following (admittedly complex) list comprehension could do it:

[(p[0], sum( [p[1] for p in histogram[:i+1]] ))
    for i, p in enumerate(histogram)]

assuming histogram refers to a list of 2-tuples like h above.

Here's what I have so far in Haskell:

zip [fst p | p <- h] (scanl1 (+) [snd k | k <- h])

This works, but I wonder:

  • Is this reading through the list once, or twice?
  • Can it be expressed better? (I expect so.)

In case it isn't clear, this is the expected output for the above:

[(1,2),(3,7),(4,13),(5,16),(6,23),(7,27),(8,33),(9,34)]
like image 828
Two-Bit Alchemist Avatar asked Apr 03 '14 05:04

Two-Bit Alchemist


People also ask

How do I convert a list to tuple Haskell?

In a general way, you can't. Each size of tuple is a distinct type, whereas lists of any length are a single type. Thus, there's no good way to write a function that takes a list and returns a tuple of the same length--it wouldn't have a well-defined return type.

How do you make a list of pairs in Haskell?

First, we write a function that takes an element and a list and pairs that element with every list member. Then, to make all possible pairs from a given list, we can pair the head element with all the remaining elements in the list. We then remove the head element and repeat the above process of the remaining list.


2 Answers

You could use this function

accumulate = scanl1 step
        where step (_,acc) (p1,p2) = (p1,acc+p2)

Here is the result on your sample data:

*Main> accumulate h
[(1,2),(3,7),(4,13),(5,16),(6,23),(7,27),(8,33),(9,34)]
like image 179
Lee Duhem Avatar answered Sep 20 '22 17:09

Lee Duhem


If you're new to Haskell this might be a little too early, but lens offers a nice succinct way:

> scanl1Of (traverse . _2) (+) h
[(1,2),(3,7),(4,13),(5,16),(6,23),(7,27),(8,33),(9,34)]

You can easily accumulate only the first one by switching to _1:

> scanl1Of (traverse . _1) (+) h
[(1,2),(4,5),(8,6),(13,3),(19,7),(26,4),(34,6),(43,1)]

Or accumulate all values as a sort of nested list:

> scanl1Of (traverse . both) (+) h
[(1,3),(6,11),(15,21),(26,29),(35,42),(49,53),(61,67),(76,77)]
like image 38
Markus1189 Avatar answered Sep 19 '22 17:09

Markus1189