%%%----------------------------------------------------------------------------- %%% @doc Miscelaneous list functions %%% @author Serge Aleynikov %%% @end %%%----------------------------------------------------------------------------- %%% Copyright (c) 2015 Serge Aleynikov %%% %%% Permission is hereby granted, free of charge, to any person %%% obtaining a copy of this software and associated documentation %%% files (the "Software"), to deal in the Software without restriction, %%% including without limitation the rights to use, copy, modify, merge, %%% publish, distribute, sublicense, and/or sell copies of the Software, %%% and to permit persons to whom the Software is furnished to do %%% so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included %%% in all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. %%%----------------------------------------------------------------------------- -module(listx). -export([group/2, copy_tuple_except/5, sum/1, sum/2, zip_record/2]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%----------------------------------------------------------------------------- %%% API %%%----------------------------------------------------------------------------- %% @doc Group elements in the `List' by element at position `Pos'. -spec group(Pos::integer(), List::[tuple()]) -> [{any(), tuple()}]. group(Pos, List) when is_integer(Pos), is_list(List) -> lists:foldl(fun(T, A) when is_tuple(T), tuple_size(T) >= Pos -> TT0 = erlang:make_tuple(tuple_size(T)-1, undefined), TT = copy_tuple_except(Pos, 1, tuple_size(T), T, TT0), Key = element(Pos, T), Old = maps:get(Key, A, []), A#{Key => [TT | Old]} end, #{}, List). %% @doc Add every positional element of each tuple in the list. %% E.g. `sum([{1,2}, {3,4}, {5,6}]) -> {9,12}.' sum(ListOfTuples = [H|_]) when is_tuple(H) -> suml(ListOfTuples, erlang:make_tuple(tuple_size(H), 0)). %% @doc Add every positional element of two tuples. %% E.g. `sum({1,2}, {3,4}) -> {3,6}.' sum(Tuple1, Tuple2) when tuple_size(Tuple1) =:= tuple_size(Tuple2) -> sum(1, tuple_size(Tuple1)+1, Tuple1, Tuple2). %%%----------------------------------------------------------------------------- %%% Internal functions %%%----------------------------------------------------------------------------- %% @doc Copy every element of tuple TS to tuple TT ignoring the item at %% Ignore position copy_tuple_except(_Ignore, I, N,_TS, TT) when I > N -> TT; copy_tuple_except(I, I, N, TS, TT) -> copy_tuple_except(I, I+1, N, TS, TT); copy_tuple_except(Ignore, I, N, TS, TT) when Ignore > I -> copy_tuple_except(Ignore, I+1, N, TS, setelement(I, TT, element(I, TS))); copy_tuple_except(Ignore, I, N, TS, TT) -> % Ignore < I copy_tuple_except(Ignore, I+1, N, TS, setelement(I-1, TT, element(I, TS))). suml([H|T], Acc) when tuple_size(H) =:= tuple_size(Acc) -> suml(T, sum(1,tuple_size(Acc)+1,H,Acc)); suml([], Acc) -> Acc. sum(N,N,_,Acc) -> Acc; sum(I,N,H,Acc) -> sum(I+1,N,H,setelement(I, Acc, element(I, H) + element(I, Acc))). %% @doc Convert a record/tuple to a list of `{Name,Value}' pairs, where `Name' %% is a field name taken from the `Fields' list. -spec zip_record(list(), tuple()) -> list(). zip_record(Fields, State) when is_list(Fields), tuple_size(State) =:= length(Fields)+1 -> Vals = tl(tuple_to_list(State)), lists:zip(Fields, Vals). %%%----------------------------------------------------------------------------- %%% Unit Tests %%%----------------------------------------------------------------------------- -ifdef(EUNIT). group_test() -> ?assertEqual( #{a => [{11,13},{10,12}],b => [{15,16},{30,60}],c => [{10,15}]}, group(1, [{a, 10, 12}, {a, 11, 13}, {b, 30, 60}, {b, 15, 16}, {c, 10, 15}])), ?assertEqual( #{a => [{11,13},{10,12}],b => [{15,16},{30,60}],c => [{15}]}, group(1, [{a, 10, 12}, {a, 11, 13}, {b, 30, 60}, {b, 15, 16}, {c, 15}])), ?assertEqual( #{a => [{11,13},{10,12}],b => [{15,16},{30,60}],c => [{}]}, group(1, [{a, 10, 12}, {a, 11, 13}, {b, 30, 60}, {b, 15, 16}, {c}])). sum_test() -> ?assertEqual({4,6}, sum({1,2}, {3,4})), ?assertEqual({9,12}, sum([{1,2}, {3,4}, {5,6}])), ?assertEqual({6}, sum([{1}, {2}, {3}])), ?assertEqual({9,12,12}, sum([{1,2,3}, {3,4,4}, {5,6,5}])). -endif.