Outer with non-diagonal elements
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).
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}
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