view scripts/set/setxor.m @ 27238:177be3c01238

setxor.m: Accept a "legacy" flag for Matlab compatibility. * NEWS: Announce "legacy" flag. * setxor.m Add new calling form and explanation of "legacy" option to docstring. Allow up to 4 inputs in input validation. Check for "legacy" in input options and set variable optlegacy. Set variable isrowvec based on optlegacy and orientation of inputs. Adjust orientation of outputs ia, ib based on optlegacy and isrowvec. Add BIST test for "legacy" input.
author Rik <rik@octave.org>
date Thu, 11 Jul 2019 19:55:53 -0700
parents 00f796120a6d
children aa4147476138
line wrap: on
line source

## Copyright (C) 2014-2019 Julien Bect
## Copyright (C) 2008-2019 Jaroslav Hajek
## Copyright (C) 2000, 2006-2007 Paul Kienzle
##
## This file is part of Octave.
##
## Octave is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {} {@var{c} =} setxor (@var{a}, @var{b})
## @deftypefnx {} {@var{c} =} setxor (@var{a}, @var{b}, "rows")
## @deftypefnx {} {@var{c} =} setxor (@dots{}, "legacy")
## @deftypefnx {} {[@var{c}, @var{ia}, @var{ib}] =} setxor (@dots{})
##
## Return the unique elements exclusive to sets @var{a} or @var{b} sorted in
## ascending order.
##
## If @var{a} and @var{b} are both row vectors then return a row vector;
## Otherwise, return a column vector.  The inputs may also be cell arrays of
## strings.
##
## If the optional input @qcode{"rows"} is given then return the rows exclusive
## to sets @var{a} and @var{b}.  The inputs must be 2-D matrices to use this
## option.
##
## The optional outputs @var{ia} and @var{ib} are column index vectors such
## that @code{@var{a}(@var{ia})} and @code{@var{b}(@var{ib})} are disjoint sets
## whose union is @var{c}.
##
## Programming Note: The input flag @qcode{"legacy"} changes the algorithm
## to be compatible with @sc{matlab} releases prior to R2012b.
##
## @seealso{unique, union, intersect, setdiff, ismember}
## @end deftypefn

function [c, ia, ib] = setxor (a, b, varargin)

  if (nargin < 2 || nargin > 4)
    print_usage ();
  endif

  [a, b] = validsetargs ("setxor", a, b, varargin{:});

  by_rows = any (strcmp ("rows", varargin));
  optlegacy = any (strcmp ("legacy", varargin));

  if (optlegacy)
    isrowvec = ! iscolumn (a) || ! iscolumn (b);
  else
    isrowvec = isrow (a) && isrow (b);
  endif

  ## Form A and B into sets.
  if (nargout > 1)
    [a, ia] = unique (a, varargin{:});
    [b, ib] = unique (b, varargin{:});
  else
    a = unique (a, varargin{:});
    b = unique (b, varargin{:});
  endif

  if (isempty (a))
    c = b;
  elseif (isempty (b))
    c = a;
  else
    ## Reject duplicates.
    if (by_rows)
      na = rows (a);  nb = rows (b);
      [c, i] = sortrows ([a; b]);
      n = rows (c);
      idx = find (all (c(1:n-1, :) == c(2:n, :), 2));
      if (! isempty (idx))
        c([idx, idx+1],:) = [];
        i([idx, idx+1],:) = [];
      endif
    else
      na = numel (a);  nb = numel (b);
      [c, i] = sort ([a(:); b(:)]);
      n = length (c);
      if (iscell (c))
        idx = find (strcmp (c(1:n-1), c(2:n)));
      else
        idx = find (c(1:n-1) == c(2:n));
      endif
      if (! isempty (idx))
        c([idx, idx+1]) = [];
        i([idx, idx+1]) = [];
      endif

      ## Adjust output orientation for Matlab compatibility
      if (isrowvec)
        c = c.';
      endif
    endif

    if (nargout > 1)
      ia = ia(i(i <= na));
      ib = ib(i(i > na) - na);
      if (optlegacy && isrowvec)
        ia = ia(:).';
        ib = ib(:).';
      endif
    endif
  endif

endfunction


%!assert (setxor ([1,2,3], [2,3,4]), [1,4])
%!assert (setxor ({'a'}, {'a', 'b'}), {'b'})

%!test
%! a = [3, 1, 4, 1, 5];
%! b = [1, 2, 3, 4];
%! [c, ia, ib] = setxor (a, b.');
%! assert (c, [2; 5]);
%! assert (c, sort ([a(ia)'; b(ib)']));

%!test
%! a = [1 2; 4 5; 1 3];
%! b = [1 1; 1 2; 4 5; 2 10];
%! [c, ia, ib] = setxor (a, b, "rows");
%! assert (c, [1 1; 1 3; 2 10]);
%! assert (c, sortrows ([a(ia,:); b(ib,:)]));

%!assert (setxor (1, []), 1)
%!assert (setxor ([], 1), 1)

%!test
%! [c, ia, ib] = setxor (1, []);
%! assert (c, 1);
%! assert (ia, 1);
%! assert (isempty (ib));

%!test
%! [c, ia, ib] = setxor ([], 1);
%! assert (c, 1);
%! assert (isempty (ia));
%! assert (ib, 1);

%!test
%! a = [2 1; 4 3];  b = [];
%! [c, ia, ib] = setxor (a, b);
%! assert (c, [1; 2; 3; 4]);
%! assert (ia, [3; 1; 4; 2]);
%! assert (isempty (ib));

%!test
%! a = [];  b = [2 1; 4 3];
%! [c, ia, ib] = setxor (a, b);
%! assert (c, [1; 2; 3; 4]);
%! assert (isempty (ia));
%! assert (ib, [3; 1; 4; 2]);
## Test orientation of output
%!shared x,y
%! x = 1:3;
%! y = 2:5;

%!assert (size (setxor (x, y)), [1 3])
%!assert (size (setxor (x', y)), [3 1])
%!assert (size (setxor (x, y')), [3 1])
%!assert (size (setxor (x', y')), [3 1])

## Test multi-dimensional arrays
%!test
%! a = rand (3,3,3);
%! b = a;
%! b(1,1,1) = 2;
%! assert (intersect (a, b), sort (a(2:end)'));

## Test "legacy" input
%!test
%! a = [5 1 3 3 3];
%! b = [4 1 2 2];
%! [c,ia,ib] = setxor (a,b);
%! assert (c, [2, 3, 4, 5]);
%! assert (ia, [3; 1]);
%! assert (ib, [3; 1]);
%! [c,ia,ib] = setxor (a,b, "legacy");
%! assert (c, [2, 3, 4, 5]);
%! assert (ia, [5, 1]);
%! assert (ib, [4, 1]);