IDL wish list: vectorized ternary operator
posted Tue 13 Oct 2015 by Michael Galloy under Feature request, IDLThe ternary operator is a handy syntax for creating a result based on a (scalar) condition that is evaluated for truth. For example, I frequently use it to set a default value for an input variable, like the following that uses a default value of 5.0
if x
is not present:
_x = n_elements(x) eq 0L ? 5.0 : x
Without the syntax, you would need an if
statement:
if (n_elements(x) eq 0L) then _x = 5.0 else _x = x
(Or, in this case, a special purpose routine like default
.) But, besides being a bit longer, the if
statement hides the possibility that you might not assign to _x
in both cases of the if
statement, whereas it is clear in the ternary statement that _x
is going to receive a value in any case.
The ternary statement is even more useful because it can be chained with other statements. For example,
results[f] = day + (year eq currentYear $
? string(time, format='(A6)') $
: string(year, format='(I6)'))
Without the ternary operator, this would require a new local variable and would be twice as long:
if (year eq currentYear) then begin
yeartime = string(time, format='(A6)')
endif else begin
yeartime = string(year, format='(I6)')
endelse
result[f] = day + yeartime
One of the main limitations to the ternary operator is that, like an if
statement, the condition must be evaluated to a scalar truth value. I would like to extend the ternary operator to arrays. Let’s see how it would work.
First, let’s create some arrays to use in the examples:
IDL> x = findgen(5)
IDL> y = 2 * findgen(5)
IDL> z = lindgen(5) mod 2
IDL> print, x
0.00000 1.00000 2.00000 3.00000 4.00000
IDL> print, y
0.00000 2.00000 4.00000 6.00000 8.00000
IDL> print, z
0 1 0 1 0
Then, my proposal would be for the following lines to work:
IDL> print, z ? x : y
0.00000 1.00000 4.00000 3.00000 8.00000
One, or both, of the operators could be a scalar:
IDL> print, z ? 1.0 : y
0.00000 1.00000 4.00000 1.00000 8.00000
IDL> print, z ? 1.0 : 2.0
2.00000 1.00000 2.00000 1.00000 2.00000
Now, of course, this can be done already, but it takes a lot more code. For example, the first case expands from one line to:
IDL> result = fltarr(5)
IDL> ind = where(z, count, complement=not_ind, ncomplement=not_count)
IDL> if (count gt 0L) then result[ind] = x[ind]
IDL> if (not_count gt 0L) then result[not_ind] = y[not_ind]
IDL> print, result
0.00000 1.00000 4.00000 3.00000 8.00000
IDL already has rules for type promotion if an operation has operands of two differing types; those could be used here to determine the type of the result since the elements of both value operands would be intermixed in a single array.
October 19th, 2015 at 9:24 am
By the way, Swift has a “Nil Coalescing Operator” which is basically a default value operator that would be very handy. Instead of:
you could do:
October 19th, 2015 at 11:37 am
While no performance advantage, syntactically this is more compact and would accomplish your goals wouldn’t it:
IDL> result = xz + y(~z)
October 19th, 2015 at 12:54 pm
Yes, that would be compact, but I think obscures the meaning of the statement. This is a statement about logic, but relies on multiplication by 0 and 1 doing the right thing. Also, you have to repeat z, so you would either construct it twice (inefficient) or assign it to a variable (so it would take two lines instead of one and you have another named temporary variable). Plus the second use of z is negated, so another temporary variable.
October 21st, 2015 at 1:36 pm
Brace yourself:
IDL> print,([[y],[x]])[indgen(n_elements(x)),z]
0.00000 1.00000 4.00000 3.00000 8.00000
By the way, when you use where(), it’s often better to use where(,/null) so you don’t have to test for zero matches.
IDL> result = y
IDL> ind = where(z, /null)
IDL> result[ind] = x[ind]
Or you can switch to Python:
numpy.where(condition[, x, y])
Return elements, either from x or y, depending on condition.