Is there a Matlab conditional IF operator that can be placed INLINE like VBA's IIF
Asked Answered
C

11

29

In VBA I can do the following:

A = B + IIF(C>0, C, 0)

so that if C>0 I get A=B+C and C<=0 I get A=B

Is there an operator or function that will let me do these conditionals inline in MATLAB code?

Certain answered 30/1, 2013 at 19:36 Comment(6)
possible duplicate of Matlab conditional assignmentUnsocial
@Tim Williams, No, I'm looking for INLINE.Certain
Have a look at this blog post that shows how to create an inline function for iffMazman
@Jonas, that worked great if you want to write up an answer with the link and an excerpt of the basics I'll upvote and accept it.Certain
@LanceRoberts: I have created the answerMazman
For an extension of @Alex's answer, see https://mcmap.net/q/501259/-matlab-conditional-assignment-duplicateFaires
M
28

There is no ternary operator in Matlab. You can, of course, write a function that would do it. For example, the following function works as iif with n-d input for the condition, and with numbers and cells for the outcomes a and b:

function out = iif(cond,a,b)
%IIF implements a ternary operator

% pre-assign out
out = repmat(b,size(cond));

out(cond) = a;

For a more advanced solution, there's a way to create an inline function that can even do elseif, as outlined in this blog post about anonymous function shenanigans:

iif  = @(varargin) varargin{2*find([varargin{1:2:end}], 1, 'first')}();

You use this function as

iif(condition_1,value_1,...,true,value_final)

where you replace the dots with any number of additional condition/value pairs.

The way this works is that it picks among the values the first one whose condition is true. 2*find(),1,'first') provides the index into the value arguments.

Mazman answered 30/1, 2013 at 20:41 Comment(1)
@EitanT: Yes, this works indeed well when you can multiply (though you may want to use parentheses around ~cond). However, the other answer already mentioned it, so I though I'd not repeat the other answer.Mazman
P
39

How about simply using the fact that MATLAB automatically converts variable types when required by the operation? E.g., logical to double.

If your variables are scalar double, your code, I believe, can be replaced by

a = b + (c > 0) * c;

In this case, the operator (c > 0) values 1 (logical type) whenever c > 0 and values to 0 otherwise.

Polyvalent answered 23/12, 2013 at 10:35 Comment(1)
You could even use this do define iif = @(c,t,f) c.*(t-f) + f, so you can even do things vectorised. I think your answer is the best MATLAB-solution for simple uses.Digestion
M
28

There is no ternary operator in Matlab. You can, of course, write a function that would do it. For example, the following function works as iif with n-d input for the condition, and with numbers and cells for the outcomes a and b:

function out = iif(cond,a,b)
%IIF implements a ternary operator

% pre-assign out
out = repmat(b,size(cond));

out(cond) = a;

For a more advanced solution, there's a way to create an inline function that can even do elseif, as outlined in this blog post about anonymous function shenanigans:

iif  = @(varargin) varargin{2*find([varargin{1:2:end}], 1, 'first')}();

You use this function as

iif(condition_1,value_1,...,true,value_final)

where you replace the dots with any number of additional condition/value pairs.

The way this works is that it picks among the values the first one whose condition is true. 2*find(),1,'first') provides the index into the value arguments.

Mazman answered 30/1, 2013 at 20:41 Comment(1)
@EitanT: Yes, this works indeed well when you can multiply (though you may want to use parentheses around ~cond). However, the other answer already mentioned it, so I though I'd not repeat the other answer.Mazman
C
7

There is no built-in solution for this, but you can write an IIF yourself.

function result=iif(cond, t, f)
%IIF - Conditional function that returns T or F, depending of condition COND
%
%  Detailed 
%     Conditional matrix or scalar double function that returns a matrix
%     of same size than COND, with T or F depending of COND boolean evaluation
%     if T or/and F has the same dimensions than COND, it uses the corresponding 
%     element in the assignment
%     if COND is scalar, returns T or F in according with COND evaluation, 
%     even if T or F is matrices like char array.
%
%  Syntax
%    Result = iif(COND, T, F)
%           COND - Matrix or scalar condition
%           T  - expression if COND is true
%           F  - expression if COND is false
%           Result - Matrix or scalar of same dimensions than COND, containing
%                    T if COND element is true or F if COND element is false.
%
if isscalar(cond) 
   if cond 
       result = t;
   else
       result = f;
   end
