How to avoid returning a Null if there is no "else" condition in an If construct
If
does not have the attribute SequenceHold
; use the "vanishing function" ##&[]
instead of Sequence[]
:
If[# > 5, #, ## &[]] & /@ Range[10]
{6, 7, 8, 9, 10}
See this and this for other uses, and SlotSequence if you are confused by ##
.
To further explain the method above, Sequence
is flattened when it appears as an argument (level one) in an expression with a head that does not have either the SequenceHold
or HoldAllComplete
attributes.
For our purpose in If
one can use any expression which:
- is held unevaluated by
Hold
(More specifically theHoldRest
attribute.) - does not have the head
Sequence
- evaluates to
Sequence[]
All of these heads meet this requirement:
## &
Unevaluated
- Symbol (e.g.
seq = Sequence[]
) Apply
(e.g.Sequence @@ {}
)
Observe:
seq = Sequence[8, 9];
h = Hold[1, Sequence[2, 3], ## &[4, 5], Unevaluated[6, 7], seq, Sequence @@ {10, 11}]
Hold[1, 2, 3, (##1 &)[4, 5], Unevaluated[6, 7], seq, Sequence @@ {10, 11}]
Note that Sequence[2, 3]
is flattened while all others remain. An application of List
will invoke additional evaluation:
List @@ h
{1, 2, 3, 4, 5, Unevaluated[6, 7], 8, 9, 10, 11}
One sees that Unevaluated[6, 7]
remains, which is an important distinction, but this is acknowledged behavior* for inert functions like List
and it will evaluate in other applications:
Plus @@ h == Plus @@ Range[11]
True
*Working with Unevaluated Expressions - Robby Villegas
Note: You may notice red highlighting in the Front End in the expression Unevaluated[6, 7]
; the multi-argument (and null-argument) form of Unevaluated
is not documented but it is used internally by Mathematica. You can adjust the highlighting to match by setting:
Unprotect[Unevaluated];
SyntaxInformation[Unevaluated] = {"ArgumentsPattern" -> {___}};
Protect[Unevaluated];
Version 10.2
Version 10.2 introduces the function Nothing
which is applicable to a subset of this problem. Specifically Nothing
vanishes only if the surrounding head is List
or if it is a non-delayed Value within an Association
. Therefore one can write:
If[# > 5, #, Nothing] & /@ Range[10]
{6, 7, 8, 9, 10}
However it will remain in the output of:
If[OddQ[#], #, Nothing] & /@ foo[1, 2, 3, 4]
foo[1, Nothing, 3, Nothing]
Performance
Oleksandr expressed concern about the performance of ## &[]
:
I am not extremely keen on
##&[]
. It is concise (and I think clear enough after one understands it), butFunction
invocation is not the fastest operation in Mathematica. In most cases (except where the subtle differences actually matter), I would preferUnevaluated@Sequence[]
for explicitness and avoiding an unnecessary function call. Simon Woods's suggestion ofUnevaluated[]
could be the best compromise.
Here is a benchmark in 10.0.2 comparing the various methods.
SetAttributes[test, HoldFirst]
test[form_][list_] := If[#, 1, form] & /@ list
seq = Sequence[];
Needs["GeneralUtilities`"]
BenchmarkPlot[
{
test[Unevaluated @ Sequence[]],
test[Sequence @@ {}],
test[seq],
test[## &[]],
test[Unevaluated[]]
},
RandomChoice[{True, False}, #] &
]
In this log-log scale the methods are so close that it is hard to distinguish them. Here is another presentation using the average of five runs:
rand = RandomChoice[{True, False}, 1*^6];
Mean @ Table[First @ Timing @ # @ rand, {5}] & /@
{
test[Unevaluated @ Sequence[]],
test[Sequence @@ {}],
test[seq],
test[## &[]],
test[Unevaluated[]]
}
{0.430563, 0.564724, 0.377522, 0.508563, 0.514803}
At least on my system ## &[]
appears to be slightly faster than Unevaluated[]
contrary to Oleksandr's assertion. Both are a bit slower than Unevaluated @ Sequence[]
because of an additional transformation step. (##&[]
and Unevaluated[]
both evaluate to Sequence[]
.) The assignment method (seq = Sequence[]
) is the fastest. However this Function
is extremely simple; in any nontrivial use the overhead of ## &[]
is likely insignificant.
If the slight overhead or cryptic nature of ## &[]
bothers you I propose using:
vanish[] = Sequence[];
It depends what you consider nothing, but you could try something like this
If[a, b, Unevaluated[Sequence[]]]
for example
3 + If[False, 1, Unevaluated[Sequence[]]]
returns 3. Wrapping an argument of a function in Unevaluated
is effectively the same as temporarily setting the attribute Hold
for that argument meaning that the argument isn't evaluated until after it's inserted in the definition of that function.
By the way, in your definition of QuickSort
you're calling Cases[x, j_ /; j < pivot]
six times. It's probably more efficient to assign Cases[x, j_ /; j < pivot]
to a dummy variable and use that instead.
I note that all answers so far try to solve the problem of assigning a potential Null value by manipulating the return value. I feel it would be more appropriate to make the whole assignment conditional. Like this:
If[condition, aa = value]
There's also a small bug in your program (count
isn't initialized), and, of course, it doesn't sort at the moment. I assume that you aware of that and that the Return value is used for testing.
The code would then be:
QuickSort[x_List] :=
Module[{pivot, aa = 0, bb = 0, count = 0},
If[Length@x <= 1, Return[x]];
pivot = First[x];
If[Length[Cases[x, j_ /; j < pivot]] > 1,
aa = Length[Cases[x, j_ /; j < pivot]] - 1
];
If[Length[Cases[x, j_ /; j < pivot]] > 1,
bb = Length[Cases[x, j_ /; j > pivot]] - 1
];
count = count + aa + bb;
Flatten@{QuickSort[Cases[x, j_ /; j < pivot]],
Cases[x, j_ /; j == pivot], QuickSort[Cases[x, j_ /; j > pivot]]};
Return[count]]
Remove ;Return[count]
to let it sort again.
I'm not sure about the j < pivot
test in the second If
. It depends on your intention with bb, but I guess the test should be j > pivot
.