changeset 28212:d052c11d47c6

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.
author Abdallah Elshamy <abdallah.k.elshamy@gmail.com>
date Sun, 29 Mar 2020 22:15:32 +0200
parents 289882040316
children bb50407f9c60
files NEWS doc/interpreter/strings.txi scripts/help/__unimplemented__.m scripts/strings/endsWith.m scripts/strings/module.mk scripts/strings/startsWith.m
diffstat 6 files changed, 382 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- 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
 
--- 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
 
--- 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",
--- /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 <https://octave.org/copyright/>.
+##
+## 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{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 <must be strings or cell arrays of strings> endsWith (152, "hi")
+
+%!error <must be strings or cell arrays of strings> 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 <invalid argument "ignire"; third input must be> ...
+%!      endsWith ("hello", "he", "ignire", 1)
+
+%!error <invalid argument "IgnoreCasefd"; third input must be> ...
+%!      endsWith ("hello", "he", "IgnoreCasefd", 1)
+
+%!error <third input must be "IgnoreCase"> endsWith ("hello", "he", "", 1)
+
+%!error <third input must be "IgnoreCase"> endsWith ("hello", "he", 5, 1)
--- 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 \
--- /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 <https://octave.org/copyright/>.
+##
+## 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{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 <must be strings or cell arrays of strings> startsWith (152, "hi")
+
+%!error <must be strings or cell arrays of strings> 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 <invalid argument "ignire"; third input must be> ...
+%!      startsWith ("hello", "he", "ignire", 1)
+
+%!error <invalid argument "IgnoreCasefd"; third input must be> ...
+%!      startsWith ("hello", "he", "IgnoreCasefd", 1)
+
+%!error <third input must be "IgnoreCase"> startsWith ("hello", "he", "", 1)
+
+%!error <third input must be "IgnoreCase"> startsWith ("hello", "he", {5}, 1)