else
  result = (cond).*t + (~cond).*f;
end  
end
Complect answered 30/1, 2013 at 20:27 Comment(1)
Vector version doesn't handle NaN properlySwordtail
S
7

Others have said already that there is no ternary ?: operator in Matlab. As a solution I suggest this function, which takes three functions instead of values. Therefore the amount of unnecessary calculations is minimized and you can check conditions before starting calculations, e.g. if a value is really numeric, or finite, or nonzero:

function [ out ] = iif( condition, thenF, elseF, in, out)
%iif Implements the ternary ?: operator
%   out = iif (@condition, @thenF, @elseF, in[, out])
%
%   The result is equivalent to:
%   condition(x) ? thenF(x) : elseF(x)
%
%   The optional argument out serves as a template, if the output type is
%   different from the input type, e.g. for mapping arrays to cells and
%   vice versa.
%
% This code is in the public domain.

mask = condition(in);
if nargin <= 4
  out = in;
end

if sum(mask)
  out(mask)  = thenF(in(mask));
end
if sum(~mask)
  out(~mask) = elseF(in(~mask));
end

end

Use it like this:

f = @(y)(iif(@(x)(x > 3), @(x)(x.^2), @(x)(x/2), y))
f(linspace(0,6,10))
Shamanism answered 23/5, 2013 at 12:8 Comment(1)
This is the best approximation to a C ?: which will not evaluate the branch that is not taken. Unfortunately the syntax is meh.Cyprio
G
4

Inspired by Jonas' answer the function below also works for mixed type input and chars, for which his function isn't stable.

function out = iif(cond, a, b)
%IIF implements a ternary operator

    % Use cell output for either char or mixed type input
    if ischar(a) || ischar(b) || ~strcmp(class(a), class(b))
        out = cell(size(cond));
        [out{cond}] = deal(a);
        [out{~cond}] = deal(b);
    else
        % Use array output and logical indexing
        out = repmat(b, size(cond));
        out(cond) = a;
    end
end

Edit: weeded out the extra conditional options in the cell branch, which were apparently remnants of a previous mistake, this is probably faster, and definitely cleaner.

Gynaecology answered 27/12, 2014 at 21:18 Comment(0)
M
3

What you refer to is a ternary operator, in C-like notation, ?:. The expression

tern = bool ? T : F

returns T if bool evaluates to true, F otherwise. MATLAB has no ternary operator, however it can be implemented in different ways as an inline expression.

Arrays

% cell array version (any type)
tern = {F,T}{bool+1} % only Octave
tern = subsref({F,T}, struct('type', '{}', 'subs', {{bool+1}}))
% vector array version (numeric types only)
tern = [F,T](bool+1) % only Octave
tern = subsref([F,T], struct('type', '()', 'subs', {{bool+1}}))

Note that T and F have been swapped, and that different brackets are used. The vector array version is a specialization for numeric types. MATLAB does not allow direct indexation of fresh arrays, hence the use of subsref.

  • Pros: Works for any type and any value. Can be used inline.
  • Cons: Before returning the result to tern, both T and F are evaluated.

Logical operators and eval

( bool && eval('tern=T') ) || eval('tern=F')

Note that the logical operators are short-circuited.

  • Pros: Works for any type and any value. Only one, T or F, is evaluated.
  • Cons: Works only in Octave. Can't be used inline, but through the variable tern. eval is not efficient but required.

Basic arithmetic

tern = bool*T + !bool*F
  • Pros: Can be used inline.
  • Cons: Works only for numeric types, and might fail when T or F are NaN or Inf. Both, T and F, are evaluated.

Maximum

tern = max(T,F) % bool = T>F

Note that this solution fits the particular requirements of the initial question with max(C,0).

  • Pros: Can be used inline.
  • Cons: Works only for numeric types, and might fail when T or F are NaN. Both, T and F, are evaluated. Use is strongly limited.
Maytime answered 6/10, 2020 at 18:16 Comment(4)
The first method works only in Octave, it is not legal MATLAB syntax.Callahan
It is. Here an official example: Content Indexing with Curly Braces, {}. mathworks.com/help/matlab/matlab_prog/…Maytime
You can do tern = {F,T}; tern = tern{bool+1}, but you can’t do tern = {F,T}{bool+1}.Callahan
I see, I stand corrected. I will edit the answer to incorporate this.Maytime
P
1

