Calculate input with atoms, erlang - erlang

I am trying to calculate this input:
evaluate({mul,{plus,{num,2},{num,3}},{num,4}}).
and get this as an answer:
20
But I cant get it working, here is my code:
evaluate(List) ->
[Res] = lists:foldl(fun evaluate/2, [], tuple_to_list(List)),
Res.
evaluate({num,X},Stack) -> [X|Stack];
evaluate(plus,[N1,N2|Stack])->[N1+N2|Stack];
evaluate(mul,[N1,N2|Stack])->[N1*N2|Stack];
evaluate([{Optr, Num1, Num2}],Stack) ->
evaluate(Num1,Stack),evaluate(Num2,Stack),evaluate(Optr,Stack).
Can you point out my mistakes and correct me, thank you.

First, define your syntax:
{num,Value} for a number,
{plus,Term1,Term2} for an addition,
{mul,Term1,Term2} for a multiplication,
and so on.
Second, solve individual cases:
evaluate({num,Value}) -> Value;
evaluate({plus,Term1,Term2}) -> evaluate(Term1) + evaluate(Term2);
evaluate({mul,Term1,Term2}) -> evaluate(Term1) * evaluate(Term2).
in the shell:
1> E = fun E({num,Value}) -> Value;
1> E({plus,Term1,Term2}) -> E(Term1) + E(Term2);
1> E({mul,Term1,Term2}) -> E(Term1) * E(Term2)
1> end.
#Fun<erl_eval.30.90072148>
2> E({mul,{plus,{num,2},{num,3}},{num,4}}).
20
3>
Although I used a lot the reverse polish notation, it does not apply in your example. The input should be (for your example) [ 2, 3, plus, 4, mul] and it can be solved like this:
evaluate(plus,[A,B|Rest]) -> [A+B|Rest];
% pop operands,perform the operation and push the result
evaluate(mul,[A,B|Rest]) -> [A*B|Rest];
evaluate(A,Acc) when is_number(A) -> [A|Acc]. % push operand on the stack
evaluate(Exp) -> [Res] = lists:foldl(evaluate/2,[],Exp), Res.
in the shell
22> E = fun E(plus,[A,B|Rest]) -> [A+B|Rest];
22> E(mul,[A,B|Rest]) -> [A*B|Rest];
22> E(A,Acc) -> [A|Acc]
22> end.
#Fun<erl_eval.36.90072148>
23> Eval = fun(Exp) -> [Res] = lists:foldl(E,[],Exp), Res end.
#Fun<erl_eval.6.90072148>
24> Eval([2,3,plus,4,mul]).
20

Related

Erlang inference

