Iterate over a cartesian product in Erlang without

2019-02-25 16:41发布

问题:

What's the Erlang equivalent to the following Python code:

for x in range(9):
    for y in range(9):
        for z in range(9):
            foo(x, y, z)

I know I can generate the product first with C = [{X,Y,Z} || X<- lists:seq(1,9), Y<- lists:seq(1,9), Z<- lists:seq(1,9)] then foo([])->done; foo([H|T])->blah blah.

How do I do it without an auxiliary list, using recursion only?

回答1:

You could do it with three recursive functions.

You might be able to do it with some complex pattern-matching in function head.

But easiest way to skip creation of auxiliary list is to call your function inside list comprehension

C = [foo(X, Y, Z) || X<- lists:seq(1,9), 
                     Y<- lists:seq(1,9),  
                     Z<- lists:seq(1,9)]

Where foo/3 process one element.



回答2:

List comprehension still forces you to create auxiliary lists in memory. In case of dealing with huge data sets you should avoid it. Writing recursive functions every time is also awkward so i came up with my own generic for function. It's a little bit slower in traversing than direct recursion or list comprehension but it's memory stable, generic and easy to use.

Usage:

(for({10}))(
    fun (X) -> io:format("~p ",[X]) end).
> 1 2 3 4 5 6 7 8 9 10

(for({10, -10, -2}))(
    fun (X) -> io:format("~p ",[X]) end).
> 10 8 6 4 2 0 -2 -4 -6 -8 -10

Works with lists too:

(for(lists:seq(10, -10, -2)))(
    fun (X) -> io:format("~p ",[X]) end).
> 10 8 6 4 2 0 -2 -4 -6 -8 -10

It's also possible to define step or guard as a function:

(for({256, 1.1, fun (X) -> math:sqrt(X) end, fun (X, Range) -> X > Range end}))(
    fun (X) -> io:format("~p ",[X]) end).
> 256 16.0 4.0 2.0 1.4142135623730951 1.189207115002721

If you pass to for a two parameter function, then you can use accumulator feature just like with lists:foldl/3. You also need to pass initial accumulator to for:

Fact = (for(1, {1, 5}))(
    fun(X, Acc) ->
      X * Acc
    end),
io:format("~p", [Fact]).
> 120

e_fact(N) ->
  {_, E} = (for({1, 1}, {1, N}))( % i assumed  1/0! equals 1
    fun(X, {LastFact, Sum}) ->
      Fact = LastFact * X,
      {Fact, Sum + 1 / Fact}
    end),
  E.
io:format("e=~p", [e_fact(10)]).
> e=2.7182818011463845

Also step and guard functions can be dependent on accumulator. Just pass function with one more parameter.

Nested loops finding Pythagorean triples. Easy with closures:

pyth_lists(N) ->
  [io:format("~p ", [{A, B, C}]) ||
    A <- lists:seq(1, N),
    B <- lists:seq(A + 1, N),
    C <- lists:seq(B + 1, N),
    A * A + B * B == C * C].

pyth_for(N) ->
  (for({1, N}))(
    fun(A) ->
      (for({A + 1, N}))(
        fun(B) ->
          (for({B + 1, N}))(
            fun(C) ->
              case A * A + B * B == C * C of
                true -> io:format("~p ", [{A, B, C}]);
                false -> ok
              end
            end)
        end)
    end).

It's too small for external repository. I keep it in my utilities module. If you find it helpful, here is code:

-export([for/1, for/2]).

for(Through) ->
  for([], Through).

for(InitAcc, Opts) when is_tuple(Opts) ->
  {Init, Range, Step, Guard} = for_apply_default_opts(Opts),
  fun(Fun) -> 
    UpdFun = if
      is_function(Fun, 1) ->
        fun(I, _FAcc) -> Fun(I) end;
      is_function(Fun, 2) ->
        Fun
    end,
    for_iter(UpdFun, InitAcc, Init, Range, Step, Guard) end;

for(InitAcc, List) when is_list(List) ->
  fun(Fun) -> for_list_eval(Fun, InitAcc, List) end.

for_iter(Fun, Acc, I, Range, Step, Guard) ->
  case Guard(I, Range, Acc) of
    false ->
      Acc;
    true ->
      NewAcc = Fun(I, Acc),
      for_iter(Fun, NewAcc, Step(I, NewAcc), Range, Step, Guard)
  end.

for_list_eval(Fun, Acc, List) ->
  if
    is_function(Fun, 1) ->
      lists:foreach(Fun, List);
    is_function(Fun, 2) ->
      lists:foldl(Fun, Acc, List)
  end.

for_apply_default_opts({Range}) ->
  DefaultInit = 1,
  for_apply_default_opts({DefaultInit, Range});

for_apply_default_opts({Init, Range}) ->
  DefaultStep = 1,
  for_apply_default_opts({Init, Range, DefaultStep});

for_apply_default_opts({Init, Range, Step}) ->
  DefaultGuard = case (Step > 0) or is_function(Step) of
                   true -> fun(I, IterRange, _Acc) -> I =< IterRange end;
                   false -> fun(I, IterRange, _Acc) -> I >= IterRange end
                 end,
  for_apply_default_opts({Init, Range, Step, DefaultGuard});

for_apply_default_opts({Init, Range, Step, Guard}) when is_function(Guard, 2) ->
  for_apply_default_opts({Init, Range, Step, fun(I, IterRange, _Acc) -> Guard(I, IterRange) end});

for_apply_default_opts({Init, Range, Step, DefaultGuard}) when is_number(Step) ->
  for_apply_default_opts({Init, Range, fun(I, _Acc) -> I + Step end, DefaultGuard});

for_apply_default_opts({Init, Range, Step, DefaultGuard}) when is_function(Step, 1) ->
  for_apply_default_opts({Init, Range, fun(I, _Acc) -> Step(I) end, DefaultGuard});

for_apply_default_opts({_Init, _Range, _Step, _DefaultGuard} = Opts) ->
  Opts.


标签: erlang