changeset 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 c80681b4948d
children 9f44123dc25b
files NEWS scripts/set/intersect.m scripts/set/private/validsetargs.m
diffstat 3 files changed, 87 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Jul 10 17:25:39 2019 -0700
+++ b/NEWS	Wed Jul 10 17:32:52 2019 -0700
@@ -53,6 +53,10 @@
   behavior, or Matlab behavior from releases prior to R2012b, can be
   obtained by using the `"legacy"` flag.
 
+- The function `intersect` now accepts a `"legacy"` flag which changes
+  the index values (second and third outputs) as well as the orientation
+  of the outputs to match Matlab releases prior to R2012b.
+
 - Complex RESTful web services can now be accessed by the `webread` and
   `webwrite` functions alongside with the `weboptions` structure.  One
   major feature is the support for cookies to enable RESTful
--- a/scripts/set/intersect.m	Wed Jul 10 17:25:39 2019 -0700
+++ b/scripts/set/intersect.m	Wed Jul 10 17:32:52 2019 -0700
@@ -20,6 +20,7 @@
 ## -*- 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
@@ -32,15 +33,19 @@
 ## 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 index vectors @var{ia} and @var{ib} such that
+## 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 > 3)
+  if (nargin < 2 || nargin > 4)
     print_usage ();
   endif
 
@@ -58,13 +63,21 @@
     endif
     ia = ib = [];
   else
-    by_rows = nargin == 3;
-    isrowvec = isrow (a) && isrow (b);
+    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{:});
@@ -72,13 +85,21 @@
 
     if (by_rows)
       c = [a; b];
-      [c, ic] = sortrows (c);
+      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(:)];
-      [c, ic] = sort (c);         # [a(:);b(:)](ic) == c
+      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
@@ -88,15 +109,20 @@
       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
 
-    ## Adjust output orientation for Matlab compatibility
-    if (! by_rows && isrowvec)
-      c = c.';
-    endif
   endif
 
 endfunction
@@ -107,10 +133,17 @@
 %! 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)), [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
@@ -150,6 +183,19 @@
 %! 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'}), {})
--- a/scripts/set/private/validsetargs.m	Wed Jul 10 17:25:39 2019 -0700
+++ b/scripts/set/private/validsetargs.m	Wed Jul 10 17:32:52 2019 -0700
@@ -19,7 +19,7 @@
 
 ## Validate arguments for binary set operation.
 
-function [x, y] = validsetargs (caller, x, y, byrows_arg)
+function [x, y] = validsetargs (caller, x, y, varargin)
 
   isallowedarraytype = @(x) isnumeric (x) || ischar (x) || islogical (x);
 
@@ -42,21 +42,29 @@
       error ("%s: A and B must be arrays or cell arrays of strings", caller);
     endif
   elseif (nargin == 4)
-    if (! strcmpi (byrows_arg, "rows"))
-      error ("%s: invalid option: %s", caller, byrows_arg);
-    endif
+    for arg = varargin
+      switch (arg{1})
+        case "legacy"
+          ## Accepted option, do nothing.
 
-    if (iscell (x) || iscell (y))
-      error ('%s: cells not supported with "rows"', caller);
-    elseif (! (isallowedarraytype (x) && isallowedarraytype (y)))
-      error ("%s: A and B must be arrays or cell arrays of strings", caller);
-    else
-      if (ndims (x) > 2 || ndims (y) > 2)
-        error ('%s: A and B must be 2-dimensional matrices for "rows"', caller);
-      elseif (columns (x) != columns (y) && ! (isempty (x) || isempty (y)))
-        error ("%s: number of columns in A and B must match", caller);
-      endif
-    endif
+        case "rows"
+          if (iscell (x) || iscell (y))
+            error ('%s: cells not supported with "rows"', caller);
+          elseif (! (isallowedarraytype (x) && isallowedarraytype (y)))
+            error ("%s: A and B must be arrays or cell arrays of strings", caller);
+          else
+            if (ndims (x) > 2 || ndims (y) > 2)
+              error ('%s: A and B must be 2-dimensional matrices for "rows"', caller);
+            elseif (columns (x) != columns (y) && ! (isempty (x) || isempty (y)))
+              error ("%s: number of columns in A and B must match", caller);
+            endif
+          endif
+
+        otherwise
+          error ("%s: invalid option: %s", caller, byrows_arg);
+
+      endswitch
+    endfor
   endif
 
 endfunction