The following source doesn't compile because Stopover is unbound.
I'm new to Erlang, how can I rewrite it?
-module(distances).
-export([ path/2 ]).
path( madrid, paris ) ->
{ km, 1049 };
path( paris, moscou ) ->
{ km, 2482 };
path( moscou, berlin ) ->
{ km, 1603 };
path( From, To ) ->
path( From, Stopover ) + path( Stopover, To ).
The usage of this module maybe:
path( madrid, moscou ).
And the epected answer should be { km, 3531}.
The following source doesn't compile because Stopover is unbound.
I'm new to Erlang, how can I rewrite it?
Look at this code:
-module(a).
-compile(export_all).
do_stuff() ->
Stopover.
Here's what happens when I try to compile it:
a.erl:5: variable 'Stopover' is unbound
The variable Stopover was never assigned a value, so erlang has no idea what should be returned by the function do_stuff(). You are doing something similar here:
path( From, Stopover ) + path( Stopover, To ).
The variables From and To are parameter variables for the function path(), and when path() is called, e.g. path(madrid, moscow), then madrid will be assigned to the variable From, and moscow will be assigned to the variable To. Note, however, that nowhere do you assign any value to the variable Stopover.
You need to redefine path() to look like this:
path(From, To, Stopover) ->
Next, you should try to see if adding tuples actually works:
2> {km, 5} + {km, 3}.
** exception error: an error occurred when evaluating an arithmetic expression
in operator +/2
called as {km,5} + {km,3}
3>
Nope!
What you need to do is use pattern matching to extract the distance, an integer, from each tuple, then add the two integers:
{km, Distance1} = path( From, Stopover ),
... = path(Stopover, To),
{km, Distance1 + Distance2}.
This question is already answered by #7stud, and I was wondering how to implement such a path search in erlang. Here is a possible solution:
-module(distances).
-export([ path/2,getTowns/0,start/1, stop/0 ]).
path(From,To) ->
Paths = getPath(),
path(From,To,maps:get(orderedTuple(From,To), Paths, not_found),Paths).
% distanceServer in charge to keep the liste of known distances
% server interfaces
start(Towns) ->
{ok,List} = file:consult(Towns),
Paths = lists:foldl(fun({A,B,D},Acc) -> maps:put(orderedTuple(A,B), D, Acc) end,#{},List),
start(Paths,distance_server).
stop() ->
distance_server ! stop.
getTowns() ->
K = maps:keys(getPath()),
L = lists:usort(lists:flatten([[A,B] || {A,B} <- K])),
io:format("list of towns :~n~p~n~n",[L]).
getPath() ->
distance_server ! {getPath,self()},
receive
Path -> Path
end.
% server fuctions
start(Paths,Server) ->
Pid = spawn(fun() -> distanceServer(Paths) end),
register(Server, Pid).
distanceServer(Path) ->
receive
stop -> stop;
{getPath,From} ->
From ! Path,
distanceServer(Path)
end.
% Searching path
path(From,To,not_found,Paths) -> % if not in the known list, seach for the shortest path
{KM,P} = searchBestPath({0,[From]},To,maps:keys(Paths),{no_dist,no_path}),
case P of
no_path -> not_found;
_ -> {lists:reverse(P),KM}
end;
path(From,To,KM,_) -> % else give the result. Assumption: the known path contains always the best one.
{[From,To],KM}.
searchBestPath({N,[To|_]}=Path,To,_,{BestD,_}) when N < BestD -> Path; % keep the new path if it is better
searchBestPath({N,_},_,_,{BestD,_}=Best) when N >= BestD -> Best; % cancel search if the path so far is longer or equal to the best found
searchBestPath({D,[H|_]=PathSoFar},To,Remaining,Best) ->
Next = [remove(H,{A,B}) || {A,B} <- Remaining, (A =:= H) orelse (B =:= H)], % list of all possible next steps
case Next of
[] -> Best;
Next -> lists:foldl(
fun(X,Acc) ->
{_,ND} = path(H,X), % will always match
R = Remaining -- [orderedTuple(H,X)], % necessary to avoid possible infinite loop in the first search
searchBestPath({D+ND,[X|PathSoFar]},To,R,Acc) % evaluate path for all possible next steps
end,
Best,Next)
end.
% helpers
orderedTuple(A,B) when B > A -> {A,B};
orderedTuple(A,B) -> {B,A}.
remove(X,{X,B}) -> B;
remove(X,{A,X}) -> A.
it uses an external file to define the "known distances", I have used this one for test:
{paris,lyon,465}.
{lyon,marseille,314}.
{marseille,nice,198}.
{marseille,toulouse,404}.
{toulouse,bordeaux,244}.
{bordeaux,paris,568}.
{bordeaux,nantes,347}.
{nantes,paris,385}.
{paris,lille,225}.
{paris,strasbourg,491}.
{lille,strasbourg,525}.
{lille,bruxelles,120}.
{rennes,brest,244}.
{rennes,paris,351}.
{rennes,nantes,113}.
and the result in the shell:
1> c(distances).
{ok,distances}
2> distances:start("distances.txt").
true
3> distances:getTowns().
list of towns :
[bordeaux,brest,bruxelles,lille,lyon,marseille,nantes,nice,paris,rennes,
strasbourg,toulouse]
ok
4> distances:path(bordeaux,bruxelles).
{[bordeaux,paris,lille,bruxelles],913}
5> distances:path(nice,bruxelles).
{[nice,marseille,lyon,paris,lille,bruxelles],1322}
6> distances:path(moscou,paris).
not_found
7> distances:stop().
stop
8>
next step could be to increase the list of known distances each time a new request is done.

Erlang repetition string in string

I have a string:
"abc abc abc abc"
How do I calculate the number of "abc" repetitions?
If you are looking for practical and efficient implementation which will scale well for even longer substrings you can use binary:matches/2,3 which is using Boyer–Moore string search algorithm (and Aho-Corasic for multiple substrings). It obviously works only for ASCII or Latin1 strings.
repeats(L, S) -> length(binary:matches(list_to_binary(L), list_to_binary(S))).
If it is for education purposes, you can write your own less efficient version for lists of any kind. If you know substring in compile time you can use very simple and not so much bad in performance:
-define(SUBSTR, "abc").
repeats(L) -> repeats(L, 0).
repeats(?SUBSTR ++ L, N) -> repeats(L, N+1);
repeats([_|L] , N) -> repeats(L, N);
repeats([] , N) -> N.
If you don't know substring you can write a little bit more complicated and less efficient
repeats(L, S) -> repeats(L, S, 0).
repeats([], _, N) -> N;
repeats(L, S, N) ->
case prefix(L, S) of
{found, L2} -> repeats( L2, S, N+1);
nope -> repeats(tl(L), S, N)
end.
prefix([H|T], [H|S]) -> prefix(T, S);
prefix( L, [ ]) -> {found, L};
prefix( _, _ ) -> nope.
And you, of course, can try write some more sophisticated variant as simplified Boyer–Moore for lists.
1> F = fun
F([],_,_,N) -> N;
F(L,P,S,N) ->
case string:sub_string(L,1,S) == P of
true -> F(tl(string:sub_string(L,S,length(L))),P,S,N+1);
_ -> F(tl(L),P,S,N)
end
end.
#Fun<erl_eval.28.106461118>
2> Find = fun(L,P) -> F(L,P,length(P),0) end.
#Fun<erl_eval.12.106461118>
3> Find("abc abc abc abc","abc").
4
4>
this works if defined in a module, or in the shell but only with the R17.
length(lists:filter(fun(X) -> X=="abc" end, string:tokens("abc abc abc abc", " "))).

How to collect frequencies of characters using a list of tuples {char,freq} in Erlang

I am supposed to collect frequencies of characters.
freq(Sample) -> freq(Sample,[]).
freq([],Freq) ->
Freq;
freq([Char|Rest],Freq)->
freq(Rest,[{Char,1}|Freq]).
This function does not work in the right way. If the input is "foo", then the output will be
[{f,1},{o,1},{o,1}].
But I wished to have the output like
[{f,1},{o,2}].
I can't manage to modify element in a tulpe. Can anyone help me out of this and show me how it can be fixed?
a one line solution :o)
% generate a random list
L = [random:uniform(26)+$a-1 || _ <- lists:seq(1,1000)].
% collect frequency
lists:foldl(fun(X,[{[X],I}|Q]) -> [{[X],I+1}|Q] ; (X,Acc) -> [{[X],1}|Acc] end , [], lists:sort(L)).
in action
1> lists:foldl(fun(X,[{[X],I}|Q]) -> [{[X],I+1}|Q] ; (X,Acc) -> [{[X],1}|Acc] end , [], lists:sort("foo")).
[{"o",2},{"f",1}]
quite fast with short list, but the execution time increase a lot with long list (on my PC, it needs 6.5s for a 1 000 000 character text) .
in comparison, with the same 1 000 000 character text Ricardo solution needs 5 sec
I will try another version using ets.
By far the easiest way is to use an orddict to store the value as it already comes with an update_counter function and returns the value in a (sorted) list.
freq(Text) ->
lists:foldl(fun (C, D) -> orddict:update_counter(C, 1, D) end, orddict:new(), Text).
Try with something like this:
freq(Text) ->
CharsDictionary = lists:foldl(fun(Char, Acc) -> dict:update_counter(Char, 1, Acc) end, dict:new(), Text),
dict:fold(fun(Char, Frequency, Acc) -> [{Char, Frequency} | Acc] end, [], CharsDictionary).
The first line creates a dictionary that uses the char as key and the frequency as value (dict:update_counter).
The second line converts the dictionary in the list that you need.
Using pattern matching and proplists.
-module(freq).
-export([char_freq/1]).
-spec char_freq(string()) -> [tuple()].
char_freq(L) -> char_freq(L, []).
char_freq([], PL) -> PL;
char_freq([H|T], PL) ->
case proplists:get_value([H], PL) of
undefined ->
char_freq(T, [{[H],1}|PL]);
N ->
L = proplists:delete([H], PL),
char_freq(T, [{[H],N+1}|L])
end.
Test
1> freq:char_freq("abacabz").
[{"z",1},{"b",2},{"a",3},{"c",1}]
L = [list_to_atom(X) || X <- Str].
D = lists:foldl(fun({Char, _}, Acc) -> dict:update_counter(Char, 1, Acc) end, dict:new(), L).
dict:to_list(D).

