# HG changeset patch # User Markus Mützel # Date 1689506025 -7200 # Node ID 0eaa354b7ed143985dee3e139a2b6c9ea35ffa57 # Parent 09f9b8f663fb42c661de5722dd4b176df821b826 Overhaul new function ismembertol (bug #56735, patch #10355). * scripts/set/ismembertol.m: Add one line description to top of help text. Change wording in documentation string. Use table for documentation of property- value pairs. Adapt to Octave coding style. Use more specific error messages in input validation. Allow lower-case property names. Use transpose operator instead of ctranspose operator. Break some very long lines. Remove some debug output. Use spaces instead of tabs for indentation. Remove whitespace from end of lines. Update self tests. * scripts/set/ismember.m: Add cross-reference to new function. * scripts/set/module.mk: Add new function to build system. * doc/interpreter/set.txi: Add help text of new function to documentation. * etc/NEWS.9.md: Add new function to list. diff -r 09f9b8f663fb -r 0eaa354b7ed1 doc/interpreter/set.txi --- a/doc/interpreter/set.txi Fri Jun 02 10:32:24 2023 -0300 +++ b/doc/interpreter/set.txi Sun Jul 16 13:13:45 2023 +0200 @@ -67,4 +67,6 @@ @DOCSTRING(ismember) +@DOCSTRING(ismembertol) + @DOCSTRING(powerset) diff -r 09f9b8f663fb -r 0eaa354b7ed1 etc/NEWS.9.md --- a/etc/NEWS.9.md Fri Jun 02 10:32:24 2023 -0300 +++ b/etc/NEWS.9.md Sun Jul 16 13:13:45 2023 +0200 @@ -126,6 +126,7 @@ ### Alphabetical list of new functions added in Octave 9 * `isenv` +* `ismembertol` * `isuniform` * `tensorprod` diff -r 09f9b8f663fb -r 0eaa354b7ed1 scripts/set/ismember.m --- a/scripts/set/ismember.m Fri Jun 02 10:32:24 2023 -0300 +++ b/scripts/set/ismember.m Sun Jul 16 13:13:45 2023 +0200 @@ -71,7 +71,7 @@ ## @end group ## @end example ## -## @seealso{lookup, unique, union, intersect, setdiff, setxor} +## @seealso{lookup, unique, union, intersect, setdiff, setxor, ismembertol} ## @end deftypefn function [tf, s_idx] = ismember (a, s, varargin) diff -r 09f9b8f663fb -r 0eaa354b7ed1 scripts/set/ismembertol.m --- a/scripts/set/ismembertol.m Fri Jun 02 10:32:24 2023 -0300 +++ b/scripts/set/ismembertol.m Sun Jul 16 13:13:45 2023 +0200 @@ -28,15 +28,17 @@ ## @deftypefnx {} {@var{tf} =} ismembertol (@var{a}, @var{s}, @var{tol}) ## @deftypefnx {} {@var{tf} =} ismembertol (@var{a}, @var{s}, @var{name}, @var{value}) ## @deftypefnx {} {[@var{tf}, @var{s_idx}] =} ismembertol (@dots{}) -## -## Return a logical matrix @var{tf} with the same shape as @var{a} which is -## true (1) if the element in @var{a} is @var{tol} close to @var{s} and false (0) if it -## is not. When @var{tol} is not provided, uses a default tolerance of @qcode{1e-6}. +## Check if values are member of a set within a tolerance. ## -## If a second output argument is requested then the index into @var{s} of each -## matching element is also returned. +## This functions returns a logical matrix @var{tf} with the same shape as +## @var{a} which is true (1) where the element in @var{a} is close to @var{s} +## within a tolerance @var{tol} and false (0) if it is not. If @var{tol} is +## not provided, a default tolerance of @qcode{1e-6} is used. ## -## The inputs @var{a} and @var{s} are numberic values. +## If a second output argument is requested, then the index into @var{s} of +## each matching element is also returned. +## +## The inputs @var{a} and @var{s} must be numeric values. ## ## @example ## @group @@ -48,21 +50,33 @@ ## @end group ## @end example ## -## Optional argument pair @var{name} and @var{value} might be given. -## The @var{value} is either @qcode{true} or @qcode{false} and the @var{name} -## might be given chosen from the following options: -## - @qcode{"ByRows"}: compares the rows of @var{a} and @var{s} by considering -## each column separately. Two rows, @var{u} and @var{v}, are within tolerance -## @qcode{if all(abs(u-v) <= tol*max(abs([a;s])))}. -## - @qcode{"OutputAllIndices"}: returns a cell array containing the indices for -## all elements in @var{s} that are within tolerance of the corresponding value -## in @var{s}. -## - @qcode{"DataScale"}: change the scale in the tolerance test to -## @qcode{abs(u-v) <= tol*DS}. +## Optional property-value pairs @var{name} and @var{value} might be given. +## For each of these pairs, the @var{name} might be one of the following +## strings: +## +## @table @asis +## @item @qcode{"ByRows"} +## If set to @qcode{false} (default), all elements in @var{a} and @var{s} are +## treated separately. If set to @qcode{true}, @var{tf} contains @qcode{true} +## for each row in @var{a} that matches a row in @var{s} within the given +## tolerance. Two rows, @var{u} and @var{v}, are within tolerance if they +## fullfill the condition @qcode{all (abs (u-v) <= tol*max (abs ([a;s])))}. +## +## @item @qcode{"OutputAllIndices"} +## If set to @qcode{false} (default), @var{s_idx} contains indices for one +## of the matches. If set to @qcode{true}, @var{s_idx} is a cell array +## containing the indices for all elements in @var{s} that are within tolerance +## of the corresponding value in @var{a}. +## +## @item @qcode{"DataScale"} +## The corresponding value @var{DS} is used to change the scale factor in the +## tolerance test to @qcode{abs(u-v) <= tol*DS}. By default, the maximum +## absolute value in @var{a} and @var{s} is used as the scale factor. +## @end table ## ## Example: ## @example -## s = [1:6]'*pi; +## s = [1:6].' * pi; ## a = 10.^log10 (x); ## [tf, s_idx] = ismembertol (a, s); ## @end example @@ -73,44 +87,49 @@ function [tf, s_idx] = ismembertol (a, s, varargin) if (nargin < 2 || nargin > 9) - print_usage () + print_usage (); endif - if nargin < 3 || ! isnumeric (varargin{1}) + if (nargin < 3 || ! isnumeric (varargin{1})) # defaut tolerance tol = 1e-6; else tol = varargin{1}; + varargin(1) = []; endif - if ! isnumeric (a) || ! isnumeric (s) - print_usage (); + if (! isnumeric (a) || ! isnumeric (s)) + error ("ismembertol: A and S must contain numeric values"); endif - if nargin > 2 && any (! ismember ( {varargin{ find(cellfun(@ischar,varargin)) }}, {"OutputAllIndices", "ByRows", "DataScale"})) - print_usage (); + if (nargin > 2 ... + && (! iscellstr (varargin(1:2:end)) ... + || any (! ismember (lower (varargin(1:2:end)), ... + {"outputallindices", "byrows", "datascale"})))) + error ("ismembertol: unsupported property"); endif - if nargin > 3 && (! all (cellfun(@(x) ischar(x) || islogical(x) || isnumeric(x), {varargin{2:end}})) || all (cellfun(@(x) isnumeric(x), {varargin{1:2}}))) - print_usage (); - endif - - by_rows_idx = find (strcmp ("ByRows", varargin)); + by_rows_idx = find (strcmpi ("ByRows", varargin)); by_rows = (! isempty (by_rows_idx) && logical (varargin{by_rows_idx+1}) ); - all_indices_idx = find (strcmp ("OutputAllIndices", varargin)); - all_indices = (! isempty (all_indices_idx) && logical (varargin{all_indices_idx+1}) ); + if (by_rows && columns (a) != columns (s)) + error ("ismembertol: number of columns in A and S must match for 'ByRows'"); + endif - data_scale_idx = find (strcmp ("DataScale", varargin)); - data_scale = (! isempty (data_scale_idx) && isnumeric (varargin{data_scale_idx+1}) ); - if data_scale + all_indices_idx = find (strcmpi ("OutputAllIndices", varargin)); + all_indices = (! isempty (all_indices_idx) ... + && logical (varargin{all_indices_idx+1}) ); + + data_scale_idx = find (strcmpi ("DataScale", varargin)); + data_scale = (! isempty (data_scale_idx) ... + && isnumeric (varargin{data_scale_idx+1}) ); + if (data_scale) DS = varargin{data_scale_idx+1}; else DS = max (abs ([a(:);s(:)])); endif if (! by_rows) - disp ('not by rows'); sa = size (a); s = s(:); a = a(:); @@ -126,13 +145,12 @@ s = s(1:(end - sum (isnan (s)))); endif - if ! data_scale + if (! data_scale) DS = max (abs ([a(:);s(:)])); endif [s_i, s_j] = find (abs (transpose (s) - a) < tol * DS); - if ! all_indices - disp ('not all indices'); + if (! all_indices) s_idx = zeros (size (a)); [~, I] = unique (s_i); s_j = s_j(I); @@ -144,30 +162,24 @@ s_idx = reshape (s_idx, sa); tf = reshape (tf, sa); else # all_indices - disp ('all indices'); - disp ([s_i'; s_j']); s_idx = cell (size(a)); tf = zeros (size(a)); C = unique (s_j); - for ic = C', - printf ("ic = %d\n",ic); + for ic = C.' ii = find (s_j == ic); - disp (ii); - for sii = s_i(ii)' - printf ("adding %d to s_idx{%d}\n",ic,sii); - if ! isempty (is) - s_idx{sii} = [s_idx{sii} is(ic)]; - else - s_idx{sii} = [s_idx{sii} ic]; - endif - endfor - disp (s_idx{ic}); + for sii = s_i(ii).' + if (! isempty (is)) + s_idx{sii} = [s_idx{sii}, is(ic)]; + else + s_idx{sii} = [s_idx{sii}, ic]; + endif + endfor + tf(ic) = 1; endfor endif - else # "rows" argument - disp ('by rows'); + else # "ByRows" if (isempty (a) || isempty (s)) tf = false (rows (a), 1); s_idx = zeros (rows (a), 1); @@ -176,30 +188,35 @@ tf = all (bsxfun (@eq, a, s), 2); s_idx = double (tf); else - # Two rows, u and v, are within tolerance if all(abs(u-v) <= tol*max(abs([A;B]))). + ## Two rows, u and v, are within tolerance if + ## all(abs(u-v) <= tol*max(abs([A;B]))). na = rows (a); - if ! all_indices + if (! all_indices) s_idx = zeros (na, 1); - else - s_idx = cell (na, 1); - endif - if length (DS) == 1, + else + s_idx = cell (na, 1); + endif + if (length (DS) == 1) DS = repmat (DS, 1, columns (a)); endif - for i = 1:na, - if ! all_indices + for i = 1:na + if (! all_indices) s_i = find ( all (abs (a(i,:) - s) < tol * DS, 2), 1); - if ! isempty (s_i), s_idx(i) = s_i; endif - else - s_i = find ( all (abs (a(i,:) - s) < tol * DS, 2)); - if ! isempty (s_i), s_idx{i} = s_i; endif - endif + if (! isempty (s_i)) + s_idx(i) = s_i; + endif + else + s_i = find (all (abs (a(i,:) - s) < tol * DS, 2)); + if (! isempty (s_i)) + s_idx{i} = s_i; + endif + endif endfor - if ! all_indices + if (! all_indices) tf = logical (s_idx); - else - tf = cellfun(@(x) ! isempty (x) && all (x(:)!=0), s_idx); - endif + else + tf = cellfun(@(x) ! isempty (x) && all (x(:)!=0), s_idx); + endif endif endif endif @@ -207,22 +224,17 @@ endfunction %!demo -%! A = rand(1000,2); -%! B = [(0:.2:1)',0.5*ones(6,1)]; -%! [LIA,LocAllB] = ismembertol(B, A, 0.1, 'ByRows', true, 'OutputAllIndices', true, 'DataScale', [1,Inf]); -%! hold on -%! plot(B(:,1),B(:,2),'x') -%! for k = 1:length(LocAllB) -%! plot(A(LocAllB{k},1), A(LocAllB{k},2),'.'); +%! ## Group random data +%! A = rand (1000, 2); +%! B = [(0:.2:1).', 0.5*ones(6,1)]; +%! [LIA, LocAllB] = ismembertol (B, A, 0.1, 'ByRows', true, 'OutputAllIndices', true, 'DataScale', [1,Inf]); +%! plot (B(:,1), B(:,2), 'x'); +%! hold on +%! for k = 1:length (LocAllB) +%! plot (A(LocAllB{k},1), A(LocAllB{k},2), '.'); %! endfor %!assert (isempty (ismembertol ([], [1, 2])), true) -%!fail ("ismembertol ([], {1, 2})") -%!fail ("ismembertol ({[]}, {1, 2})") -%!fail ("ismembertol ({}, {1, 2})") -%!fail ("ismembertol ({1}, {'1', '2'})") -%!fail ("ismembertol ({'1'}, {'1' '2'},'ByRows',true)") -%!fail ("ismembertol ([1 2 3], [5 4 3 1], 'ByRows',true)") %!test %! [result, s_idx] = ismembertol ([1; 2], []); @@ -231,7 +243,7 @@ %!test %! [result, s_idx] = ismembertol ([], [1, 2]); -%! assert (result, logical ( [] )); +%! assert (result, logical ([])); %! assert (s_idx, []); %!test @@ -241,13 +253,13 @@ %!test %! [result, s_idx] = ismembertol ([1 6], [1 2 3 4 5 1 6 1]); -%! assert (result, [true true]); +%! assert (result, [true, true]); %! assert (s_idx(2), 7); %!test %! [result, s_idx] = ismembertol ([3,10,1], [0,1,2,3,4,5,6,7,8,9]); -%! assert (result, [true false true]); -%! assert (s_idx, [4 0 2]); +%! assert (result, [true, false, true]); +%! assert (s_idx, [4, 0, 2]); %!test %! [result, s_idx] = ismembertol ([1:3; 5:7; 4:6], [0:2; 1:3; 2:4; 3:5; 4:6], "ByRows", true); @@ -255,16 +267,17 @@ %! assert (s_idx, [2; 0; 5]); %!test -%! [result, s_idx] = ismembertol ([1.1,1.2,1.3; 2.1,2.2,2.3; 10,11,12], [1.1,1.2,1.3; 10,11,12; 2.12,2.22,2.32], "ByRows", true); +%! [result, s_idx] = ismembertol ([1.1,1.2,1.3; 2.1,2.2,2.3; 10,11,12], ... +%! [1.1,1.2,1.3; 10,11,12; 2.12,2.22,2.32], "ByRows", true); %! assert (result, [true; false; true]); %! assert (s_idx, [1; 0; 2]); %!test %! [result, s_idx] = ismembertol ([1:3; 5:7; 4:6; 0:2; 1:3; 2:4], [1:3], "ByRows", true); -%! assert (result, logical ([1 0 0 0 1 0]')); -%! assert (s_idx, [1 0 0 0 1 0]'); +%! assert (result, logical ([1 0 0 0 1 0].')); +%! assert (s_idx, [1 0 0 0 1 0].'); -%!test +%!test %! [tf, s_idx] = ismembertol ([5, 4-3j, 3+4j], [5, 4-3j, 3+4j]); %! assert (tf, logical ([1 1 1])); %! assert (s_idx, [1 2 3]); @@ -307,10 +320,16 @@ %!test %! [tf, s_idx] = ismembertol ([1:10] + 0.01 * (rand (1,10) - 0.5), [1:10], 0.01); -%! assert (tf, logical ([1:10])); +%! assert (tf, true (1, 10)); %! assert (s_idx, [1:10]); ## Test input validation %!error ismembertol () %!error ismembertol (1) -%!error ismembertol (1,2,3,4) +%!error ismembertol (1,2,3,4) +%!error ismembertol ([], {1, 2}) +%!error ismembertol ({[]}, {1, 2}) +%!error ismembertol ({}, {1, 2}) +%!error ismembertol ({1}, {'1', '2'}) +%!error ismembertol ({'1'}, {'1', '2'}, 'ByRows', true) +%!error ismembertol ([1 2 3], [5 4 3 1], 'ByRows', true) diff -r 09f9b8f663fb -r 0eaa354b7ed1 scripts/set/module.mk --- a/scripts/set/module.mk Fri Jun 02 10:32:24 2023 -0300 +++ b/scripts/set/module.mk Sun Jul 16 13:13:45 2023 +0200 @@ -9,6 +9,7 @@ %reldir%/.oct-config \ %reldir%/intersect.m \ %reldir%/ismember.m \ + %reldir%/ismembertol.m \ %reldir%/powerset.m \ %reldir%/setdiff.m \ %reldir%/setxor.m \