changeset 27294:aa4147476138

setxor.m: Add new option "stable" to control output ordering. * NEWS: Announce new option. * setxor.m: Rewrite docstring to include "stable" and "sorted" options and documentation. Implement "stable" algorithm. Add BIST tests for behavior.
author Rik <rik@octave.org>
date Sat, 27 Jul 2019 18:28:45 -0700
parents 6525b3fe3cf9
children 7ec25367bdc5
files NEWS scripts/set/setxor.m
diffstat 2 files changed, 78 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sat Jul 27 15:40:17 2019 -0700
+++ b/NEWS	Sat Jul 27 18:28:45 2019 -0700
@@ -9,9 +9,9 @@
   behavior can be restored by setting `"editinplace"` to `false` and
   `"home"` to `"~/octave"`.
 
-- The `setdiff', `union`, `unique` functions accept a new sorting option
-  `"stable"` which will return output values in the same order as the
-  input, rather than in ascending order.
+- The `setdiff', `setxor`, `union`, `unique` functions accept a new
+  sorting option `"stable"` which will return output values in the same
+  order as the input, rather than in ascending order.
 
 #### Graphics backend
 
--- a/scripts/set/setxor.m	Sat Jul 27 15:40:17 2019 -0700
+++ b/scripts/set/setxor.m	Sat Jul 27 18:28:45 2019 -0700
@@ -21,19 +21,25 @@
 ## -*- texinfo -*-
 ## @deftypefn  {} {@var{c} =} setxor (@var{a}, @var{b})
 ## @deftypefnx {} {@var{c} =} setxor (@var{a}, @var{b}, "rows")
+## @deftypefnx {} {@var{c} =} setxor (@dots{}, "sorted")
+## @deftypefnx {} {@var{c} =} setxor (@dots{}, "stable")
 ## @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.
+## Return the unique elements exclusive to sets @var{a} or @var{b}.
 ##
 ## 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.
+## to sets @var{a} and @var{b}.  The inputs must be 2-D numeric matrices to use
+## this option.
+##
+## The optional argument @qcode{"sorted"}/@qcode{"stable"} controls the order
+## in which unique values appear in the output.  The default is
+## @qcode{"sorted"} and values in the output are placed in ascending order.
+## The alternative @qcode{"stable"} preserves the order found in the input.
 ##
 ## 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
@@ -54,6 +60,7 @@
   [a, b] = validsetargs ("setxor", a, b, varargin{:});
 
   by_rows = any (strcmp ("rows", varargin));
+  optsorted = ! any (strcmp ("stable", varargin));
   optlegacy = any (strcmp ("legacy", varargin));
 
   if (optlegacy)
@@ -63,7 +70,7 @@
   endif
 
   ## Form A and B into sets.
-  if (nargout > 1)
+  if (nargout > 1 || ! optsorted)
     [a, ia] = unique (a, varargin{:});
     [b, ib] = unique (b, varargin{:});
   else
@@ -82,22 +89,42 @@
       [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],:) = [];
+      if (optsorted)
+        if (! isempty (idx))
+          c([idx, idx+1],:) = [];
+          i([idx, idx+1],:) = [];
+        endif
+      else
+        c = [a; b];
+        c(i([idx, idx+1]), :) = [];
+        if (nargout > 1)
+          i([idx, idx+1]) = [];
+          ## FIXME: Is there a way to avoid a call to sort?
+          i = sort (i);
+        endif
       endif
     else
       na = numel (a);  nb = numel (b);
       [c, i] = sort ([a(:); b(:)]);
-      n = length (c);
+      n = numel (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]) = [];
+      if (optsorted)
+        if (! isempty (idx))
+          c([idx, idx+1]) = [];
+          i([idx, idx+1]) = [];
+        endif
+      else
+        c = [a(:); b(:)];
+        c(i([idx, idx+1])) = [];
+        if (nargout > 1)
+          i([idx, idx+1]) = [];
+          ## FIXME: Is there a way to avoid a call to sort?
+          i = sort (i);
+        endif
       endif
 
       ## Adjust output orientation for Matlab compatibility
@@ -119,37 +146,58 @@
 endfunction
 
 
-%!assert (setxor ([1,2,3], [2,3,4]), [1,4])
+%!assert (setxor ([3,1,2], [4,3,2]), [1,4])
 %!assert (setxor ({'a'}, {'a', 'b'}), {'b'})
+%!assert (setxor ([5, NaN, NaN], [NaN, NaN, 5]), [NaN NaN NaN NaN])
 
 %!test
 %! a = [3, 1, 4, 1, 5];
-%! b = [1, 2, 3, 4];
-%! [c, ia, ib] = setxor (a, b.');
+%! b = [1; 2; 3; 4];
+%! [c, ia, ib] = setxor (a, b);
 %! assert (c, [2; 5]);
-%! assert (c, sort ([a(ia)'; b(ib)']));
+%! assert (ia, [5]);
+%! assert (ib, [2]);
 
+## Test "rows" input
 %!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 (ia, [3]);
+%! assert (ib, [1; 4]);
 
+## Test "stable" sort order
+%!test
+%! a = [3, 1, 4, 1, 5];
+%! b = [1; 2; 3; 4];
+%! [c, ia, ib] = setxor (a, b, "stable");
+%! assert (c, [5; 2]);
+%! assert (ia, [5]);
+%! assert (ib, [2]);
+
+%!test
+%! a = [1 2; 4 5; 1 3];
+%! b = [1 1; 1 2; 4 5; 2 10];
+%! [c, ia, ib] = setxor (a, b, "rows", "stable");
+%! assert (c, [1 3; 1 1; 2 10]);
+%! assert (ia, [3]);
+%! assert (ib, [1; 4]);
+
+## Test various empty matrix inputs
 %!assert (setxor (1, []), 1)
 %!assert (setxor ([], 1), 1)
 
 %!test
-%! [c, ia, ib] = setxor (1, []);
-%! assert (c, 1);
-%! assert (ia, 1);
-%! assert (isempty (ib));
-
+%! [c, ia, ib] = setxor ([3 1], []);
+%! assert (c, [1 3]);
+%! assert (ia, [2; 1]);
+%! assert (ib, []);
 %!test
-%! [c, ia, ib] = setxor ([], 1);
-%! assert (c, 1);
-%! assert (isempty (ia));
-%! assert (ib, 1);
+%! [c, ia, ib] = setxor ([], [3 1]);
+%! assert (c, [1 3]);
+%! assert (ia, []);
+%! assert (ib, [2; 1]);
 
 %!test
 %! a = [2 1; 4 3];  b = [];
@@ -164,6 +212,7 @@
 %! 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;