changeset 31942:7782d1ead0a0

Replace perms.m with new perms.cc (bugs 63962 and 63965) The perms() function was previously an Octave m-file. This edit replaces it with the faster perms.cc C++ function for performance (bug 63962), using std::next_permutation from the C++ STL. Incidentally, this new function also correctly returns unique permutations for cells, thereby fixing bug 63965. * scripts/specfun/perms.m: Delete old m-file. * scripts/specfun/module.mk: Remove m-file from build system. * libinterp/corefcn/perms.cc: Add new C++ function. * libinterp/corefcn/module.mk: Add perms.cc to build system.
author Hendrik Koerner <koerhen@web.de>
date Tue, 28 Mar 2023 11:52:15 -0400
parents 8e82a7fc21aa
children c90de146a9ed
files libinterp/corefcn/module.mk libinterp/corefcn/perms.cc scripts/specfun/module.mk scripts/specfun/perms.m
diffstat 4 files changed, 401 insertions(+), 239 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/module.mk	Mon Mar 27 13:04:48 2023 -0400
+++ b/libinterp/corefcn/module.mk	Tue Mar 28 11:52:15 2023 -0400
@@ -235,6 +235,7 @@
   %reldir%/ordqz.cc \
   %reldir%/ordschur.cc \
   %reldir%/pager.cc \
+  %reldir%/perms.cc \
   %reldir%/pinv.cc \
   %reldir%/pow2.cc \
   %reldir%/pr-flt-fmt.cc \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/perms.cc	Tue Mar 28 11:52:15 2023 -0400
