views:

238

answers:

3

Is there a fast way to convert a flat list into a list of two-tuples such that a flat list like [1,2,3,4,5,6] becomes [{1,2},{3,4},{5,6}]?

This works, but it feels just plain WRONG:

tuples_from_flat_list(Target) ->
    TargetWithIndex = lists:zip(lists:seq(1, length(Target)), Target),
    {K, V} = lists:partition(fun({I, _}) -> I rem 2 == 1 end, TargetWithIndex),
    lists:zipwith(fun({_, X}, {_, Y}) -> {X, Y} end, K, V).
+2  A: 

This version is more efficient than 'straight' approach with lists concatenation proposed earlier:

combine(L) when length(L) rem 2 == 0 -> combine([], L).
combine(Acc, []) -> lists:reverse(Acc);
combine(Acc, [H1,H2|T])  -> combine([{H1, H2}|Acc], T).

To benchmark:

combine.erl

-module(combine).
-export([reverse/1, straight/1, test/2]).

test(F, L) -> {Micros, _} = timer:tc(?MODULE, F, [L]), Micros.

reverse(L) when length(L) rem 2 == 0 -> reverse([], L).                                  
straight(L) when length(L) rem 2 == 0 -> straight([], L).

reverse(Acc, []) -> lists:reverse(Acc);
reverse(Acc, [H1, H2 | T]) -> reverse([{H1, H2} | Acc], T).

straight(Acc, []) -> Acc;
straight(Acc, [H1, H2 | T]) -> straight(Acc ++ [{H1, H2}], T).

output:

130> combine:test(reverse, lists:seq(1,1000)).
34
131> combine:test(straight, lists:seq(1,1000)).
1772
zakovyrya
Yes, reversing and prefixing is always more efficient. But I think the "straight" approach works well as Lesson 1, followed quickly by the reverse/prefix as Lesson 2...!
Alan Moore
Why you count length of list when you doesn't make some more sensitive error msg. Your reverse/2 will raise same error message as your reverse/1. It is worthless work and slow down it a little bit.
Hynek -Pichi- Vychodil
@Hynek: I think it's better because it fails earlier. Also it adds clarity for one who is going to read the code
zakovyrya
@zakovyrya: Well, you will traversal this list three times.
Hynek -Pichi- Vychodil
@Hynek: I know that. Needless to say that it all depends on user's preferences and the actual data he is going to work with
zakovyrya
+3  A: 
tuples_from_flat_list(List) -> tuples_from_flat_list(List, []).

tuples_from_flat_list([], Result) -> lists:reverse(Result).
tuples_from_flat_list([X, Y|T], Acc) -> tuples_from_flat_list(T, [{X, Y}|Acc]).

It is best and fastest way.

Hynek -Pichi- Vychodil
In case of not even list and without guard, it fails at the very end of the list processing, when all the work is almost done:** exception error: no function clause matching test:tuples_from_flat_list([5], [{3,4},{1,2}])
zakovyrya
Yes, it is intentional.
Hynek -Pichi- Vychodil
+9  A: 

The shortest and most succinct approach:

pair_up([A, B | Tail]) ->
    [{A,B} | pair_up(Tail)];
pair_up([]) ->
    [].

Or the longer version carrying an accumulator, but still very idiomatic Erlang:

pair_up(List) ->
    pair_up(List, []).

pair_up([A, B | Tail], Acc) ->
    pair_up(Tail, [{A,B} | Acc]);
pair_up([], Acc) ->
    lists:reverse(Acc).

See this section in the erlang efficiency guide "Myth: Tail-recursive functions are MUCH faster than recursive functions".

As you will notice, both approaches will lead to a 'badarg' exit when called with an uneven length list. This is probably desirable from a fail-fast perspective.

Also read "Myth: '++' is always bad" to see why we build up the accumulator in reverse only to reverse it when done, instead of appending to the end of the list.

Christian