# HG changeset patch # User Abdallah Elshamy # Date 1585512932 -7200 # Node ID d052c11d47c6289d1d809527562dc286f9a8f1ca # Parent 2898820403165254090f834bfdea182f4617aaba Add new functions startsWith and endsWith (bug #57041). * scripts/strings: Add startsWith.m and endsWith.m. * scripts/strings/module.mk: Add references to new files. * doc/interpreter/strings.txi: Add docstrings to manual. * NEWS: Add to list of new functions. * scripts/help/__unimplemented__.m: Remove the two functions. diff -r 289882040316 -r d052c11d47c6 NEWS --- a/NEWS Sun Apr 12 18:14:05 2020 -0700 +++ b/NEWS Sun Mar 29 22:15:32 2020 +0200 @@ -56,7 +56,9 @@ ### Alphabetical list of new functions added in Octave 7 +* `endsWith` * `rng` +* `startsWith` ### Deprecated functions and properties diff -r 289882040316 -r d052c11d47c6 doc/interpreter/strings.txi --- a/doc/interpreter/strings.txi Sun Apr 12 18:14:05 2020 -0700 +++ b/doc/interpreter/strings.txi Sun Mar 29 22:15:32 2020 +0200 @@ -416,6 +416,10 @@ @DOCSTRING(strncmpi) +@DOCSTRING(startsWith) + +@DOCSTRING(endsWith) + @node Manipulating Strings @section Manipulating Strings diff -r 289882040316 -r d052c11d47c6 scripts/help/__unimplemented__.m --- a/scripts/help/__unimplemented__.m Sun Apr 12 18:14:05 2020 -0700 +++ b/scripts/help/__unimplemented__.m Sun Mar 29 22:15:32 2020 +0200 @@ -727,7 +727,6 @@ "edges", "empty", "enableservice", - "endsWith", "enumeration", "eraseBetween", "eventlisteners", @@ -1185,7 +1184,6 @@ "ss2tf", "stack", "standardizeMissing", - "startsWith", "stats", "step", "stopasync", diff -r 289882040316 -r d052c11d47c6 scripts/strings/endsWith.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/strings/endsWith.m Sun Mar 29 22:15:32 2020 +0200 @@ -0,0 +1,191 @@ +######################################################################## +## +## Copyright (C) 2020 The Octave Project Developers +## +## See the file COPYRIGHT.md in the top-level directory of this +## distribution or . +## +## 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 +## . +## +######################################################################## + +## -*- texinfo -*- +## @deftypefn {} {@var{retval} =} endsWith (@var{str}, @var{pattern}) +## @deftypefnx {} {@var{retval} =} endsWith (@var{str}, @var{pattern}, "IgnoreCase", @var{confirm_ignore}) +## Checks that a cell array of strings ends with a pattern +## +## Return an array of logical values that indicates which strings in the cell +## array of strings --- or the string --- @var{str} ends with a string in the +## cell array of strings --- or the string --- @var{pattern}. +## +## If the parameter @qcode{"IgnoreCase"} is set to true, then the function +## will ignore the letter case of @var{str} and @var{pattern}. By default, the +## comparison is case sensitive. +## +## Examples: +## +## @example +## ## one string and one pattern while considering case +## endsWith ("hello", "lo") +## @result{} 1 +## +## ## one string and one pattern while ignoring case +## endsWith ("hello", "LO", "IgnoreCase", true) +## @result{} 1 +## +## ## multiple strings and multiple patterns while considering case +## endsWith (@{"tests.txt", "mydoc.odt", "myFunc.m", "results.pptx"@}, +## @{".docx", ".odt", ".txt"@}) +## @result{} 1 1 0 0 +## +## ## multiple strings and one pattern while considering case +## endsWith (@{"TESTS.TXT", "mydoc.odt", "result.txt", "myFunc.m"@}, +## ".txt", "IgnoreCase", false) +## @result{} 0 0 1 0 +## +## ## multiple strings and one pattern while ignoring case +## endsWith (@{"TESTS.TXT", "mydoc.odt", "result.txt", "myFunc.m"@}, +## ".txt", "IgnoreCase", true) +## @result{} 1 0 1 0 +## @end example +## +## @seealso{startsWith, strcmp, strcmpi} +## @end deftypefn + +function retval = endsWith (str, pattern, ignore_case, confirm_ignore) + if (! (nargin == 2 || nargin == 4)) + print_usage (); + endif + + ## check input str and pattern + if (! (iscellstr (str) || ischar (str)) + || ! (iscellstr (pattern) || ischar (pattern))) + error ("endsWith: arguments must be strings or cell arrays of strings"); + else + str = cellstr (str); + pattern = cellstr (pattern); + endif + + retval = zeros (size (str)); + + ## reverse str and pattern + for j = 1:numel (str) + str{j} = flip (str{j}); + endfor + for j = 1:numel (pattern) + pattern{j} = flip (pattern{j}); + endfor + + if (nargin == 2) + IgnoreFlag = false; + endif + + if (nargin == 4) + ## checks third input argument + if (! ischar (ignore_case) || isempty (ignore_case)) + error ('endsWith: third input must be "IgnoreCase"'); + endif + + ## to be compatible with MATLAB + ## (MATLAB accepts the command with "I", "i", "Ig", "IG" ..etc ) + if (! strncmpi (ignore_case, "IgnoreCase", length (ignore_case))) + error (['endsWith: invalid argument "%s"; ', ... + 'third input must be "IgnoreCase"'], ignore_case); + endif + + if (numel (confirm_ignore) != 1) + error ("endsWith: 'IgnoreCase' value must be a logical scalar"); + endif + + ## to be compatible with MATLAB + ## (MATLAB accepts the command with true, 5, 1 ..etc ) + try + IgnoreFlag = logical (confirm_ignore); + catch + error ("endsWith: 'IgnoreCase' value must be a logical scalar"); + end_try_catch + endif + + if (IgnoreFlag) + for j = 1:numel (pattern) + retval |= strncmpi (str, pattern{j}, length (pattern{j})); + endfor + else + for j = 1:numel (pattern) + retval |= strncmp (str, pattern{j}, length (pattern{j})); + endfor + endif + +endfunction + + +## Test simple use with one string and one pattern +%!assert (endsWith ("hello", "lo")) +%!assert (! endsWith ("hello", "LO")) +%!assert (endsWith ("hello", "LO", "i", 5)) # check compatibility with MATLAB +%!assert (! endsWith ("hello", "no")) + +## Test multiple strings with a single pattern +%!test +%! str = {"myFile.odt", "results.ppt", "myCode.m"; ... +%! "data-analysis.ppt", "foundations.txt", "data.odt"}; +%! pattern = ".odt"; +%! correct_ans = [true, false, false; false, false, true]; +%! assert (endsWith (str, pattern), correct_ans) + +## Test multiple strings with multiple patterns +%!test +%! str = {"tests.txt", "mydoc.odt", "myFunc.m", "results.pptx"}; +%! pattern = {".docx", ".odt", ".txt"}; +%! correct_ans = [true, true, false, false]; +%! assert (endsWith (str, pattern), correct_ans) + +## Test IgnoreCase +%!test +%! str = {"TESTS.TXT", "mydoc.odt", "result.txt", "myFunc.m"}; +%! pattern = ".txt"; +%! correct_ans_with_ignore = [true, false, true, false]; +%! correct_ans_without_ignore = [false, false, true, false]; +%! assert (endsWith (str, pattern, "IgnoreCase", true), ... +%! correct_ans_with_ignore) +%! assert (endsWith (str, pattern, "IgnoreCase", false), ... +%! correct_ans_without_ignore) +%! assert (endsWith (str, pattern, "I", 500), ... +%! correct_ans_with_ignore) # check compatibility with MATLAB +%! assert (endsWith (str, pattern, "iG", 0), ... +%! correct_ans_without_ignore) # check compatibility with MATLAB + +## Test error detection +%!error endsWith (152, "hi") + +%!error endsWith ("hello", 152) + +%!error <'IgnoreCase' value must be a logical scalar> ... +%! endsWith ("hello", "hi", "i", "true") + +%!error <'IgnoreCase' value must be a logical scalar> ... +%! endsWith ("hello", "hi", "i", [1, 2]) + +%!error ... +%! endsWith ("hello", "he", "ignire", 1) + +%!error ... +%! endsWith ("hello", "he", "IgnoreCasefd", 1) + +%!error endsWith ("hello", "he", "", 1) + +%!error endsWith ("hello", "he", 5, 1) diff -r 289882040316 -r d052c11d47c6 scripts/strings/module.mk --- a/scripts/strings/module.mk Sun Apr 12 18:14:05 2020 -0700 +++ b/scripts/strings/module.mk Sun Mar 29 22:15:32 2020 +0200 @@ -9,6 +9,7 @@ %reldir%/dec2base.m \ %reldir%/dec2bin.m \ %reldir%/dec2hex.m \ + %reldir%/endsWith.m \ %reldir%/erase.m \ %reldir%/hex2dec.m \ %reldir%/index.m \ @@ -20,6 +21,7 @@ %reldir%/ostrsplit.m \ %reldir%/regexptranslate.m \ %reldir%/rindex.m \ + %reldir%/startsWith.m \ %reldir%/str2num.m \ %reldir%/strcat.m \ %reldir%/strchr.m \ diff -r 289882040316 -r d052c11d47c6 scripts/strings/startsWith.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/strings/startsWith.m Sun Mar 29 22:15:32 2020 +0200 @@ -0,0 +1,183 @@ +######################################################################## +## +## Copyright (C) 2020 The Octave Project Developers +## +## See the file COPYRIGHT.md in the top-level directory of this +## distribution or . +## +## 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 +## . +## +######################################################################## + +## -*- texinfo -*- +## @deftypefn {} {@var{retval} =} startsWith (@var{str}, @var{pattern}) +## @deftypefnx {} {@var{retval} =} startsWith (@var{str}, @var{pattern}, "IgnoreCase", @var{confirm_ignore}) +## Checks that a cell array of strings starts with a pattern +## +## Return an array of logical values that indicates which strings in the cell +## array of strings --- or the string --- @var{str} starts with a string in the +## cell array of strings --- or the string --- @var{pattern} +## +## If the parameter @qcode{"IgnoreCase"} is set to true, then the function +## will ignore the letter case of @var{str} and @var{pattern}. By default, the +## comparison is case sensitive. +## +## Examples: +## +## @example +## ## one string and one pattern while considering case +## startsWith ("hello", "he") +## @result{} 1 +## +## ## one string and one pattern while ignoring case +## startsWith ("hello", "HE", "IgnoreCase", true) +## @result{} 1 +## +## ## multiple strings and multiple patterns while considering case +## startsWith (@{"lab work.pptx", "data.txt", "foundations.ppt"@}, +## @{"lab", "data"@}) +## @result{} 1 1 0 +## +## ## multiple strings and one pattern while considering case +## startsWith (@{"DATASHEET.ods", "data.txt", "foundations.ppt"@}, +## "data", "IgnoreCase", false) +## @result{} 0 1 0 +## +## ## multiple strings and one pattern while ignoring case +## startsWith (@{"DATASHEET.ods", "data.txt", "foundations.ppt"@}, +## "data", "IgnoreCase", true) +## @result{} 1 1 0 +## @end example +## +## @seealso{endsWith, strcmp, strcmpi} +## @end deftypefn + +function retval = startsWith (str, pattern, ignore_case, confirm_ignore) + if (! (nargin == 2 || nargin == 4)) + print_usage (); + endif + + ## check input str and pattern + if (! (iscellstr (str) || ischar (str)) + || ! (iscellstr (pattern) || ischar (pattern))) + error ("startsWith: arguments must be strings or cell arrays of strings"); + else + str = cellstr (str); + pattern = cellstr (pattern); + endif + + retval = zeros (size (str)); + + if (nargin == 2) + IgnoreFlag = false; + endif + + if (nargin == 4) + ## check third input argument + if (! ischar (ignore_case) || isempty (ignore_case)) + error ('startsWith: third input must be "IgnoreCase"'); + endif + + ## to be compatible with MATLAB + ## (MATLAB accepts the command with "I", "i", "Ig", "IG" ..etc ) + if (! strncmpi (ignore_case, "IgnoreCase", length (ignore_case))) + error (['startsWith: invalid argument "%s"; ', ... + 'third input must be "IgnoreCase"'], ignore_case); + endif + + if (! isscalar (confirm_ignore)) + error ("startsWith: 'IgnoreCase' value must be a logical scalar."); + endif + + ## to be compatible with MATLAB + ## (MATLAB accepts the command with true, 5, 1 ..etc ) + try + IgnoreFlag = logical (confirm_ignore); + catch + error ("startsWith: 'IgnoreCase' value must be a logical scalar"); + end_try_catch + endif + + if (IgnoreFlag) + for j = 1:numel (pattern) + retval |= strncmpi (str, pattern{j}, length (pattern{j})); + endfor + else + for j = 1:numel (pattern) + retval |= strncmp (str, pattern{j}, length (pattern{j})); + endfor + endif + +endfunction + + +## Test simple use with one string and one pattern +%!assert (startsWith ("hello", "he")) +%!assert (! startsWith ("hello", "HE")) +%!assert (startsWith ("hello", "HE", "i", 5)) # check compatibility with MATLAB +%!assert (! startsWith ("hello", "no")) + +## Test multiple strings with a single pattern +%!test +%! str = {"data science", "dataSheet.ods", "myFunc.m"; "foundations.ppt", ... +%! "results.txt", "myFile.odt"}; +%! pattern = "data"; +%! correct_ans = [true, true, false; false, false, false]; +%! assert (startsWith (str, pattern), correct_ans) + +## Test multiple strings with multiple patterns +%!test +%! str = {"lab work.pptx", "myFile.odt", "data.txt", "foundations.ppt"}; +%! pattern = {"lab", "data"}; +%! correct_ans = [true, false, true, false]; +%! assert (startsWith (str, pattern), correct_ans) + +## Test IgnoreCase +%!test +%! str = {"DATASHEET.ods", "myFile.odt", "data.txt", "foundations.ppt"}; +%! pattern = "data"; +%! correct_ans_with_ignore = [true, false, true, false]; +%! correct_ans_without_ignore = [false, false, true, false]; +%! assert (startsWith (str, pattern, "IgnoreCase", true), ... +%! correct_ans_with_ignore) +%! assert (startsWith (str, pattern, "IgnoreCase", false), ... +%! correct_ans_without_ignore) +%! assert (startsWith (str, pattern, "I", 500), ... +%! correct_ans_with_ignore) # check compatibility with MATLAB +%! assert (startsWith (str, pattern, "iG", 0), ... +%! correct_ans_without_ignore) # check compatibility with MATLAB + +## Test error detection +%!error startsWith (152, "hi") + +%!error startsWith ("hello", 152) + +%!error <'IgnoreCase' value must be a logical scalar> ... +%! startsWith ("hello", "hi", "i", "true") + +%!error <'IgnoreCase' value must be a logical scalar> ... +%! startsWith ("hello", "hi", "i", [1, 2]) + +%!error ... +%! startsWith ("hello", "he", "ignire", 1) + +%!error ... +%! startsWith ("hello", "he", "IgnoreCasefd", 1) + +%!error startsWith ("hello", "he", "", 1) + +%!error startsWith ("hello", "he", {5}, 1)