@@ -0,0 +1,400 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2023 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined(HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <algorithm>
+
+#include "defun.h"
+#include "error.h"
+#include "errwarn.h"
+#include "ovl.h"
+
+OCTAVE_BEGIN_NAMESPACE (octave)
+
+static inline double
+Factorial (octave_idx_type n)
+{
+  double ret = 1;
+  for (octave_idx_type i = 2; i <= n; i++)
+    ret *= i;
+  return ret;
+}
+
+//
+// Use C++ template to cater for the different octave array classes.
+//
+template <typename T>
+static inline Array<T>
+GetPerms (const Array<T> &ar_in, bool uniq_v, bool do_sort = true)
+{
+  octave_idx_type m = ar_in.numel ();
+  double nr = Factorial (m);
+
+  // Setup index vector filled from 0..m-1
+  int myvidx[m];
+  for (int i = 0; i < m; i++)
+    myvidx[i] = i;
+
+  // Interim array to sort ar_in for octave sort order and to implement
+  // "unique".
+  Array<T> ar (ar_in);
+
+  if (uniq_v)
+    {
+      ar = ar.sort (ar.dims () (1) > ar.dims () (0) ? 1 : 0, ASCENDING);
+      const T *Ar = ar.data ();
+      int ctr = 0;
+      int N_el = 1;
+
+      // Number of same elements where we need to remove permutations
+      // Number of unique permutations is n! / (n_el1! * n_el2! * ...)
+      for (octave_idx_type i = 0; i < m - 1; i++)
+        {
+          myvidx[i] = ctr;
+          if (Ar[i + 1] != Ar[i])
+            {
+              nr /= Factorial (N_el);
+              ctr = i + 1;  // index of next different element
+              N_el = 1;
+            }
+          else
+            N_el++;
+        }
+      myvidx[m - 1] = ctr;
+      nr /= Factorial (N_el);
+    }
+  else if (do_sort)
+    {
+      ar = ar.sort (ar.dims () (1) > ar.dims () (0) ? 1 : 0, ASCENDING);
+    }
+
+  // Sort vector indices for inverse lexicographic order later.
+  std::sort (myvidx, myvidx + m, std::greater<int> ());
+
+  const T *Ar = ar.data ();
+
+  // Set up result array
+  octave_idx_type n = static_cast<octave_idx_type> (nr);
+  Array<T> res (dim_vector (n, m));
+  T *Res = res.fortran_vec ();
+
+  // Do the actual job
+  octave_idx_type i = 0;
+  std::sort (myvidx, myvidx + m, std::greater<int> ());
+  do
+    {
+      for (octave_idx_type j = 0; j < m; j++)
+        Res[i + j * n] = Ar[myvidx[j]];
+      i++;
+    }
+  while (std::next_permutation (myvidx, myvidx + m, std::greater<int> ()));
+
+  return res;
+}
+
+// Template for non-numerical types (e.g. Cell) without sorting.
+// The C++ compiler complains as the provided type octave_value does not
+// support the test of equality via '==' in the above template.
+
+template <typename T>
+static inline Array<T>
+GetPermsNoSort (const Array<T> &ar_in, bool uniq_v = false)
+{
+
+  octave_idx_type m = ar_in.numel ();
+  double nr = Factorial (m);
+
+  // Setup index vector filled from 0..m-1
+  int myvidx[m];
+  for (int i = 0; i < m; i++)
+    myvidx[i] = i;
+
+  const T *Ar = ar_in.data ();
+
+  if (uniq_v)
+    {
+      // Mutual Comparison using is_equal to detect duplicated values
+      int N_el = 1;
+      // Number of unique permutations is n! / (n_el1! * n_el2! * ...)
+      for (octave_idx_type i = 0; i < m - 1; i++)
+        {
+          for (octave_idx_type j = i + 1; j < m; j++)
+            {
+              if (myvidx[j] > myvidx[i] && Ar[i].is_equal (Ar[j]))
+                {
+                  myvidx[j] = myvidx[i];  // not yet processed...
+                  N_el++;
+                }
+              else
+                {
+                  nr /= Factorial (N_el);
+                  N_el = 1;
+                }
+            }
+        }
+      nr /= Factorial (N_el);
+    }
+
+  // Sort vector indices for inverse lexicographic order later.
+  std::sort (myvidx, myvidx + m, std::greater<int> ());
+
+  // Set up result array
+  octave_idx_type n = static_cast<octave_idx_type> (nr);
+  Array<T> res (dim_vector (n, m));
+  T *Res = res.fortran_vec ();
+
+  // Do the actual job
+  octave_idx_type i = 0;
+  do
+    {
+      for (octave_idx_type j = 0; j < m; j++)
+        Res[i + j * n] = Ar[myvidx[j]];
+      i++;
+    }
+  while (std::next_permutation (myvidx, myvidx + m, std::greater<int> ()));
+  return res;
+}
+
+DEFUN (perms, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{P} =} perms (@var{v})
+@deftypefnx {} {@var{P} =} perms (@var{v}, "unique")
+Generate all permutations of vector @var{v} with one row per permutation.
+
+Results are returned in inverse lexicographic order.  The result has size
+@code{factorial (@var{n}) * @var{n}}, where @var{n} is the length of
+@var{v}.  Any repeated elements are included in the output.
+
+If the optional argument @qcode{"unique"} is given then only unique
+permutations are returned, using less memory and generally taking less time
+than calling @code{unique (perms (@var{v}), "rows")}.
+
+Example 1
+
+@example
+@group
+perms ([1, 2, 3])
+@result{}
+3   2   1
+3   1   2
+2   3   1
+2   1   3
+1   3   2
+1   2   3
+@end group
+@end example
+
+Example 2
+
+@example
+@group
+perms ([1, 1, 2, 2], "unique")
+@result{}
+2   2   1   1
+2   1   2   1
+2   1   1   2
+1   2   2   1
+1   2   1   2
+1   1   2   2
+@end group
+@end example
+
+Programming Note: If the @qcode{"unique"} option is not used, the length of
+@var{v} should be no more than 10-12 to limit memory consumption.  Even with
+@qcode{"unique"}, there should be no more than 10-12 unique elements in
+@var{v}.
+@seealso{permute, randperm, nchoosek}
+
+@end deftypefn */)
+{
+  int nargin = args.length ();
+
+  if (nargin < 1 || nargin > 2)
+    print_usage ();
+
+  octave_value retval;
+
+  // Parameter check "unique"
+  bool uniq_v = false;
+  if (nargin == 2)
+    {
+      const charMatrix opt = args (1).char_matrix_value ();
+      const char *str = opt.data ();
+      if (std::string (str, opt.cols ()) != "unique")
+        {
+          error ("perms: option must be the string \"unique\".");
+        }
+      uniq_v = true;
+    }
+
+  if (! (args (0).is_matrix_type () || args (0).is_range ()
+      || args (0).iscell () || args (0).is_scalar_type ()
+      || args (0).isstruct ()))
+    {
+      error ("perms: INPUT must be a matrix, a range, a cell array, "
+             "a struct or a scalar.");
+    }
+
+  std::string clname = args (0).class_name ();
+
+  // Execute main permutation code for the different classes
+  if (clname == "double")
+    retval = GetPerms<double> (args (0).array_value (), uniq_v);
+  else if (clname == "single")
+    retval = GetPerms<float> (args (0).float_array_value (), uniq_v);
+  else if (clname == "logical")
+    retval = GetPerms<bool> (args (0).bool_array_value (), uniq_v, false);
+    // Do not sort logicals by value as the other numerical data types.
+    // This reflects past design decisions using the order positioning
+    // for logicals.  See the discussion in
+    // https://savannah.gnu.org/bugs/?50426 and 
+    // https://savannah.gnu.org/bugs/?63962 for context.
+  else if (clname == "char")
+    retval = GetPerms<char> (args (0).char_array_value (), uniq_v);
+  else if (clname == "int8")
+    retval = GetPerms<octave_int8> (args (0).int8_array_value (), uniq_v);
+  else if (clname == "int16")
+    retval = GetPerms<octave_int16> (args (0).int16_array_value (), uniq_v);
+  else if (clname == "int32")
+    retval = GetPerms<octave_int32> (args (0).int32_array_value (), uniq_v);
+  else if (clname == "int64")
+    retval = GetPerms<octave_int64> (args (0).int64_array_value (), uniq_v);
+  else if (clname == "uint8")
+    retval = GetPerms<octave_uint8> (args (0).uint8_array_value (), uniq_v);
+  else if (clname == "uint16")
+    retval = GetPerms<octave_uint16> (args (0).uint16_array_value (), uniq_v);
+  else if (clname == "uint32")
+    retval = GetPerms<octave_uint32> (args (0).uint32_array_value (), uniq_v);
+  else if (clname == "uint64")
+    retval = GetPerms<octave_uint64> (args (0).uint64_array_value (), uniq_v);
+  else if (clname == "cell")
+    retval = GetPermsNoSort<octave_value> (args (0).cell_value (), uniq_v);
+  else if (clname == "struct")
+    {
+      const octave_map map_in (args (0).map_value ());
+      string_vector fn = map_in.fieldnames ();
+      if (fn.numel () == 0 && map_in.numel () != 0)
+        {
+          octave_scalar_map out;
+          retval = out;
+        }
+      else
+        {
+          octave_map out;
+          if (fn.numel () == 0)
+            {
+              out = octave_map (dim_vector (1, 0));
+            }
+          else
+            {
+              for (octave_idx_type i = 0; i < fn.numel (); i++)
+                {
+                  out.assign (fn (i), GetPermsNoSort<octave_value>
+                                      (map_in.contents (fn (i)), uniq_v));
+                }
+            }
+          retval = out;
+        }
+    }
+  else  // none of the above class criteria were met
+    {
+      warning ("perms: unable to permute for class %s", clname.c_str ());
+      // retval stays empty 
+    }
+  return ovl (retval);
+}
+
+/*
+
+%!assert (rows (perms (1:6)), factorial (6))
+%!assert (perms (pi), pi)
+%!assert (perms ([pi, e]), [pi, e; e, pi])
+%!assert (perms ([1,2,3]), [3,2,1;3,1,2;2,3,1;2,1,3;1,3,2;1,2,3])
+%!assert (perms (1:5), perms ([2 5 4 1 3]'))
+%!assert (perms ("abc"), char ("cba", "cab", "bca", "bac", "acb", "abc"))
+%!assert (perms ("fobar"), sortrows (unique (perms ("fobar"), "rows"), -(1:5)))
+%!assert (unique (perms (1:5)(:))', 1:5)
+%!assert (perms (int8 (1:4)), int8 (perms (1:4)))
+
+%!assert (sortrows (perms ("abb", "unique")), ["abb"; "bab"; "bba"])
+%!assert (size (perms ([1 1 1 1 2 2 2 3 3], "unique")), [1260 9])
+%!assert (size (perms (int8([1 1 1 1 1 2 2 2 2 3 3 3]), "unique")), [27720 12])
+
+## Should work for any array type, such as cells and structs,
+## and not only for numeric data.
+
+%!assert <*52431> (perms ({1}), {1})
+%!assert <*52431> (perms ({0.1, "foo"}), {"foo", 0.1; 0.1, "foo"})
+%!assert <*52431> (perms ({"foo", 0.1}), {0.1, "foo"; "foo", 0.1})
+%!assert <*52431> (perms ({"foo"; 0.1}), {0.1, "foo"; "foo", 0.1})
+%!assert <*52431> (perms ({0.1; "foo"}), {"foo", 0.1; 0.1, "foo"})
+%!assert <*52431> (perms ({"foo", "bar"}), {"bar", "foo"; "foo", "bar"})
+%!assert <*52431> (perms ({"bar", "foo"}), {"foo", "bar"; "bar", "foo"})
+%!
+%!assert <*52431> (perms (struct ()), struct ())
+%!assert <*52431> (perms (struct ("foo", {1, 2})),
+%!                struct ("foo", {2, 1; 1, 2}))
+%!assert <*52431> (perms (struct ("foo", {1, 2}, "bar", {3, 4})),
+%!                struct ("foo", {2, 1; 1, 2}, "bar", {4, 3; 3, 4}))
+
+## Also sort logical input with order dependent on the input order and
+## not their values.
+
+%!assert <*52431> (perms (logical ([1 0])), logical ([0 1;, 1 0]))
+%!assert <*52431> (perms (logical ([0 1])), logical ([1 0; 0 1]))
+%!assert <*52431> (perms (logical ([0 1 0])),
+%!                logical ([0 1 0; 0 0 1; 1 0 0; 1 0 0; 0 0 1; 0 1 0]))
+%!assert <*52431> (perms (logical ([0 1 1])),
+%!                logical ([1 1 0; 1 0 1; 1 1 0; 1 0 1; 0 1 1; 0 1 1]))
+
+%!assert <*52432> (perms ([]), reshape ([], 1, 0))
+%!assert <*52432> (perms (single ([])), reshape (single ([]), 1, 0))
+%!assert <*52432> (perms (int8 ([])), reshape (int8 ([]), 1, 0))
+%!assert <*52432> (perms ({}), cell (1, 0))
+
+%!test <*52432>
+%! s = struct ();
+%! s(1) = [];
+%! assert (perms (reshape (s, 0, 0)), reshape (s, 1, 0));
+%! assert (perms (reshape (s, 0, 1)), reshape (s, 1, 0));
+
+## test if "unique" works also for cell arrays
+%!assert <*63965> (perms ({"foo"; "foo"}, "unique"), {"foo", "foo"})
+%!assert <*63965> (perms ({"foo", "foo", "bar"}, "unique"), ...
+%!                        {"bar", "foo", "foo";
+%!                         "foo", "bar", "foo";
+%!                         "foo", "foo", "bar"})
+
+## Test input validation
+%!error <Invalid call> perms ()
+%!error <option must be the string "unique"> perms (1:5, "foobar")
+%!error <option must be the string "unique"> perms (1:5, {"foo"})
+
+*/
+
+OCTAVE_END_NAMESPACE (octave)
--- a/scripts/specfun/module.mk	Mon Mar 27 13:04:48 2023 -0400
+++ b/scripts/specfun/module.mk	Tue Mar 28 11:52:15 2023 -0400
@@ -18,7 +18,6 @@
   %reldir%/legendre.m \
   %reldir%/nchoosek.m \
   %reldir%/nthroot.m \
