view scripts/set/intersect.m @ 27229:255f2681d224

intersect.m: Accept a "legacy" flag for Matlab compatibility. * NEWS: Announce change. * intersect.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. Only use two-input form of sort or sortrows when number of outputs of intersect function is greater than 1. After calculation, change orientation of outputs as required based on optlegacy. Add BIST tests to test output orientation and values. * validsetargs.m: Modify to accept a variable number of option arguments as last input (varargin). Use switch statement to validate possible options including accepting "legacy".
author Rik <rik@octave.org>
date Wed, 10 Jul 2019 17:32:52 -0700
parents 6eb32f0aea87
children e12571df6466
line wrap: on
line source

## Copyright (C) 2000-2019 Paul Kienzle
## Copyright (C) 2008-2009 Jaroslav Hajek
##
## 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} =} intersect (@var{a}, @var{b})
## @deftypefnx {} {@var{c} =} intersect (@var{a}, @var{b}, "rows")
## @deftypefnx {} {@var{c} =} intersect (@dots{}, "legacy")
## @deftypefnx {} {[@var{c}, @var{ia}, @var{ib}] =} intersect (@dots{})
##
## Return the unique elements common to both @var{a} and @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 common rows of
## @var{a} and @var{b}.  The inputs must be 2-D matrices to use this option.
##
## If requested, return column index vectors @var{ia} and @var{ib} such that
## @code{@var{c} = @var{a}(@var{ia})} and @code{@var{c} = @var{b}(@var{ib})}.
##
## Programming Note: The input flag @qcode{"legacy"} changes the shape of the
## outputs (@var{c}, @var{i}, @var{j} to row vectors whenever at least one of
## the inputs is a row vector.
##
## @seealso{unique, union, setdiff, setxor, ismember}
## @end deftypefn

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

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

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

  if (isempty (a) || isempty (b))
    ## Special case shortcuts algorithm.
    ## Lots of type checking required for Matlab compatibility.
    if (isnumeric (a) && isnumeric (b))
      c = [];
    elseif (iscell (b))
      c = {};
    else
      c = "";
    endif
    ia = ib = [];
  else
    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, ja] = unique (a, varargin{:});
      ja = ja(:);
      [b, jb] = unique (b, varargin{:});
      jb = jb(:);
    else
      a = unique (a, varargin{:});
      b = unique (b, varargin{:});
    endif

    if (by_rows)
      c = [a; b];
      if (nargout > 1)
        [c, ic] = sortrows (c);
      else
        c = sortrows (c);
      endif
      ii = find (all (c(1:end-1,:) == c(2:end,:), 2));
      c = c(ii,:);
      len_a = rows (a);
    else
      c = [a(:); b(:)];
      if (nargout > 1)
        [c, ic] = sort (c);         # [a(:);b(:)](ic) == c
      else
        c = sort (c);
      endif
      if (iscellstr (c))
        ii = find (strcmp (c(1:end-1), c(2:end)));
      else
        ii = find (c(1:end-1) == c(2:end));
      endif
      c = c(ii);
      len_a = length (a);
    endif

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

    if (nargout > 1)
      ia = ja(ic(ii));            # a(ia) == c
      ib = jb(ic(ii+1) - len_a);  # b(ib) == c
      if (optlegacy && isrowvec)
        ia = ia.';
        ib = ib.';
      endif
    endif

  endif

endfunction


## Test orientation of output
%!shared a,b
%! a = 1:4;
%! b = 2:5;

%!assert (size (intersect (a, b)), [1, 3])
%!assert (size (intersect (a', b)), [3, 1])
%!assert (size (intersect (a, b')), [3, 1])
%!assert (size (intersect (a', b')), [3, 1])
%!assert (size (intersect (a, b, "legacy")), [1, 3])
%!assert (size (intersect (a', b, "legacy")), [1, 3])
%!assert (size (intersect (a, b', "legacy")), [1, 3])
%!assert (size (intersect (a', b', "legacy")), [3, 1])

## Clear shared variables
%!shared

## 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 the routine for index vectors ia and ib
%!test
%! a = [3 2 4 5 7 6 5 1 0 13 13];
%! b = [3 5 12 1 1 7];
%! [c,ia,ib] = intersect (a, b);
%! assert (c, [1, 3, 5, 7]);
%! assert (ia, [8; 1; 4; 5]);
%! assert (ib, [4; 1; 2; 6]);
%! assert (a(ia), c);
%! assert (b(ib), c);
%!test
%! a = [1,1,2;1,4,5;2,1,7];
%! b = [1,4,5;2,3,4;1,1,2;9,8,7];
%! [c,ia,ib] = intersect (a, b, "rows");
%! assert (c, [1,1,2;1,4,5]);
%! assert (ia, [1;2]);
%! assert (ib, [3;1]);
%! assert (a(ia,:), c);
%! assert (b(ib,:), c);
%!test
%! a = [1 1 1 2 2 2];
%! b = [1 2 3 4 5 6];
%! c = intersect (a, b);
%! assert(c, [1,2]);
%!test
%! a = [1 2 3 4; 5 6 7 8; 9 10 11 12];
%! [b, ia, ib] = intersect (a, a, "rows");
%! assert (b, a);
%! assert (ia, [1:3]');
%! assert (ib, [1:3]');

## Test "legacy" argument
%!test
%! a = [7 1 7 7 4]; 
%! b = [7 0 4 4 0];
%! [c, ia, ib] = intersect (a, b);
%! assert (c, [4, 7]);
%! assert (ia, [5; 1]);
%! assert (ib, [3; 1]);
%! [c, ia, ib] = intersect (a, b, "legacy");
%! assert (c, [4, 7]);
%! assert (ia, [5, 4]);
%! assert (ib, [4, 1]);

## Test return type of empty intersections
%!assert (intersect (['a', 'b'], {}), {})
%!assert (intersect ([], {'a', 'b'}), {})
%!assert (intersect ([], {}), {})
%!assert (intersect ({'a', 'b'}, []), {})
%!assert (intersect ([], ['a', 'b']), "")
%!assert (intersect ({}, []), {})
%!assert (intersect (['a', 'b'], []), "")