% Copyright (C) 2003-2019 Olivier Boudeville % % This file is part of the Ceylan-Myriad library. % % This library is free software: you can redistribute it and/or modify % it under the terms of the GNU Lesser General Public License or % the GNU General Public License, as they are published by the Free Software % Foundation, either version 3 of these Licenses, or (at your option) % any later version. % You can also redistribute it and/or modify it under the terms of the % Mozilla Public License, version 1.1 or later. % % This library is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU Lesser General Public License and the GNU General Public License % for more details. % % You should have received a copy of the GNU Lesser General Public % License, of the GNU General Public License and of the Mozilla Public License % along with this library. % If not, see and % . % % Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com] % Creation date: July 1, 2007. % Gathering of various facilities about lists. % % See list_utils_test.erl for the corresponding test. % % See also: set_utils.erl and list_table.erl. % -module(list_utils). % Note: if having an (at least mostly) constant list, possibly containing a % large number of elements, and on which the main operation that shall be fast % is element look-up (telling whether or not a given element is in the list), % then the set type shall be preferred (see set_utils.erl for that). % For list_impl: -include("data_types.hrl"). % Checks regarding lists: -export([ ensure_list/1, ensure_list_of_atoms/1, ensure_list_of_tuples/1, ensure_list_of_pids/1 ]). % Basic list operations: -export([ get_element_at/2, insert_element_at/3, extract_element_at/2, remove_element_at/2, remove_last_element/1, get_last_element/1, extract_last_element/1, get_index_of/2, split_at/2, uniquify/1, ensure_is_once_in/2, has_duplicates/1, get_duplicates/1, union/2, intersection/2, difference/2, cartesian_product/1, subtract_all_duplicates/2, delete_existing/2, delete_if_existing/2, delete_all_in/2, append_at_end/2, is_list_of_integers/1, unordered_compare/2, flatten_once/1, filter_out_undefined/1 ]). % For list of tuples (ex: typically used by the HDF5 binding), extended flatten % and al: % -export([ determine_tuple_info/1, flatten_tuples/1, reconstruct_tuples/2 ]). % Random operations on lists: -export([ random_permute/1, random_permute_reciprocal/1, draw_element/1, draw_element/2, draw_element_weighted/1, draw_elements_from/2 ]). % An element of a list: -type element() :: term(). % Either a list of terms, or a term by itself. % % Note: different from maybe( list() ). % -type maybe_list( T ) :: [ T ] | T. -export_type([ maybe_list/1 ]). % Section for the checking of lists. % Ensures that the specified argument is a list: encloses it in a list of its % own if not already a list. % % Note: not to be applied on strings for example. % -spec ensure_list( maybe_list( T ) ) -> [ T ]. ensure_list( List ) when is_list( List ) -> List; ensure_list( Term ) -> [ Term ]. % Ensures that the specified argument is a list of atoms: encloses any atom in % a list of its own if not already a list, or check that this list is only % populated of atoms. % ensure_list_of_atoms( Atom ) when is_atom( Atom ) -> [ Atom ]; ensure_list_of_atoms( List ) when is_list( List ) -> case type_utils:is_homogeneous( List, _CommonType=atom ) of true -> List; false -> throw( { not_list_of_atoms, List } ) end; ensure_list_of_atoms( Other ) -> throw( { neither_list_nor_atom, Other } ). % Ensures that the specified argument is a list of tuples: encloses any tuple in % a list of its own if not already a list, or check that this list is only % populated of tuples. % ensure_list_of_tuples( Tuple ) when is_tuple( Tuple ) -> [ Tuple ]; ensure_list_of_tuples( List ) when is_list( List ) -> case type_utils:is_homogeneous( List, _CommonType=tuple ) of true -> List; false -> throw( { not_list_of_tuples, List } ) end; ensure_list_of_tuples( Other ) -> throw( { neither_list_nor_tuple, Other } ). % Ensures that the specified argument is a list of PIDs: encloses any PID in a % list of its own if not already a list, or check that this list is only % populated of PIDs. % ensure_list_of_pids( Pid ) when is_pid( Pid ) -> [ Pid ]; ensure_list_of_pids( List ) when is_list( List ) -> case type_utils:is_homogeneous( List, _CommonType=pid ) of true -> List; false -> throw( { not_list_of_pids, List } ) end; ensure_list_of_pids( Other ) -> throw( { neither_list_nor_pid, Other } ). % Section for basic list operations. % Index start at position #1, not #0. % Returns the element in the list at the specified index, in [1..length(List)]. % % If the index is out of bounds, a function_clause is raised. % % Note: usually these kinds of functions should not be used, recursive % algorithms are a lot more effective, when applicable. % % Not tail recursive version: % %% get_element_at( List, 1 ) -> %% hd(List); % %% get_element_at( [ _H | T ], Index ) -> %% get_element_at( T, Index-1 ). % -spec get_element_at( list(), basic_utils:positive_index() ) -> element(). get_element_at( List, Index ) -> %io:format( " - getting element #~B of ~w~n", [ Index, List ] ), lists:nth( Index, List ). % Inserts specified element at specified position in specified list. % % For example, insert_element_at( foo, [ a, b, c, d ], 3 ) will return % [ a, b, foo, c, d ]. % -spec insert_element_at( element(), list(), basic_utils:positive_index() ) -> list(). insert_element_at( Element, List, Index ) -> %io:format( " - inserting element ~p at #~B in ~w~n", % [ Element, Index, List ] ), insert_element_at( Element, List, Index, _Acc=[] ). insert_element_at( Element, _List=[], _Index=1, Acc ) -> lists:reverse( [ Element | Acc ] ); insert_element_at( _Element, _List=[], Index, Acc ) -> % Rebuilds input parameters: throw( { invalid_index, Index + length( Acc ), lists:reverse( Acc ) } ); insert_element_at( Element, List, _Index=1, Acc ) -> lists:reverse( [ Element | Acc ] ) ++ List; insert_element_at( Element, _List=[ H | T ], Index, Acc ) -> insert_element_at( Element, T, Index-1, [ H | Acc ] ). % Extracts element from the specified list, at the specified index. % % Returns that element and the resulting, shrunk list. % % Ex: { b, [ a, c ] } = extract_element_at( [ a, b, c ], 2 ). % -spec extract_element_at( list(), basic_utils:positive_index() ) -> { element(), list() }. extract_element_at( List, Index ) -> % Nothing relevant found in the lists module, so: extract_element_at( List, Index, _Acc=[] ). % (helper) extract_element_at( _List=[ H | T ], _Index=1, Acc ) -> { H, lists:reverse( Acc ) ++ T }; extract_element_at( _List=[], Index, Acc ) -> throw( { index_out_of_range, Index + length( Acc ), lists:reverse( Acc ) } ); extract_element_at( _List=[ H | T ], Index, Acc ) -> extract_element_at( T, Index - 1, [ H | Acc ] ). % Returns a list corresponding to the specified one with the element at % specified index removed. % % If the index is out of bounds, a function_clause like % '[{list_utils,remove_element_at,...}]' is triggered. % % Note: usually these kinds of functions should not be used, recursive % algorithms are a lot more effective, when applicable. % % Signature: remove_element_at( List, Index ). % % Curiously lists:nth exists, but no function to remove an element specified by % its index seems to be available in the lists module. % % Not tail recursive version: %remove_element_at( [ _H | T ], _LastIndex=1 ) -> % T; % %remove_element_at( [ H | T ], _Index=N ) -> % [ H | remove_element_at( T, _NextIndex=N-1 ) ]. % % Tail recursive version: % -spec remove_element_at( list(), basic_utils:positive_index()) -> list(). remove_element_at( List, Index ) -> remove_element_at( List, Index, _Result=[] ). remove_element_at( [ _H | RemainingList ], 1, Result ) -> lists:reverse( Result ) ++ RemainingList; remove_element_at( [ H | RemainingList ], Index, Result ) -> remove_element_at( RemainingList, Index-1, [ H | Result ] ). % Removes the last element of the specified list. % % Crashes (with 'no function clause') if the input list is empty. % % Note: not computationnally efficient, usually removing the last element % suggests a bad code design. % -spec remove_last_element( list() ) -> list(). remove_last_element( List ) -> lists:droplast( List ). % remove_last_element( List, _Acc=[] ). %remove_last_element( _List=[ _Last ], Acc ) -> % lists:reverse( Acc ); %remove_last_element( _List=[ H | T ], Acc ) -> % remove_last_element( T, [ H | Acc ] ). % Returns the last element of the specified list. % % Note: not computationnally efficient, usually having to retrieve the last % element suggests a bad code design. % % Crashes (with 'no function clause') if the input list is empty. % -spec get_last_element( list() ) -> element(). get_last_element( _List=[ SingleElement ] ) -> SingleElement; get_last_element( _List=[ _H | T ] ) -> get_last_element( T ). % Extracts the last element of the specified list, returning a pair made of that % element and of the remainder of the list (in its original order). % % Note: not computationnally efficient, usually having to retrieve the last % element suggests a bad code design. % -spec extract_last_element( list() ) -> { element(), list() }. extract_last_element( _List=[] ) -> throw( cannot_extract_from_empty_list ); extract_last_element( List ) -> % Probably the most efficient variant: [ LastElement | RevRest ] = lists:reverse( List ), { LastElement, lists:reverse( RevRest ) }. % Variant: %extract_last_element( List ) -> % extract_last_element( List, _Acc=[] ). % (helper) %extract_last_element( _List=[ LastElement ], Acc ) -> % { LastElement, lists:reverse( Acc ) }; % %extract_last_element( _List=[ H | T ], Acc ) -> % extract_last_element( T, [ H | Acc ] ). % Returns the index, in [1..length(List)], of the (first occurrence of the) % specified element in the specified list. Throws an exception if the element is % not found. % % Ex: 3 = get_index_of( bar, [ foo, ugh, bar, baz ] ) % -spec get_index_of( element(), list() ) -> basic_utils:count(). get_index_of( Element, List ) -> get_index_of( Element, List, _Count=1 ). get_index_of( Element, _List=[], _Count ) -> throw( { non_existing_element, Element } ); get_index_of( Element, _List=[ Element | _T ], Count ) -> Count; get_index_of( Element, _List=[ _H | T ], Count ) -> get_index_of( Element, T, Count + 1 ). % Splits the specified (plain) list in two parts (two plain lists, that are % returned): the first contains the first elements, up to MaxLen (in reverse % order), and the second the others (if any). % % Ex: split_at( [ a, b, c, d, e ], 3 ) = { [ c, b, a ], [ d, e] } % -spec split_at( list(), basic_utils:count() ) -> { list(), list() }. split_at( List, MaxLen ) -> split_at( List, _Count=0, MaxLen, _Acc=[] ). % (helper) split_at( InputList, _Count=MaxLen, MaxLen, AccList ) -> % Max len reached, stopping here: { AccList, InputList }; split_at( _InputList=[], _Count, _MaxLen, AccList ) -> % Input list exhausted: { AccList, _InputList=[] }; split_at( _List=[ H | T ], Count, MaxLen, Acc ) -> split_at( T, Count + 1, MaxLen, [ H | Acc ] ). % Returns a list whose elements are the ones of the specified list, except that % they are unique (all their duplicates have been removed). % % No specific order is respected in the returned list. % % Ex: if L = [1,2,3,2,2,4,5,5,4,6,6,5], then basic_utils:uniquify(L) is: % [3,6,2,5,1,4]. % -spec uniquify( list() ) -> list(). uniquify( List ) -> % There is probably a more efficient way of doing the same: sets:to_list( sets:from_list( List ) ). % Ensures that specified element is included once in specified unordered list % (supposed to contain it already up to once). % % Note: refer to set_utils for a more proper implementation of set. % -spec ensure_is_once_in( term(), list() ) -> list(). ensure_is_once_in( Elem, List ) -> case lists:member( Elem, List ) of true -> List; false -> [ Elem | List ] end. % Tells whether there are in the specified list elements that are present more % than once. % has_duplicates( List ) -> length( uniquify( List ) ) =/= length( List ). % Returns the duplicates in the specified list: returns an (unordered) list of { % DuplicatedTerm, DuplicationCount } pairs, where each duplicated term (i.e. a % term present more than once) is specified, alongside the total number of % occurrences of that terms in the specified list. % % Ex: list_utils:get_duplicates([a,a,b,b,b,c,d,d]) = [{b,3},{d,2},{a,2}] % -spec get_duplicates( list() ) -> [ { element(), basic_utils:count() } ]. get_duplicates( List ) -> get_duplicates( List, _Acc=[] ). get_duplicates( _List=[], Acc ) -> Acc; get_duplicates( _List=[ Term | T ], Acc ) -> % trace_utils:debug_fmt( "Inquiring about term '~p' into ~p.", % [ Term, T ] ), case count_and_filter_term( Term, _InitialList=T, _FilteredList=[], _InitialCount=0 ) of not_found -> % No a duplicated element, just iterating on the next term: get_duplicates( T, Acc ); { TermCount, FilteredList } -> % We already extracted the first Term: get_duplicates( FilteredList, [ { Term, TermCount + 1 } | Acc ] ) end. count_and_filter_term( _Term, _List=[], _FilteredList, _CurrentCount=0 ) -> not_found; count_and_filter_term( _Term, _List=[], FilteredList, CurrentCount ) -> { CurrentCount, FilteredList }; % Term found: count_and_filter_term( Term, _List=[ Term | H ], FilteredList, CurrentCount ) -> count_and_filter_term( Term, H, FilteredList, CurrentCount + 1 ); % Other term: count_and_filter_term( Term, _List=[ OtherTerm | H ], FilteredList, CurrentCount ) -> count_and_filter_term( Term, H, [ OtherTerm | FilteredList ], CurrentCount ). % Returns the union of the two specified lists, i.e. the list of all elements % that are in either list. % -spec union( list(), list() ) -> list(). union( L1, L2 ) -> %uniquify( L1 ++ L2 ). set_utils:to_list( set_utils:union( set_utils:from_list( L1 ), set_utils:from_list( L2 ) ) ). % Returns the intersection of the two specified lists, i.e. the list of all % elements that are in both lists. % % See also: subtract_all_duplicates/2. % -spec intersection( list(), list() ) -> list(). intersection( L1, L2 ) -> %set_utils:to_list( set_utils:intersection( set_utils:from_list( L1 ), % set_utils:from_list( L2 ) ) ). lists:filter( fun( E ) -> lists:member( E, L2 ) end, L1 ). % Returns the difference between the first specified list and the second, % i.e. the elements of the first list that are not in the second one. % -spec difference( list(), list() ) -> list(). difference( L1, L2 ) -> set_utils:to_list( set_utils:difference( set_utils:from_list( L1 ), set_utils:from_list( L2 ) ) ). % Returns the cartesian product of the specified lists (collected in a top-level % list). % % Ex: cartesian_product( [ [a,b,c], [d,e], [f] ] ) = % [ [a,d,f], [a,e,f], [b,d,f], [b,e,f], [c,d,f], [c,e,f] ] % -spec cartesian_product( [ [ T ] ] ) -> [ [ T ] ]. cartesian_product( [ SingleList ] ) -> [ [ E ] || E <- SingleList ]; cartesian_product( [ List | OtherLists ] ) -> [ [ E | SubList ] || E <- List, SubList <- cartesian_product( OtherLists ) ]. % Returns a list equal to L1 except that all elements found in L2 have been % removed, even if in L1 they were duplicated. % % Note: like lists:subtract, except that *all* occurences from L2 in L1 (not % only the first one) are removed. % % Example: [1,4] = basic_utils:subtract_all_duplicates( [1,2,3,4,2], [2,3] ) % % Taken from % http://www.trapexit.org/Finding_Elements_in_One_Array_but_Not_Another % -spec subtract_all_duplicates( list(), list() ) -> list(). subtract_all_duplicates( L1, L2 ) -> lists:filter( fun( E ) -> not lists:member( E, L2 ) end, L1 ). % Returns a copy of the specified list where the first element matching Elem is % deleted, ensuring at least one of these elements exists (as opposed to % lists:delete/2). The order of the specified list is preserved. % -spec delete_existing( element(), list() ) -> list(). delete_existing( Elem, List ) -> % We keep a copy of the original list to be able to generate better error % messages: % delete_existing( Elem, List, _OriginalList=List, _Acc=[] ). delete_existing( Elem, _List=[], OriginalList, _Acc ) -> throw( { element_to_delete_not_found, Elem, OriginalList } ); delete_existing( Elem, _List=[ Elem | T ], _OriginalList, Acc ) -> % The first element found stops the iteration: lists:reverse( Acc ) ++ T; delete_existing( Elem, _List=[ H | T ], OriginalList, Acc ) -> delete_existing( Elem, T, OriginalList, [ H | Acc ] ). % Deletes the first matching of specified element from specified list, returning % whether an element has been removed: either the 'not_found' atom (in which % case the list remained the same) or the corresponding new list (same order and % content, except first occurrence removed). % % Note: allows to perform only one traversal of the list (compared for example % to a lists:member/2 then a lists:delete/2). % -spec delete_if_existing( element(), list() ) -> 'not_found' | list(). delete_if_existing( Elem, List ) -> delete_if_existing( Elem, List, _Acc=[] ). delete_if_existing( _Elem, _List=[], _Acc ) -> not_found; delete_if_existing( Elem, _List=[ Elem | T ], Acc ) -> lists:reverse( Acc) ++ T; delete_if_existing( Elem, _List=[ H | T ], Acc ) -> delete_if_existing( Elem, T, [ H | Acc ] ). % Returns a copy of the specified list where all elements matching Elem are % deleted, whether or not there is any. % % The element order of the specified list is preserved. % -spec delete_all_in( element(), list() ) -> list(). delete_all_in( Elem, List ) -> % A list comprehension would have sufficed: delete_all_in( Elem, List, _Acc=[] ). delete_all_in( _Elem, _List=[], Acc ) -> lists:reverse( Acc ); delete_all_in( Elem, _List=[ Elem | T ], Acc ) -> % An element not to retain: delete_all_in( Elem, T, Acc ); delete_all_in( Elem, _List=[ H | T ], Acc ) -> % Non-matching, keep it: delete_all_in( Elem, T, [ H | Acc ] ). % Appends specified element at the end of specified list, without changing the % order of the list. % % Ex: append_at_end( d, [a,b,c] ) returns [a,b,c,d]. % % Note: usually adding elements at the end of a list should be avoided, as it is % costlier than adding them at head. % -spec append_at_end( element(), list() ) -> nonempty_list(). % This clause was not a bright idea: % - beware of not using a string (hence a list) as first parameter, if not % expecting it to be managed as a list % %append_at_end( ElemList, L ) when is_list( ElemList ) andalso is_list( L ) -> % L ++ ElemList; % append_at_end( Elem, L ) when is_list( L ) -> % Should be more efficient than: %lists:reverse( [ Elem | lists:reverse( L ) ] ): L ++ [ Elem ]. % Returns whether the specified list contains only integers. -spec is_list_of_integers( term() ) -> boolean(). is_list_of_integers( [] ) -> true; is_list_of_integers( [ H | T ] ) when is_integer( H ) -> is_list_of_integers( T ); is_list_of_integers( _ ) -> false. % Compares the two specified lists with no regard to the order of their % elements: returns true iff they have the exact same elements (differentiating % between 1 and 1.0 for example), possibly in a different order. % -spec unordered_compare( list(), list() ) -> boolean(). unordered_compare( L1, L2 ) -> lists:sort( L1 ) =:= lists:sort( L2 ). % Flattens specified list of lists only once (i.e. on a single level), as % opposed to indefinitively (as done recursively by lists:flatten/1); provides % more control than a recursive counterpart. % % Element order is preserved. % % Ex: if L=[ [1], [2,[3,4]] ], lists:flatten(L) yields [1,2,3,4] whereas % list_utils:flatten_once(L) should yield [1,2,[3,4]]. % -spec flatten_once( [ list() ] ) -> list(). flatten_once( List ) -> flatten_once( List, _Acc=[] ). % (helper) % % Note: not using simply 'lists:reverse( Acc );' and a (more efficient) 'L ++ % Acc', as we would end up with [1,[3,4],2] - whereas we want to preserve order. % flatten_once( [], Acc ) -> Acc; flatten_once( [ L | T ], Acc ) when is_list( L ) -> flatten_once( T, Acc ++ L ); flatten_once( [ Unexpected | _T ], _Acc ) -> throw( { not_a_list, Unexpected } ). % Filters out all elements equal to 'undefined'; preserves order. -spec filter_out_undefined( list() ) -> list(). filter_out_undefined( L ) -> % Or: delete_all_in( undefined, L ). [ E || E <- L, E =/= undefined ]. % Determines tuple-related information about specified datastructure: returns { % TupleCount, TupleSize }, supposing the list is made of tuples of uniform % sizes. % -spec determine_tuple_info( [ tuple() ] ) -> { basic_utils:count(), basic_utils:count() }. determine_tuple_info( _TupleList=[] ) -> throw( empty_list ); determine_tuple_info( _TupleList=[ FirstTuple | T ] ) when is_tuple( FirstTuple ) -> TupleSize = size( FirstTuple ), Count = check_tuple_length( T, TupleSize, _AccCount=1 ), { Count, TupleSize }. % (helper) check_tuple_length( _TupleList=[], _TupleSize, AccCount ) -> AccCount; check_tuple_length( _TupleList=[ Tuple | T ], TupleSize, AccCount ) -> case size( Tuple ) of TupleSize -> check_tuple_length( T, TupleSize, AccCount + 1 ); OtherSize -> throw( { heterogeneous_tuple_size, { Tuple, OtherSize }, { expected, TupleSize } } ) end. % Flattens a list of tuples into a simple list of their elements, without % tuples and in the same order. % % Ex: flatten_tuples( [ { 1, 2, 3 }, { 4, 5, 6 } ] ) = [ 1, 2, 3, 4, 5, 6 ] ) % -spec flatten_tuples( [ tuple() ] ) -> list(). flatten_tuples( List ) -> flatten_tuples( List, _Acc=[] ). flatten_tuples( _List=[], Acc ) -> lists:reverse( Acc ); flatten_tuples( [ H | T ], Acc ) -> NewAcc = lists:reverse( tuple_to_list( H ) ) ++ Acc, flatten_tuples( T, NewAcc ). % Reconstructs a list of tuples of specified size from the specified flat list. % % Ex: reconstruct_tuples( [ 1, 2, 3, 4, 5, 6 ], 3 ) = % [ { 1, 2, 3 }, { 4, 5, 6 } ] % -spec reconstruct_tuples( list(), basic_utils:count() ) -> [ tuple() ]. reconstruct_tuples( List, _TupleSize=1 ) -> % Atomic elements do not need to be wrapped in a single-element tuple: List; reconstruct_tuples( List, TupleSize ) -> reconstruct_tuples( List, TupleSize, _Acc=[] ). reconstruct_tuples( _List=[], _TupleSize, Acc ) -> lists:reverse( Acc ); reconstruct_tuples( List, TupleSize, Acc ) -> { TupleAsList, T } = lists:split( _Count=TupleSize, List ), reconstruct_tuples( T, TupleSize, [ list_to_tuple( TupleAsList ) | Acc ] ). % Section to perform random operations on lists. % Returns a random uniform permutation of the specified list. % % Inspired from http://paste.lisp.org/display/74804. % % All these algorithms would need random access to a list, which is not readily % possible here, hence must be emulated. % % See also the 'Speedy unsort:shuffle/1,2' thread in the erlang-questions % mailing list for counterparts. % -spec random_permute( list() ) -> list(). random_permute( List ) -> random_permute( List, length( List ) ). random_permute( _List, _RemainingLen=0 ) -> []; random_permute( List, RemainingLen ) -> % Checking is commented-out: %RemainingLen = length( List ), % (using remove_element_at/2 should be quicker than using % proplists:delete/2, as we stop at the first matching element found) % Index = random_utils:get_random_value( RemainingLen ), %io:format( "Index=~p, ", [ Index ] ), % We put the drawn element at head, and recurse in the remaining list: [ get_element_at( List, Index ) | random_permute( remove_element_at( List, Index ), RemainingLen-1 ) ]. % Returns a reciprocal random uniform permutation of the specified list, % compared to random_permute/1. % % Consists on the reciprocal operation, so that, if starting from a random state % S (see set_random_state/1) and if L2 = random_permute( L1 ), then, if starting % again from S, L1 = random_permute_reciprocal( L2 ). % -spec random_permute_reciprocal( list() ) -> list(). random_permute_reciprocal( List ) -> % This is a little trickier than random_permute/1; we have to reverse % operations for latest to first, hence we must start with the latest drawn % value. So we draw them all first, and start by the end of that list, % taking into account that the range is decremented at each draw: % ReciprocalIndex = lists:reverse( [ random_utils:get_random_value( L ) || L <- lists:reverse( lists:seq( 1, length( List ) ) ) ] ), %io:format( "Reciprocal index = ~p~n", [ ReciprocalIndex ] ), random_permute_reciprocal( lists:reverse( List ), ReciprocalIndex, _Acc=[] ). random_permute_reciprocal( _List=[], _ReciprocalIndex=[], Acc ) -> Acc; random_permute_reciprocal( _List=[ H | T ], _ReciprocalIndex=[ I | Is ], Acc ) -> NewAcc = insert_element_at( _Elem=H, Acc, _Index=I ), random_permute_reciprocal( T, Is, NewAcc ). % Draws one element at random of the specified list, knowing that they all have % the same probability of being drawn (uniform probability). % -spec draw_element( list() ) -> element(). draw_element( _ElementList=[] ) -> throw( cannot_draw_from_empty_list ); draw_element( ElementList ) -> Len = length( ElementList ), draw_element( ElementList, Len ). % Draws one element at random of the specified list, whose length must be the % specified one (allows to precompute it once for multiple drawings), knowing % all elements have the same probability of being drawn (uniform probability). % -spec draw_element( list(), basic_utils:count() ) -> element(). draw_element( ElementList, Length ) -> DrawnIndex = random_utils:get_random_value( Length ), get_element_at( ElementList, DrawnIndex ). % Alternate, less efficient, implementation: % Same probability: %UniformList = [ { Elem, 1 } || Elem <- ElementList ], %draw_element_weighted( UniformList ). % Draws one element at random of the specified list, which is a list of { % Element, Probability } pairs: returns the drawn element, knowing that it will % be chosen according to the probability associated to each element. % % Probabilities are managed as positive (possibly null) numbers (integer or % floating-point values) defined relatively to each other (they do not have to % sum up to 1.0). % % Ex: ElementList = [ {first,1}, {second,2}, {third,1} ] is excepted to return % on average 'second' twice as frequently as 'first' or 'third'. % % Using [ {first,1}, {second,0}, {third,1} ] instead would mean that 'second' % would never be drawn. % -spec draw_element_weighted( [ { element(), number() } ] ) -> element(). draw_element_weighted( ElementList ) -> draw_element_weighted( ElementList, sum_probabilities( ElementList ) ). -spec sum_probabilities( [ { element(), number() } ] ) -> number(). sum_probabilities( ElementList ) -> sum_probabilities( ElementList, _Acc=0 ). sum_probabilities( _ElementList=[], Acc ) -> Acc; sum_probabilities( _ElementList=[ { _Element, Probability } | T ], Acc ) -> sum_probabilities( T, Acc + Probability ). % Sum must be equal to the sum of all probabilities in ElementList. draw_element_weighted( _ElementList, 0 ) -> throw( null_total_probability ); draw_element_weighted( ElementList, Sum ) -> DrawnValue = random_utils:get_random_value( Sum ), %io:format( "draw_element: drawn ~B.~n", [ DrawnValue ] ), select_element( ElementList, DrawnValue, _CurrentSum=0 ). select_element( [ { Element, Probability } | _T ], DrawnValue, CurrentSum ) when Probability + CurrentSum >= DrawnValue -> % Just gone past the probability range: Element; select_element( [ { _Element, Probability } | T ], DrawnValue, CurrentSum ) -> % Drawn value still not reached, continuing: select_element( T, DrawnValue, CurrentSum + Probability ). % Draws the specified number of elements at random of the specified list, % knowing they all have the same probability of being drawn initially, but when % an element is drawn, it is removed from the candidate list so that the next % drawing operates on the resulting shorten list. % -spec draw_elements_from( list(), basic_utils:count() ) -> [ element() ]. draw_elements_from( ElementList, Count ) -> draw_elements_from( ElementList, Count, _Acc=[] ). draw_elements_from( _ElementList, _Count=0, Acc ) -> Acc; draw_elements_from( ElementList, Count, Acc ) -> Drawn = draw_element( ElementList ), ShortenList = lists:delete( Drawn, ElementList ), draw_elements_from( ShortenList, Count - 1, [ Drawn | Acc ] ).