-  %reldir%/perms.m \
   %reldir%/primes.m \
   %reldir%/reallog.m \
   %reldir%/realpow.m \
--- a/scripts/specfun/perms.m	Mon Mar 27 13:04:48 2023 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-########################################################################
-##
-## Copyright (C) 2001-2023 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{P} =} perms (@var{v})
-## @deftypefnx {} {@var{P} =} perms (@var{v}, "unique")
-## Generate all permutations of vector @var{v} with one row per permutation.
-##
-## Results are returned in inverse lexicographic order.  The result has size
-## @code{factorial (@var{n}) * @var{n}}, where @var{n} is the length of
-## @var{v}.  Any repeated elements are included in the output.
-##
-## If the optional argument @qcode{"unique"} is given then only unique
-## permutations are returned, using less memory and generally taking less time
-## than calling @code{unique (perms (@var{v}), "rows")}.
-##
-## Example 1
-##
-## @example
-## @group
-## perms ([1, 2, 3])
-## @result{}
-##   3   2   1
-##   3   1   2
-##   2   3   1
-##   2   1   3
-##   1   3   2
-##   1   2   3
-## @end group
-## @end example
-##
-## Example 2
-##
-## @example
-## @group
-## perms ([1, 1, 2, 2], "unique")
-## @result{}
-##   2   2   1   1
-##   2   1   2   1
-##   2   1   1   2
-##   1   2   2   1
-##   1   2   1   2
-##   1   1   2   2
-## @end group
-## @end example
-##
-## Programming Note: If the @qcode{"unique"} option is not used, the length of
-## @var{v} should be no more than 10-12 to limit memory consumption.  Even with
-## @qcode{"unique"}, there should be no more than 10-12 unique elements in
-## @var{v}.
-## @seealso{permute, randperm, nchoosek}
-## @end deftypefn
-
-## FIXME: In principle it should be more efficient to do indexing using uint8
-## type.  However, benchmarking shows doubles are faster.  If this changes in
-## a later version of Octave the index variables here can be made uint8.
-
-function P = perms (v, opt)
-
-  if (nargin < 1)
-    print_usage ();
-  endif
-
-  unique_v = false;
-  if (nargin == 2)
-    if (! strcmpi (opt, "unique"))
-      error ('perms: option must be the string "unique"');
-    endif
-    unique_v = true;
-  endif
-
-  v = v(:).';  # convert to row vector
-  if (isnumeric (v) || ischar (v))
-    ## Order of output is only dependent on the actual values for
-    ## character and numeric arrays.
-    v = sort (v, "ascend");
-  endif
-
-  n = numel (v);
-  if (n < 4)    # special cases for small n
-    switch (n)
-      case 0
-        P = reshape (v, 1, 0);
-      case 1
-        P = v;
-      case 2
-        P = [v([2 1]);v];
-      case 3
-        P = v([3 2 1; 3 1 2; 2 3 1; 2 1 3; 1 3 2; 1 2 3]);
-    endswitch
-    if (unique_v)
-      P = unique (P, "rows");
-    endif
-
-  elseif (! unique_v)
-    ## FIXME: Brief explanation of the algorithm being used would be useful.
-    v = v(end:-1:1);
-    n-= 1;
-
-    idx = zeros (factorial (n), n);
-    idx(1:6, n-2:n) = [1, 2, 3;1, 3, 2;2, 1, 3;2, 3, 1;3, 1, 2;3, 2, 1]+(n-3);
-    f = 2;    # jump-start for efficiency with medium n
-    for j = 3:n-1
-      b = 1:n;
-      f *= j;
-      perm = idx(1:f, n-(j-1):n);
-      idx(1:(j+1)*f, n-j) = (n-j:n)(ones (f, 1),:)(:);
-      for i = 0:j
-        b(i+n-j) -= 1;
-        idx((1:f)+i*f, n-(j-1):n) = b(perm);
-      endfor
-    endfor
-
-    n += 1;
-    f *= n-1;
-    P = v(1)(ones (factorial (n), n));
-    P(:,1) = v(ones (f, 1),:)(:);
-
-    for i = 1:n
-      b = v([1:i-1 i+1:n]);
-      P((1:f)+(i-1)*f, 2:end) = b(idx);
-    endfor
-
-  else  # unique permutations
-    [v, ~, j] = unique (v);
-    h = accumarray (j, 1)';
-    idx = m_perms (h);
-    P = v(sortrows (idx', -(1:rows(idx))));
-  endif
-
-endfunction
-
-function out_perms = m_perms (multis)
-  ## FIXME: Brief explanation of the algorithm being used would be useful.
-
-  l = numel (multis);
-  if (l == 1)
-    out_perms = uint8 (ones (multis, 1));
-  else
-    p1 = m_perms (multis (1:floor (l/2)));
-    p2 = m_perms (multis (floor (l/2+1):l)) + max (p1(:, 1));
-    l1 = rows (p1);
-    l2 = rows (p2);
-    cp1 = columns (p1);
-    cp2 = columns (p2);
-
-    p = nchoosek (1:l1+l2, l1);
-    rp = rows (p);
-
-    ii = false (l1+l2, rp);
-    ii(p + (0:rp - 1)' * (l1 + l2)) = true;
-    out_perms = zeros (l1 + l2, cp1 * cp2 * rp, "uint8");
-    out_perms(repmat ( ii, cp1 * cp2, 1)(:)) = repmat (p1, cp2, rp)(:);
-    out_perms(repmat (!ii, cp1 * cp2, 1)(:)) = repmat (p2, 1, cp1 * rp)(:);
-  endif
-
-endfunction
-
-
-%!assert (rows (perms (1:6)), factorial (6))
-%!assert (perms (pi), pi)
-%!assert (perms ([pi, e]), [pi, e; e, pi])
-%!assert (perms ([1,2,3]), [3,2,1;3,1,2;2,3,1;2,1,3;1,3,2;1,2,3])
-%!assert (perms (1:5), perms ([2 5 4 1 3]'))
-%!assert (perms ("abc"), char ("cba", "cab", "bca", "bac", "acb", "abc"))
-%!assert (perms ("fobar"), sortrows (unique (perms ("fobar"), "rows"), -(1:5)))
-%!assert (unique (perms (1:5)(:))', 1:5)
-%!assert (perms (int8 (1:4)), int8 (perms (1:4)))
-
-%!assert (sortrows (perms ("abb", "unique")), ["abb"; "bab"; "bba"])
-%!assert (size (perms ([1 1 1 1 2 2 2 3 3], "unique")), [1260 9])
-%!assert (size (perms (int8([1 1 1 1 1 2 2 2 2 3 3 3]), "unique")), [27720 12])
-
-## Should work for any array type, such as cells and structs,
-## and not only for numeric data.
-
-%!assert <*52431> (perms ({1}), {1})
-%!assert <*52431> (perms ({0.1, "foo"}), {"foo", 0.1; 0.1, "foo"})
-%!assert <*52431> (perms ({"foo", 0.1}), {0.1, "foo"; "foo", 0.1})
-%!assert <*52431> (perms ({"foo"; 0.1}), {0.1, "foo"; "foo", 0.1})
-%!assert <*52431> (perms ({0.1; "foo"}), {"foo", 0.1; 0.1, "foo"})
-%!assert <*52431> (perms ({"foo", "bar"}), {"bar", "foo"; "foo", "bar"})
-%!assert <*52431> (perms ({"bar", "foo"}), {"foo", "bar"; "bar", "foo"})
-%!
-%!assert <*52431> (perms (struct ()), struct ())
-%!assert <*52431> (perms (struct ("foo", {1, 2})),
-%!                struct ("foo", {2, 1; 1, 2}))
-%!assert <*52431> (perms (struct ("foo", {1, 2}, "bar", {3, 4})),
-%!                struct ("foo", {2, 1; 1, 2}, "bar", {4, 3; 3, 4}))
-
-## Also sort logical input with order dependent on the input order and
-## not their values.
-
-%!assert <*52431> (perms (logical ([1 0])), logical ([0 1;, 1 0]))
-%!assert <*52431> (perms (logical ([0 1])), logical ([1 0; 0 1]))
-%!assert <*52431> (perms (logical ([0 1 0])),
-%!                logical ([0 1 0; 0 0 1; 1 0 0; 1 0 0; 0 0 1; 0 1 0]))
-%!assert <*52431> (perms (logical ([0 1 1])),
-%!                logical ([1 1 0; 1 0 1; 1 1 0; 1 0 1; 0 1 1; 0 1 1]))
-
-%!assert <*52432> (perms ([]), reshape ([], 1, 0))
-%!assert <*52432> (perms (single ([])), reshape (single ([]), 1, 0))
-%!assert <*52432> (perms (int8 ([])), reshape (int8 ([]), 1, 0))
-%!assert <*52432> (perms ({}), cell (1, 0))
-
-%!test <*52432>
-%! s = struct ();
-%! s(1) = [];
-%! assert (perms (reshape (s, 0, 0)), reshape (s, 1, 0));
-%! assert (perms (reshape (s, 0, 1)), reshape (s, 1, 0));
-
-## Test input validation
-%!error <Invalid call> perms ()
-%!error <option must be the string "unique"> perms (1:5, "foobar")
-%!error <option must be the string "unique"> perms (1:5, {"foo"})
-