4
$\begingroup$

I want to evaluate a mathematical function of $K$ variables each of variables varying between 1:N. One way to do this is writing $K$ loops and varying variables accordingly, as shown below:

For x1 = 1:N
    For x2 = 1:N
         ...
            For xK = 1:N
               A(x1,x2,...,xk) = f(x1,x2,...,xk)
            End xK
         ...
    End x2
End x1

I want to know is there any other way to do this without $K$ loops?

Since I want to write a routine doing these evaluations for all possible $K$'s, it isn't possible to write varying number of loops in a routine, so what should I do?

$\endgroup$
4
  • 5
    $\begingroup$ Hi there. Could you add a bit more detail to your question title ("how to solve this problem" is a little on the vague side) and to the question as well. Can you supply a simple example of what you're trying to do? $\endgroup$
    – cormullion
    Commented Aug 16, 2013 at 8:53
  • $\begingroup$ isn't this just n=3; a = Table[f[x1, x2, x3], {x1, n}, {x2, n}, {x3, n}] ? $\endgroup$
    – Nasser
    Commented Aug 16, 2013 at 9:25
  • $\begingroup$ Possible duplicate of: (7924) (see Verbeia's answer). $\endgroup$
    – Mr.Wizard
    Commented Aug 16, 2013 at 11:58
  • 1
    $\begingroup$ @Mr.Wizard I would agree that it is very closely related to Verbeia's answer in your link. In fact, my answer uses the same technique she uses in the last part of her answer. But she does not say how to make the hash function, which makes that a bit incomplete I feel. Furthermore, it may be hard to realize right away that you can essentially do the same as use a variable number of For loops there. I know I am against deleting this answer, but I am still a bit fuzzy on closing questions :). $\endgroup$ Commented Aug 16, 2013 at 12:13

4 Answers 4

9
$\begingroup$

A direct replacement

Perhaps your specific example might best be handled with:

Array[
  (a[##] = f[##]) &,
  Table[4, {3}]
]

{ . . . { . . . {f[3, 1, 1], f[3, 1, 2], f[3, 1, 3], f[3, 1, 4]} . . . } . . . }

Where 4 is n and 3 is the number of loops.

Output it was included above for illustration; it may be suppressed with CompoundExpression:

Array[
  a[##] = f[##]; &,
  Table[4, {3}]
];

Note its placement both in the Function and after the Array; for former is for memory conservation.


A better alternative?

While the method above may be considered as a fairly direct replacement for your code if it is representative of the operation you are performing it would be better just to keep a table of values and extract the parts by index.

f = Multinomial;
farray = Outer[f, ##] & @@ Table[Range@9, {3}];

farray[[7, 9, 4]]
f[7, 9, 4]

55426800

55426800

This format will be more memory efficient than the DownValues (hash-table) produced by assignments to a.


Timings

rcollyer asked for timings. In version 7:

n = 9; args = 6;
f = Multinomial;

Array[(a[##] = f[##]); &, Table[n, {args}]];       // AbsoluteTiming

farray = Outer[f, ##] & @@ Table[Range@n, {args}]; // AbsoluteTiming

{2.4011374, Null}

{0.7990457, Null}

$\endgroup$
6
  • $\begingroup$ I like it, you avoid the procedural loops and maintain the same data structure. And it's short too! :) $\endgroup$ Commented Aug 16, 2013 at 12:15
  • $\begingroup$ Ah yes, my hash function felt a bit clumsy, as it would also be hard to edit. I thought about letting the work be done by a nested struct of lists and Part, but Outer did not directly come to mind :). I guess my approach may have its advantages in some cases, but I think your approach makes for a better answer :). $\endgroup$ Commented Aug 16, 2013 at 12:45
  • $\begingroup$ What's the speed difference between Array and Outer? $\endgroup$
    – rcollyer
    Commented Aug 16, 2013 at 12:49
  • $\begingroup$ Interesting. I would have expected Array to be faster. I get similar timings on 9.0.1. $\endgroup$
    – rcollyer
    Commented Aug 16, 2013 at 13:03
  • $\begingroup$ @rcollyer Array is competitive on the same operation: Array[f, Table[n, {args}]]; // AbsoluteTiming -- it's the assignments that slow it down. $\endgroup$
    – Mr.Wizard
    Commented Aug 16, 2013 at 13:35
3
$\begingroup$

You can do it like this:

Do[..., ##] & @@ Table[{a[i], n}, {i, k}]

This defines a function that takes a sequence of parameters, where those parameters will be iterators. Then you Apply it (@@) to a table of iterators. So this will expand to:

Do[..., {a[1], n}, {a[2], n}, ..., {a[k], n}]

I would always avoid For loops in Mathematica and go for Do or other specialized functions instead.

To access the a[i]th variable of your function, you can use the same technique:

f@@ReplacePart[Table[x[j],{j,n}], a[i]->somefunction[x[a[i]]]]

This would go where the ... are above (inside another loop that iterators over i). For instance, like this:

n = 5; k = 3;
f[x_, y_, z_] := Sqrt[x^2 + y^2 + z^2];
Do[
   args = Table[a[i], {i, k}];
   Print[args, "->", f @@ args],
 ##] & @@ Table[{a[i], n}, {i, k}]
$\endgroup$
3
$\begingroup$

An alternative using a different "data structure"

f[x_, y_, z_] := Sqrt[x^2 + y^2 + z^2];

n = 3;
results = f @@@ Tuples[Range[n], {3}];
With[
 {hash := FromDigits[Append[Most[{##}] - 1, Last[{##}]], n]},
 a = Function[results[[hash]]]
 ]

example

a[1, 2, 3] == f[1, 2, 3]

True

This will probably set your variables quicker than any construct with Do or For. Also looking up the values should be relatively fast, as the values of f are already calculated. The hash function in this form saves some memory. But there is always a tradeoff between memory and speed in pre-calculating values.

Of course in this example case you can use the symmetry of f to save more memory.

$\endgroup$
1
  • $\begingroup$ You have my vote but I proposed what I think is a better way in a new section of my answer. $\endgroup$
    – Mr.Wizard
    Commented Aug 16, 2013 at 12:30
3
$\begingroup$

You can take an approach whereby you create all the indices you want beforehand from the loop definitions and then process the indices.

(*Define the number, and depths, of the required loops *)
loops = {4, 2, 3};

(*Create the indices for all the loops  *)
indices = Subsets[Flatten@(Range /@ loops), {Length@loops}];

(* Process indices *)

(a[#]=f[#])&/@indices

Or as a one line mash up:

(a[#]=f[#])&/@Subsets[Flatten@(Range /@ loops), {Length@loops}]
$\endgroup$
4
  • $\begingroup$ Hum, is there something wrong here? Maybe you meant Function[a[#],f[#]]/@Subsets[Flatten@(Range /@ loops), {Length@loops}] $\endgroup$ Commented Aug 16, 2013 at 11:30
  • $\begingroup$ @JacobAkkerboom Thanks, missing parenthesis ! $\endgroup$ Commented Aug 16, 2013 at 12:09
  • 1
    $\begingroup$ I don't think this works as intended. Your subsets have duplicates and are therefore inefficient. Also, your loops seem meaningless. -1 until this is addressed. $\endgroup$
    – Mr.Wizard
    Commented Aug 16, 2013 at 12:53
  • $\begingroup$ I agree with Mr.Wizards comment. To clarify the part about loops: Flatten@(Range/@loops) would be treated the same by Subsets as Range[Max[loops]], as the lower numbers in loops just generate duplicates. So not all the information in loops is meaningful. This is a different matter than the function Subsets generating duplicates itself. $\endgroup$ Commented Aug 16, 2013 at 13:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.