There is now a tern function on the MathWorks file exchange: http://www.mathworks.com/matlabcentral/fileexchange/39735-functional-programming-constructs/content/tern.m

The code is reproduced here:

function varargout = tern(condition, true_action, false_action)

% out = tern(condition, true_action, false_action)
% 
% Ternary operator. If the first input is true, it returns the second
% input. Otherwise, it returns the third input. This is useful for writing
% compact functions and especially anonymous functions. Note that, like
% many other languages, if the condition is true, not only is the false
% condition not returned, it isn't even executed. Likewise, if the
% condition is false, the true action is never executed. The second and
% third arguments can therefore be function handles or values.
%
% Example:
%
% >> tern(rand < 0.5, @() fprintf('hi\n'), pi)
% ans =
%     3.1416
% >> tern(rand < 0.5, @() fprintf('hi\n'), pi)
% hi
%
% It works with multiple outputs as well.
%
% >> [min_or_max, index] = tern(rand < 0.5, ...
%                               @() min([4 3 5]), ...
%                               @() max([4 3 5]))
% min_or_max =
%      5
% index =
%      3
%
% Tucker McClure
% Copyright 2013 The MathWorks, Inc.

    if condition() % Works for either a value or function handle.
        [varargout{1:nargout}] = true_action();
    else
        [varargout{1:nargout}] = false_action();
    end

end
Polyvalent answered 16/9, 2015 at 23:43 Comment(0)
M
1

This is more of an addenum to Alex's answer.

Alex's method doesn't work when you want to return inf

In these cases you often end up getting a 0*inf figure, which MATLAB will evaluate to NaN. Problematic... We can avoid this multiplication using a lookup instead.

As an example, a useful barrier function in convex optimization is something that behaves like log everywhere positive, and -inf elsewhere. Here is how you might create such a function using a lookup:

INF_CONDITION = [0, inf];
fn_logbr = @(x) (x>0)*log(x) - INF_CONDITION( 1+(x<=0) )

Inline conditionals are a hack, and you lose lazy evaluation. You have to be careful. However, having semantic code is really nice, and its easier to share your code when you can't guarantee everyone's environments are the same.

Metallo answered 16/5, 2016 at 9:6 Comment(0)
W
1

The fact that Matlab lacks a proper lazy if ternary operator, and the fact that you need to pollute your workspace with extra files to obtain one, has been a long-standing point of irritation.

Matlab makes this pointlessly difficult by forbidding you from referencing (calling) a cell array (lambda) in the same expression in which it is defined. Hence, you'll see the boiler-plate dummy functions cselect and fcollect below.

Here's my version, which can be defined inline, anywhere, without a file. It's also lazy, although the syntax to achieve this is awkward.

Lazy version 1: you need to pass lambas to the ternary iff

cselect = @(carray,idx)(carray{idx+1});
fcollect = @(f)(f());
iff = @(cond,ftrue,ffalse)(fcollect(cselect({ffalse,ftrue},int32(cond))));

>>> iff(2>1,@()(4),@()(8))
ans = 4

Lazy version 2: you need to pass the expressions as strings

cselect = @(carray,idx)(carray{idx+1});
iff = @(cond,ftrue,ffalse)(eval(cselect({ffalse,ftrue},int32(cond))));
iff(2>1,"4*2","8-1")
ans = 8
Whiggism answered 27/4, 2023 at 11:44 Comment(0)
A
0

If you're looking for an option that doesn't force you to build a function and can take care fairly simple expressions, you can take advantage of anonymous functions. The anonymous function returns a logical, which can be a numeral 1 or 0. Because of this, they can be used to multiply with other numbers to determine if they still hold a value after the expression, or lose their value.

For your case (including if A, B, and C are vectors or not): A = B .+ (@() C>0)()

Anishaaniso answered 10/6, 2019 at 12:45 Comment(0)
S
-1

Using:

eval('input;', 'input = 1;');

is very helpful where 'input' might not exist in the first place.

Sunil answered 12/5, 2019 at 12:42 Comment(1)
This does not answer the question. Please take the tour before contributing to this site. Thanks!Callahan

© 2022 - 2024 — McMap. All rights reserved.