Erlang counter expression equivalent to C++, while loop?

Is there an equivalent expression for the increment/decrement operator e.g. counter++?
I also wonder how to properly do this?
-module(whileloop).
-export([call/0, while_loop/2]).
call() ->
while_loop(10,0).
while_loop(Var,Counter) ->
case Var =:= Counter of
false ->
Counter += 1,
whileloop(Var);
end.
edit:
-module(whileloop).
-export([call/0, while_loop/2]).
call() ->
while_loop(10,0).
while_loop(Var, Counter) ->
case Var =:= Counter of
false ->
while_loop(Var,Counter + 1)
end.
The meaning of C += 1 is to modify the value of C. It is a non sense in Erlang since it can only give the following result:
1> C = C+1.
* 1: variable 'C' is unbound
C = 1.
1
3> C = C+1.
** exception error: no match of right hand side value 2
Keep in mind that "A = B" does not mean assigns the value of B to A, but "pattern match" A against B,
if A is unbound then it will bind to A the value of B;
if A =:= B nothing is done, the process continue;
if A =/= B then the process crashes.
So yes, if you want to have a counter, or any information that change, you must use a state variable which is passed as argument of a recursive loop. From this point of view, your last code is correct, but lets follow what happens when you call "call()" in the shell.
first it calls, in the same process - the shell - the function while_loop(10,0).
10 is not equal to 0 so it calls immediately while_loop(10,1).
10 is not equal to 1 so it calls immediately while_loop(10,2).
and so on until it calls while_loop(10,10). Now 10 =:= 10 is true, and this result does not match any clause of the case, so you get an error and the process crash.
As your code does not contain any message receive and simply loop an loop until it crashes, thhe wole process takes only a few micro seconds, so it looks like it fails immediately.
Depending on what you expect, you can imagine several types of counter, here are 2 examples:
-module(counter).
-compile(export_all).
% one counter that help you to count some events
% interface
start_c1(End) when is_integer(End) ->
spawn(?MODULE,counter1,[End]).
start_link_c1(End) when is_integer(End) ->
spawn_link(?MODULE,counter1,[End]).
inc_c1(Pid) when is_pid(Pid) ->
Ref = make_ref(),
Pid ! {inc,self(),Ref},
receive
{Ref,done} -> done;
{Ref,V} -> V
after 1000 ->
{error,no_response}
end.
value_c1(Pid) when is_pid(Pid) ->
Ref = make_ref(),
Pid ! {get_value,self(),Ref},
receive
{Ref,V} -> V
after 1000 ->
{error,no_response}
end.
stop_c1(Pid) when is_pid(Pid) ->
Pid ! stop.
% the counter
counter1(End) -> counter1_loop(End,0).
counter1_loop(End,V) ->
receive
{inc,Pid,Ref} when V =/= done ->
NewV = case V+1 of
End -> done;
Nv -> Nv
end,
Pid ! {Ref,NewV},
counter1_loop(End,NewV);
{inc,Pid,Ref} ->
Pid ! {Ref,done},
counter1_loop(End,done);
{get_value,Pid,Ref} ->
Pid ! {Ref,V},
counter1_loop(End,V);
stop ->
ok
end.
% One kind of timeout that execute something after a while -
% note it exists a similar one in the library
start_after(T,M,F,A) when is_integer(T), is_list(A) ->
Ref = make_ref(),
{Ref,spawn(?MODULE,after_receive,[T,M,F,A,self(),Ref])}.
cancel_after(P) when is_pid(P) ->
P ! cancel.
% the counter
after_receive(T,M,F,A,Pid,Ref) ->
receive
{cancel,Ref} -> Pid ! {after_receive,Ref,cancelled}
after T ->
Pid ! {after_receive,Ref,done},
apply(M,F,A)
end.
and here how to use them:
1> c("../src/counter").
{ok,counter}
2> {Ref,P} = counter:start_after(5000,io,format,["This is the end!" ]).
{#Ref<0.0.0.29>,<0.33.0>}
This is the end!3>
3> {Refa,Pa} = counter:start_after(50000,io,format,["This is the end!" ]).
{#Ref<0.0.0.34>,<0.35.0>}
4> Pa ! {cancel,Refa}.
{cancel,#Ref<0.0.0.34>}
5> flush().
Shell got {after_receive,#Ref<0.0.0.29>,done}
Shell got {after_receive,#Ref<0.0.0.34>,cancelled}
ok
6> P1 = counter:start_c1(5).
<0.52.0>
7> counter:inc_c1(P1).
1
8> counter:inc_c1(P).
{error,no_response}
9> counter:inc_c1(P1).
2
10> counter:inc_c1(P1).
3
11> counter:value_c1(P1).
3
12> counter:inc_c1(P1).
4
13> counter:inc_c1(P1).
done
14> counter:value_c1(P1).
done
15> counter:inc_c1(P1).
done
16> counter:stop_c1(P1).
stop
17> counter:inc_c1(P1).
{error,no_response}
18>
Just recursively call while_loop with the Counter argument incremented by one:
while_loop(Var, Counter + 1)
Your edited version doesn't have a clause when Var =:= Counter and thus crashes. And you'd better use pattern matching in function clauses.
-module(whileloop).
-export([call/0, while_loop/2]).
call() ->
while_loop(10,0).
while_loop(Var, Var) ->
ok;
while_loop(Var, Counter) ->
while_loop(Var, Counter + 1).
And of course you'd need to do something inside the loop. You can use lambdas for that:
-module(whileloop).
-export([call/0, while_loop/3]).
call() ->
while_loop(10, 0, fun(Counter) -> io:format("Counter: ~p~n", [Counter]) end).
while_loop(Var, Var, _) ->
ok;
while_loop(Var, Counter, Fun) ->
Fun(Counter),
while_loop(Var, Counter + 1, Fun).

Splitting a list in equal sized chunks in Erlang

I want to split:
[1,2,3,4,5,6,7,8]
into:
[[1,2],[3,4],[5,6],[7,8]]
It generally works great with:
[ lists:sublist(List, X, 2) || X <- lists:seq(1,length(List),2) ] .
But it is really slow this way. 10000 Elements take amazing 2.5 seconds on my netbook. I have also written a really fast recursive function, but I am simply interested: Could this list comprehension also be written in a different way, so that it is faster?
Try this:
part(List) ->
part(List, []).
part([], Acc) ->
lists:reverse(Acc);
part([H], Acc) ->
lists:reverse([[H]|Acc]);
part([H1,H2|T], Acc) ->
part(T, [[H1,H2]|Acc]).
Test in erlang-shell (I've declared this function in module part):
2> part:part([1,2,3,4,5,6,7,8]).
[[1,2],[3,4],[5,6],[7,8]]
3>
3> timer:tc(part, part, [lists:seq(1,10000)]).
{774,
[[1,2],
[3,4],
[5,6],
[7,8],
"\t\n","\v\f",
[13,14],
[15,16],
[17,18],
[19,20],
[21,22],
[23,24],
[25,26],
[27,28],
[29,30],
[31,32],
"!\"","#$","%&","'(",")*","+,","-.","/0","12","34",
[...]|...]}
Just 774 microseconds (which is ~0,8 milliseconds)
Here are two quick solutions for you that are both flexible. One is easy to read, but only slightly faster than your proposed solution. The other is quite fast, but is a bit cryptic to read. And note that both of my proposed algorithms will work for lists of anything, not just numeric ordered lists.
Here is the "easy-to-read" one. Call by n_length_chunks(List,Chunksize). For example, to get a list of chunks 2 long, call n_length_chunks(List,2). This works for chunks of any size, ie, you could call n_length_chunks(List,4) to get [[1,2,3,4],[5,6,7,8],...]
n_length_chunks([],_) -> [];
n_length_chunks(List,Len) when Len > length(List) ->
[List];
n_length_chunks(List,Len) ->
{Head,Tail} = lists:split(Len,List),
[Head | n_length_chunks(Tail,Len)].
The much faster one is here, but is definitely harder to read, and is called in the same way: n_length_chunks_fast(List,2) (I've made one change to this compared with the one above, in that it pads the end of the list with undefined if the length of the list isn't cleanly divisible by the desired chunk length.
n_length_chunks_fast(List,Len) ->
LeaderLength = case length(List) rem Len of
0 -> 0;
N -> Len - N
end,
Leader = lists:duplicate(LeaderLength,undefined),
n_length_chunks_fast(Leader ++ lists:reverse(List),[],0,Len).
n_length_chunks_fast([],Acc,_,_) -> Acc;
n_length_chunks_fast([H|T],Acc,Pos,Max) when Pos==Max ->
n_length_chunks_fast(T,[[H] | Acc],1,Max);
n_length_chunks_fast([H|T],[HAcc | TAcc],Pos,Max) ->
n_length_chunks_fast(T,[[H | HAcc] | TAcc],Pos+1,Max);
n_length_chunks_fast([H|T],[],Pos,Max) ->
n_length_chunks_fast(T,[[H]],Pos+1,Max).
Tested on my (really old) laptop:
Your proposed solution took about 3 seconds.
My slow-but-readable one was slightly faster and takes about 1.5 seconds (still quite slow)
My fast version takes about 5 milliseconds.
For completeness, Isac's solution took about 180 milliseconds on my same machine.
Edit: wow, I need to read the complete question first. Oh well I'll keep here for posterity if it helps. As far as I can tell, there's not a good way to do this using list comprehensions. Your original version is slow because each iteration of sublist needs to traverse the list each time to get to each successive X, resulting in complexity just under O(N^2).
Or with a fold:
lists:foldr(fun(E, []) -> [[E]];
(E, [H|RAcc]) when length(H) < 2 -> [[E|H]|RAcc] ;
(E, [H|RAcc]) -> [[E],H|RAcc]
end, [], List).
I want to submit slightly complicated but more flexible (and mostly faster) solution of one proposed by #Tilman
split_list(List, Max) ->
element(1, lists:foldl(fun
(E, {[Buff|Acc], C}) when C < Max ->
{[[E|Buff]|Acc], C+1};
(E, {[Buff|Acc], _}) ->
{[[E],Buff|Acc], 1};
(E, {[], _}) ->
{[[E]], 1}
end, {[], 0}, List)).
so function part can be implemented as
part(List) ->
RevList = split_list(List, 2),
lists:foldl(fun(E, Acc) ->
[lists:reverse(E)|Acc]
end, [], RevList).
update
I've added reverse in case if you want to preserve order, but as I can see it adds no more than 20% of processing time.
You could do it like this:
1> {List1, List2} = lists:partition(fun(X) -> (X rem 2) == 1 end, List).
{[1,3,5|...],[2,4,6|...]}
2> lists:zipwith(fun(X, Y) -> [X, Y] end, List1, List2).
[[1,2],[3,4],[5,6]|...]
This takes ~73 milliseconds with a 10000 elements List on my computer. The original solution takes ~900 miliseconds.
But I would go with the recursive function anyway.
I was looking for a partition function which can split a large list to small amount of workers. With lkuty's partition you might get that one worker gets almost double work than all the others. If that's not what you want, here is a version which sublist lengths differ by at most 1.
Uses PropEr for testing.
%% #doc Split List into sub-lists so sub-lists lengths differ most by 1.
%% Does not preserve order.
-spec split_many(pos_integer(), [T]) -> [[T]] when T :: term().
split_many(N, List) ->
PieceLen = length(List) div N,
lists:reverse(split_many(PieceLen, N, List, [])).
-spec split_many(pos_integer(), pos_integer(), [T], [[T]]) ->
[[T]] when T :: term().
split_many(PieceLen, N, List, Acc) when length(Acc) < N ->
{Head, Tail} = lists:split(PieceLen, List),
split_many(PieceLen, N, Tail, [Head|Acc]);
split_many(_PieceLen, _N, List, Acc) ->
% Add an Elem to each list in Acc
{Appendable, LeaveAlone} = lists:split(length(List), Acc),
Appended = [[Elem|XS] || {Elem, XS} <- lists:zip(List, Appendable)],
lists:append(Appended, LeaveAlone).
Tests:
split_many_test_() ->
[
?_assertEqual([[1,2]], elibs_lists:split_many(1, [1,2])),
?_assertEqual([[1], [2]], elibs_lists:split_many(2, [1,2])),
?_assertEqual([[1], [3,2]], elibs_lists:split_many(2, [1,2,3])),
?_assertEqual([[1], [2], [4,3]], elibs_lists:split_many(3, [1,2,3,4])),
?_assertEqual([[1,2], [5,3,4]], elibs_lists:split_many(2, [1,2,3,4,5])),
?_assert(proper:quickcheck(split_many_proper1())),
?_assert(proper:quickcheck(split_many_proper2()))
].
%% #doc Verify all elements are preserved, number of groups is correct,
%% all groups have same number of elements (+-1)
split_many_proper1() ->
?FORALL({List, Groups},
{list(), pos_integer()},
begin
Split = elibs_lists:split_many(Groups, List),
% Lengths of sub-lists
Lengths = lists:usort(lists:map(fun erlang:length/1, Split)),
length(Split) =:= Groups andalso
lists:sort(lists:append(Split)) == lists:sort(List) andalso
length(Lengths) =< 2 andalso
case Lengths of
[Min, Max] -> Max == Min + 1;
[_] -> true
end
end
).
%% #doc If number of groups is divisable by number of elements, ordering must
%% stay the same
split_many_proper2() ->
?FORALL({Groups, List},
?LET({A, B},
{integer(1, 20), integer(1, 10)},
{A, vector(A*B, term())}),
List =:= lists:append(elibs_lists:split_many(Groups, List))
).
Here is a more general answer that works with any sublist size.
1> lists:foreach(fun(N) -> io:format("~2.10.0B -> ~w~n",[N, test:partition([1,2,3,4,5,6,7,8,9,10],N)] ) end, [1,2,3,4,5,6,7,8,9,10]).
01 -> [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]
02 -> [[1,2],[3,4],[5,6],[7,8],[9,10]]
03 -> [[1,2,3],[4,5,6],[7,8,9],[10]]
04 -> [[1,2,3,4],[5,6,7,8],[10,9]]
05 -> [[1,2,3,4,5],[6,7,8,9,10]]
06 -> [[1,2,3,4,5,6],[10,9,8,7]]
07 -> [[1,2,3,4,5,6,7],[10,9,8]]
08 -> [[1,2,3,4,5,6,7,8],[10,9]]
09 -> [[1,2,3,4,5,6,7,8,9],[10]]
10 -> [[1,2,3,4,5,6,7,8,9,10]]
And the code to achieve this is stored inside a file called test.erl:
-module(test).
-compile(export_all).
partition(List, N) ->
partition(List, 1, N, []).
partition([], _C, _N, Acc) ->
lists:reverse(Acc) ;
partition([H|T], 1, N, Acc) ->
partition(T, 2, N, [[H]|Acc]) ;
partition([H|T], C, N, [HAcc|TAcc]) when C < N ->
partition(T, C+1, N, [[H|HAcc]|TAcc]) ;
partition([H|T], C, N, [HAcc|TAcc]) when C == N ->
partition(T, 1, N, [lists:reverse([H|HAcc])|TAcc]) ;
partition(L, C, N, Acc) when C > N ->
partition(L, 1, N, Acc).
It could probably be more elegant regarding the special case where C > N. Note that C is the size of the current sublist being constructed. At start, it is 1. And then it increments until it reaches the partition size of N.
We could also use a modified version of #chops code to let the last list contains the remaining items even if its size < N :
-module(n_length_chunks_fast).
-export([n_length_chunks_fast/2]).
n_length_chunks_fast(List,Len) ->
SkipLength = case length(List) rem Len of
0 -> 0;
N -> Len - N
end,
n_length_chunks_fast(lists:reverse(List),[],SkipLength,Len).
n_length_chunks_fast([],Acc,_Pos,_Max) -> Acc;
n_length_chunks_fast([H|T],Acc,Pos,Max) when Pos==Max ->
n_length_chunks_fast(T,[[H] | Acc],1,Max);
n_length_chunks_fast([H|T],[HAcc | TAcc],Pos,Max) ->
n_length_chunks_fast(T,[[H | HAcc] | TAcc],Pos+1,Max);
n_length_chunks_fast([H|T],[],Pos,Max) ->
n_length_chunks_fast(T,[[H]],Pos+1,Max).
I've slightly altered the implementation from #JLarky to remove the guard expression, which should be slightly faster:
split_list(List, Max) ->
element(1, lists:foldl(fun
(E, {[Buff|Acc], 1}) ->
{[[E],Buff|Acc], Max};
(E, {[Buff|Acc], C}) ->
{[[E|Buff]|Acc], C-1};
(E, {[], _}) ->
{[[E]], Max}
end, {[], Max}, List)).

Resources