%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(diameter_enum).
%%
%% This module constructs finite enumerations.
%%
%% An enumeration is represented as a function on integers, 0 mapping
%% to the number of values enumerated and successive integers mapping
%% to enumerated values. The function will fail on anything but 0 and
%% positive integers less then or equal to the value of the function
%% at 0.
%%
%% The purpose of this is to provide a way of stepping through a large
%% number of values without explicitly constructing the list of all
%% possible values. For example, consider the following function that
%% given a list of lists constructs the list of all possible lists
%% constructed by choosing one element from each sublist.
%%
%% combine([H]) ->
%% [[X] || X <- H];
%% combine([H|T]) ->
%% Ys = combine(T),
%% [[X|Y] || X <- H, Y <- Ys].
%%
%% Eg. [[1,2],[3,4,5]] -> [[1,3],[1,4],[1,5],[2,3],[2,4],[2,5]]
%%
%% If L is a list of three 1000 element lists then combine(L) would
%% construct a list of length 10^9 which will likely exhaust available
%% memory. (Which is how this module came into being. A tail-recursive
%% implementation doesn't fare much better.) By contrast,
%%
%% F = enum:combine([enum:new(L) || L <- Lists])
%%
%% only maps existing lists. It may still be undesirable to step
%% through a very large number of values but it's possible, and easy
%% to step through a selection of values as an alternative.
%%
%% Functions that return enumerations.
-export([new/1,
combine/1,
reverse/1,
map/2,
append/1,
duplicate/2,
nthtail/2,
seq/2,
seq/3,
zip/1,
zip/2,
slice/3,
split/2]).
%% Functions that operate on existing enumerations.
-export([foreach/2,
foldl/3,
foldr/3,
all/2,
any/2,
member/2,
last/1,
nth/2,
to_list/1]).
%% ------------------------------------------------------------------------
%% new/1
%%
%% Turn a list/tuple of values into an enumeration that steps through
%% each element. Turn anything else into an enumeration of that single
%% value.
%% ------------------------------------------------------------------------
new(L)
when is_list(L) ->
new(list_to_tuple(L));
new(T)
when is_tuple(T) ->
enum(size(T), fun(N) -> element(N,T) end);
new(T) ->
fun(0) -> 1; (1) -> T end.
enum(Ord, F) ->
fun(0) -> Ord; (N) when 0 < N, N =< Ord -> F(N) end.
%% ------------------------------------------------------------------------
%% combine/1
%%
%% Map a list/tuple of enumerations to the enumeration of all
%% lists/tuples constructed by choosing one value from each
%% enumeration in the list/tuple.
%% ------------------------------------------------------------------------
combine(T)
when is_tuple(T) ->
F = combine(tuple_to_list(T)),
enum(F(0), fun(N) -> list_to_tuple(F(N)) end);
combine([]) ->
fun(0) -> 0 end;
%% Given positive integers n_1,...,n_k, construct a bijection from
%% {0,...,\prod_{i=1}^k} n_i - 1} to {0,...,n_1} x ... x {0,...,n_k}
%% that maps N to (N_1,...,N_k) where:
%%
%% N_1 = (N div 1) rem n_1
%% ...
%% N_k = (N div n_1*...*n_{k-1}) rem n_k
%%
%% That is:
%%
%% N_i = (N div \prod_{j=1}^{i-1} n_j) rem n_i
%%
%% This corresponds to looping through N_1, incrementing N_2 as N_1
%% loops, and so on up through N_k. The inverse map is as follows.
%%
%% (N_1,...,N_k) -> N = N_1 + N_2*n_1 + ... + N_k*n_{k-1}*...*n_1
%%
%% = \sum_{i=1}^k N_i*\prod_{j=i}^{i-1} n_j
%%
%% [Proof: Induction on k. For k=1 we have the identity map. If
%% g_k : (N_1,...,N_k) |-> N above is bijective then consider
%% the bijection
%%
%% G : (t,n) |--> t + n*K, K = n_k*...*n_1
%%
%% from {0,...,K-1} x {0,...,n_{k+1}-1} onto {0,...,n_{k+1}*K - 1}
%% with inverse F : n |--> (n rem K, n div K). Since
%%
%% g_{k+1}(N_1,...,N_{k+1}) = g_k(N_1,...,N_K) + N_{k+1}*K
%% = G(g_k(N_1,...,N_K), N_{k+1})
%%
%% and G, g_k and ((N-1,...,N_k),N_{k+1}) -> (N_1,...,N_{k+1})
%% are all bijections, so is g_{k+1}.]
combine([_|_] = L) ->
[Ord | Divs] = lists:foldl(fun(F,[D|_] = A) -> [F(0)*D | A] end, [1], L),
RL = lists:reverse(L),
enum(Ord, fun(N) -> combine(N, Ord, Divs, RL) end).
%% Since we use 0 to return the number of elements enumerated, use
%% bijections from {1,...,N} rather than {0,...,N-1}.
combine(N, Ord, Divs, L)
when 0 < N, N =< Ord ->
{Vs, []} = lists:foldl(fun(F, {A, [D|Ds]}) ->
{[F(1 + (((N-1) div D) rem F(0))) | A], Ds}
end,
{[], Divs},
L),
Vs.
%% ------------------------------------------------------------------------
%% reverse/1
%%
%% Construct the enumeration that reverses the order in which values
%% are traversed.
%% ------------------------------------------------------------------------
reverse(E) ->
Ord = E(0),
enum(Ord, fun(N) -> E(Ord + 1 - N) end).
%% ------------------------------------------------------------------------
%% map/2
%%
%% Construct an enumeration that maps enumerated values.
%% ------------------------------------------------------------------------
map(Fun, E) ->
enum(E(0), fun(N) -> Fun(E(N)) end).
%% ------------------------------------------------------------------------
%% append/2
%%
%% Construct an enumeration that successively steps through each of a
%% list of enumerations.
%% ------------------------------------------------------------------------
append(Es) ->
[Ord | Os] = lists:foldl(fun(E, [N|_] = A) -> [N+E(0)|A] end, [0], Es),
Rev = lists:reverse(Es),
enum(Ord, fun(N) -> append(N, Os, Rev) end).
append(N, [Ord | _], [E | _])
when N > Ord ->
E(N - Ord);
append(N, [_|Os], [_|Es]) ->
append(N, Os, Es).
%% ------------------------------------------------------------------------
%% duplicate/2
%%
%% Construct an enumeration that traverses an enumeration multiple
%% times. Equivalent to append(lists:duplicate(N, E)).
%% ------------------------------------------------------------------------
duplicate(N, E) ->
Ord = E(0),
enum(N*Ord, fun(M) -> E(1 + ((M-1) rem Ord)) end).
%% ------------------------------------------------------------------------
%% nthtail/2
%%
%% Construct an enumeration that omits values at the head of an
%% existing enumeration.
%% ------------------------------------------------------------------------
nthtail(N, E)
when 0 =< N ->
nthtail(E(0) - N, N, E).
nthtail(Ord, N, E)
when 0 =< Ord ->
enum(Ord, fun(M) -> E(M+N) end).
%% ------------------------------------------------------------------------
%% seq/[23]
%%
%% Construct an enumeration that steps through a sequence of integers.
%% ------------------------------------------------------------------------
seq(From, To) ->
seq(From, To, 1).
seq(From, To, Incr)
when From =< To ->
enum((To - From + Incr) div Incr, fun(N) -> From + (N-1)*Incr end).
%% ------------------------------------------------------------------------
%% zip/[12]
%%
%% Construct an enumeration whose nth value is the list of nth values
%% of a list of enumerations.
%% ------------------------------------------------------------------------
zip(Es) ->
zip(fun(T) -> T end, Es).
zip(_, []) ->
[];
zip(Fun, Es) ->
enum(lists:min([E(0) || E <- Es]), fun(N) -> Fun([E(N) || E <- Es]) end).
%% ------------------------------------------------------------------------
%% slice/3
%%
%% Construct an enumeration of a given length from a given starting point.
%% ------------------------------------------------------------------------
slice(N, Len, E)
when is_integer(N), N > 0, is_integer(Len), Len >= 0 ->
slice(N, Len, E(0) - (N - 1), E).
slice(_, _, Tail, _)
when Tail < 1 ->
fun(0) -> 0 end;
slice(N, Len, Tail, E) ->
enum(lists:min([Len, Tail]), fun(M) -> E(N-1+M) end).
%% ------------------------------------------------------------------------
%% split/2
%%
%% Split an enumeration into a list of enumerations of the specified
%% length. The last enumeration of the list may have order less than
%% this length.
%% ------------------------------------------------------------------------
split(Len, E)
when is_integer(Len), Len > 0 ->
split(1, E(0), Len, E, []).
split(N, Ord, _, _, Acc)
when N > Ord ->
lists:reverse(Acc);
split(N, Ord, Len, E, Acc) ->
split(N+Len, Ord, Len, E, [slice(N, Len, E) | Acc]).
%% ------------------------------------------------------------------------
%% foreach/2
%%
%% Apply a fun to each value of an enumeration.
%% ------------------------------------------------------------------------
foreach(Fun, E) ->
foldl(fun(N,ok) -> Fun(N), ok end, ok, E).
%% ------------------------------------------------------------------------
%% foldl/3
%% foldr/3
%%
%% Fold through values in an enumeration.
%% ------------------------------------------------------------------------
foldl(Fun, Acc, E) ->
foldl(E(0), 1, Fun, Acc, E).
foldl(M, N, _, Acc, _)
when N == M+1 ->
Acc;
foldl(M, N, Fun, Acc, E) ->
foldl(M, N+1, Fun, Fun(E(N), Acc), E).
foldr(Fun, Acc, E) ->
foldl(Fun, Acc, reverse(E)).
%% ------------------------------------------------------------------------
%% all/2
%%
%% Do all values of an enumeration satisfy a predicate?
%% ------------------------------------------------------------------------
all(Pred, E) ->
all(E(0), 1, Pred, E).
all(M, N, _, _)
when N == M+1 ->
true;
all(M, N, Pred, E) ->
Pred(E(N)) andalso all(M, N+1, Pred, E).
%% Note that andalso/orelse are tail-recusive as of R13A.
%% ------------------------------------------------------------------------
%% any/2
%%
%% Does any value of an enumeration satisfy a predicate?
%% ------------------------------------------------------------------------
any(Pred, E) ->
any(E(0), 1, Pred, E).
any(M, N, _, _)
when N == M+1 ->
false;
any(M, N, Pred, E) ->
Pred(E(N)) orelse any(M, N+1, Pred, E).
%% ------------------------------------------------------------------------
%% member/2
%%
%% Does a value match any in an enumeration?
%% ------------------------------------------------------------------------
member(X, E) ->
member(E(0), 1, X, E).
member(M, N, _, _)
when N == M+1 ->
false;
member(M, N, X, E) ->
match(E(N), X) orelse member(M, N+1, X, E).
match(X, X) ->
true;
match(_, _) ->
false.
%% ------------------------------------------------------------------------
%% last/1
%%
%% Return the last value of an enumeration.
%% ------------------------------------------------------------------------
last(E) ->
E(E(0)).
%% ------------------------------------------------------------------------
%% nth/2
%%
%% Return a selected value of an enumeration.
%% ------------------------------------------------------------------------
nth(N, E) ->
E(N).
%% ------------------------------------------------------------------------
%% to_list/1
%%
%% Turn an enumeration into a list. Not good if the very many values
%% are enumerated.
%% ------------------------------------------------------------------------
to_list(E) ->
foldr(fun(X,A) -> [X|A] end, [], E).