7
$\begingroup$

Is there a way to perform Outer operation without evaluating diagonal elements like:

Outer[f, {1, 2, 3}, {1, 2, 3}], expecting the following output:

{{f[1, 2], f[1, 3]}, {f[2, 1], f[2, 3]}, {f[3, 1], f[3, 2]}}?

Shold I put condition on the function f?

Thank you

$\endgroup$
6
$\begingroup$
Outer[If[# == #2, ## &[], f@##] &, {1, 2, 3}, {1, 2, 3}]

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

Also

Array[If[# == #2, ## &[], f@##] &, {3, 3}]

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

Note: You can also use Unevaluated @ Sequence and, in versions 10+, Nothing in place of ##&[].

Update: An alternative to post-processing suggested by VitaliyKaurov is to re-define f to give Nothing for equal arguments (f[x_,x_]:=Nothing) before using it in Outer:

res1 = Block[{g = f}, g[x_,x_]:=Nothing;Outer[g, Range[10^3], Range[10^3]]  ]; // 
    RepeatedTiming  // First  

0.56

res2 = Replace[Outer[f, Range[10^3], Range[10^3]], _[x_, x_] -> Nothing, {2}]; // 
    RepeatedTiming// First 

0.704

res1 == res2  

True

$\endgroup$
  • 3
    $\begingroup$ Nothing instead of ## &[] would work too. $\endgroup$ – Vitaliy Kaurov Aug 10 '18 at 8:27
  • $\begingroup$ Thank you @Vitaliy; added that variation. $\endgroup$ – kglr Aug 10 '18 at 8:32
  • $\begingroup$ Block[{g = f}, g[x_, x_] :=...] sets a value for f[x_, x_] :=... (because the head of the LHS is evaluated first inside SetDelayed). Strangely Block[{f}, f[x_, x_] :=...] adds significant running time, although it seems equivalent to blocking g. $\endgroup$ – Michael E2 Aug 10 '18 at 11:42
  • $\begingroup$ @MichaelE2, good point. I am puzzled too. $\endgroup$ – kglr Aug 10 '18 at 12:03
  • 1
    $\begingroup$ As this was accepted I assume it's fine, but maybe as a note to future readers: This doesnot remove the diagonal from the output. It removes any elements where the arguments are the same, which might not be the same depending on the second and third argument of Outer. $\endgroup$ – Lukas Lang Aug 10 '18 at 16:25
8
$\begingroup$

MapIndexed and Drop removes the diagonal:

MapIndexed[Drop, Outer[f, Range[3], Range[3]]]
(*  {{f[1, 2], f[1, 3]}, {f[2, 1], f[2, 3]}, {f[3, 1], f[3, 2]}}  *)

It removes the diagonal of any square array, for instance, if the array is generated with an f that is already defined or is a Function:

MapIndexed[Drop, Outer[10 #1 + #2 &, Range[3], Range[3]]]
(*  {{12, 13}, {21, 23}, {31, 32}}  *)

It can be used in the operator form MapIndexed[Drop] @ Outer[..].

It's faster than replacing diagonal elements or defining f[x, x] to evaluate to Nothing.

res1 = Block[{g = f},
     g[x_, x_] := Nothing; 
     Outer[g, Range[10^3], Range[10^3]]]; // RepeatedTiming // First
(*  0.42  *)

Clear[f];  (* needed if res1 is evaluated before res2 *)
res2 = Outer[f, Range[10^3], Range[10^3]], _[x_, x_] -> Nothing, {2}]; //
  RepeatedTiming // First
(*  0.43  *)

res3 = MapIndexed[Drop, Outer[f, Range[10^3], Range[10^3]]]; // 
  RepeatedTiming // First
(*  0.199  *)

res1 === res2 === res3
(*  True  *)

Most of the time for the Drop method is spent generating the array:

Outer[f, Range[10^3], Range[10^3]]; // RepeatedTiming // First
(*  0.184  *)

Note: The current method of using Block[{g = f},...] still defines a value for f[x_, x_], since g evaluates to f in SetDelayed. Failing to clear f before running res2 adds about 50% to the execution time (0.61s). Using Block[{f}, f[x_, x_] :=...] takes about 25% longer (0.506s).

$\endgroup$
7
$\begingroup$

Because operation on diagonal elements is performed anyways you could take an alternative path of removing them post-evaluation. You should ask a question what time is spent on the special construct removing diagonal during evaluation? Perhaps an alternative post-removal would be faster.

Replace[Outer[f, {1, 2, 3}, {1, 2, 3}], _[x_, x_] -> Nothing, {2}]

Timing measures:

Replace[Outer[f, Range[10^3], Range[10^3]], _[x_, x_] -> Nothing, {2}]; // AbsoluteTiming

{0.528235`, Null}

Outer[If[# == #2, Nothing, f@##] &, Range[10^3], Range[10^3]]; // AbsoluteTiming

{1.313204`, Null}

Array[If[# == #2, Nothing, f@##] &, {10^3, 10^3}]; // AbsoluteTiming

{1.41581`, Null}

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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