1########################################################################
2##
3## Copyright (C) 2000-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn {} {} isequaln (@var{x1}, @var{x2}, @dots{})
28## Return true if all of @var{x1}, @var{x2}, @dots{} are equal under the
29## additional assumption that NaN == NaN (no comparison of NaN placeholders
30## in dataset).
31## @seealso{isequal}
32## @end deftypefn
33
34## Algorithm:
35##
36## 1. Verify the class of x.
37##    a. All objects are of the same class
38##    b. All objects are of a generic "numeric" class which includes
39##       numeric, logical, and character arrays
40## 2. Verify size of all objects match.
41## 3. Convert objects to struct, and then compare as stated below.
42## 4. For each argument after x, compare it for equality with x:
43##    a. char       compare each member with strcmp
44##    b. numeric    compare each member with '==', and assume NaN == NaN
45##    c. struct     compare number of fieldnames, value of fieldnames,
46##                  and then each field with isequaln (recursive)
47##    d. cellstr    compare each cellstr member with strcmp
48##    e. cell       compare each member with isequaln (recursive)
49##    f. fcn_handle compare using overloaded "eq" operator
50
51function t = isequaln (x, varargin)
52
53  if (nargin < 2)
54    print_usage ();
55  endif
56
57  nvarargin = nargin - 1;
58  two_args = (nvarargin == 1);  # Optimization for base case of just 2 args
59
60  if (two_args)
61    y = varargin{1};  # alias y to second input for comparison
62  endif
63
64  ############################################################
65  ## Generic tests for equality
66
67  ## All arguments must either be of the same class,
68  ##  or they must be "numeric" values.
69  if (two_args)
70    t = (strcmp (class (x), class (y))
71         || ((isreal (x) || iscomplex (x)) && (isreal (y) || iscomplex (y))));
72  else
73    t = (all (cellfun ("isclass", varargin, class (x)))
74         || ((isreal (x) || iscomplex (x))
75             && all (cellfun ("isreal", varargin)
76                     | cellfun ("isnumeric", varargin))));
77  endif
78
79  ## Test that everything is the same size (which also tests dimensions)
80  if (t)
81    t = size_equal (x, varargin{:});
82  endif
83
84  ## From here on, compare any objects as if they were structures.
85  if (t && isobject (x))
86    ## Locally suppress class-to-struct warning.  We know what we are doing.
87    warning ("off", "Octave:classdef-to-struct", "local");
88    x = builtin ("struct", x);
89    if (two_args)
90      clear y;  # break link to existing variable
91      varargin(1) = builtin ("struct", varargin{1});
92      y = varargin{1};  # re-alias y to second input
93    else
94      for i = 1:nvarargin
95        varargin(i) = builtin ("struct", varargin{i});
96      endfor
97    endif
98  endif
99
100  ############################################################
101  ## Check individual classes.
102
103  if (t)
104    if (two_args)
105
106      if (ischar (x) && ischar (y))
107        ## char type.  Optimization, strcmp is ~35% faster than '==' operator.
108        t = strcmp (x, y);
109
110      elseif (isreal (x) || iscomplex (x))
111        ## general "numeric" type.  Use '==' operator.
112        m = (x == y);
113        t = all (m(:));
114
115        if (! t && isfloat (x) && isfloat (y))
116          t = isnan (x(! m)) && isnan (y(! m));
117        endif
118
119      elseif (isstruct (x))
120        ## struct type.  Compare # of fields, fieldnames, then field values.
121
122        ## Test number of fields are equal.
123        t = (numfields (x) == numfields (y));
124
125        ## Test that all the field names are equal.
126        if (t)
127          s_fnm_x = sort (fieldnames (x));
128          t = all (strcmp (s_fnm_x, sort (fieldnames (y))));
129        endif
130
131        ## Test that all field values are equal.  Slow because of recursion.
132        if (t)
133          if (isscalar (x))
134            for fldnm = s_fnm_x.'
135              t = isequaln (x.(fldnm{1}), y.(fldnm{1}));
136              if (! t)
137                break;
138              endif
139            endfor
140          else
141            ## struct arrays have to have the contents of each field wrapped
142            ## in a cell since it expands to a collection of values.
143            for fldnm = s_fnm_x.'
144              t = isequaln ({x.(fldnm{1})}, {y.(fldnm{1})});
145              if (! t)
146                break;
147              endif
148            endfor
149          endif
150        endif
151
152      elseif (iscellstr (x) && iscellstr (y))
153        ## cellstr type.  Optimization over cell type by using strcmp.
154        ## FIXME: It would be faster to use strcmp on whole cellstr arrays,
155        ## but bug #51412 needs to be fixed.  Instead, time/space trade-off.
156        ## Convert to char (space) for faster processing with strcmp (time).
157        t = strcmp (char (x), char (y));
158
159      elseif (iscell (x))
160        ## cell type.  Check that each element of a cell is equal.  Slow.
161        n = numel (x);
162        idx = 1;
163        while (t && idx <= n)
164          t = isequaln (x{idx}, y{idx});
165          idx += 1;
166        endwhile
167
168      elseif (is_function_handle (x))
169        ## function type.  Use '==' operator which is overloaded.
170        t = (x == y);
171
172      else
173        error ("isequaln: Impossible to reach code.  File a bug report.");
174
175      endif
176
177    else  ## More than two args.  This is going to be slower in general.
178
179      if (ischar (x) && all (cellfun ("isclass", varargin, "char")))
180        ## char type.  Optimization, strcmp is ~35% faster than '==' operator.
181        idx = 1;
182        while (t && idx <= nvarargin)
183          t = strcmp (x, varargin{idx});
184          idx += 1;
185        endwhile
186
187      elseif (isreal (x) || iscomplex (x))
188        ## general "numeric" type.  Use '==' operator.
189
190        idx = 1;
191        while (t && idx <= nvarargin)
192          y = varargin{idx};
193          m = (x == y);
194          t = all (m(:));
195
196          if (! t && isfloat (x) && isfloat (y))
197            t = isnan (x(! m)) && isnan (y(! m));
198          endif
199
200          idx += 1;
201        endwhile
202
203      elseif (isstruct (x))
204        ## struct type.  Compare # of fields, fieldnames, then field values.
205
206        ## Test number of fields are equal.
207        fnm_x = fieldnames (x);
208        n = numel (fnm_x);
209        fnm_v = cellfun ("fieldnames", varargin, "uniformoutput", false);
210        t = all (n == cellfun ("numel", fnm_v));
211
212        ## Test that all the field names are equal.
213        if (t)
214          fnm_x = sort (fnm_x);
215          idx = 1;
216          while (t && idx <= nvarargin)
217            ## Allow the fieldnames to be in a different order.
218            t = all (strcmp (fnm_x, sort (fnm_v{idx})));
219            idx += 1;
220          endwhile
221        endif
222
223        ## Test that all field values are equal.  Slow because of recursion.
224        if (t)
225          args = cell (1, 1 + nvarargin);
226          if (isscalar (x))
227            for fldnm = fnm_x.'
228              args{1} = x.(fldnm{1});
229              for argn = 1:nvarargin
230                args{argn+1} = varargin{argn}.(fldnm{1});
231              endfor
232
233              t = isequaln (args{:});
234
235              if (! t)
236                break;
237              endif
238            endfor
239          else
240            ## struct arrays have to have the contents of each field wrapped
241            ## in a cell since it expands to a collection of values.
242            for fldnm = fnm_x.'
243              args{1} = { x.(fldnm{1}) };
244              for argn = 1:nvarargin
245                args{argn+1} = { varargin{argn}.(fldnm{1}) };
246              endfor
247
248              t = isequaln (args{:});
249
250              if (! t)
251                break;
252              endif
253            endfor
254          endif
255        endif
256
257      elseif (iscellstr (x) && all (cellfun (@iscellstr, varargin)))
258        ## cellstr type.  Optimization over cell type by using strcmp.
259        ## FIXME: It would be faster to use strcmp on whole cellstr arrays,
260        ## but bug #51412 needs to be fixed.  Instead, time/space trade-off.
261        ## Convert to char (space) for faster processing with strcmp (time).
262        idx = 1;
263        x = char (x);
264        while (t && idx <= nvarargin)
265          t = strcmp (x, char (varargin{idx}));
266          idx += 1;
267        endwhile
268
269      elseif (iscell (x))
270        ## cell type.  Check that each element of a cell is equal.  Slow.
271        n = numel (x);
272        args = cell (1, 1 + nvarargin);
273        idx = 1;
274        while (t && idx <= n)
275          args(1) = x{idx};
276          args(2:end) = [cellindexmat(varargin, idx){:}];
277
278          t = isequaln (args{:});
279
280          idx += 1;
281        endwhile
282
283      elseif (is_function_handle (x))
284        ## function type.  Use '==' operator which is overloaded.
285        t = all (cellfun ("eq", {x}, varargin));
286
287      else
288        error ("isequaln: Impossible to reach code.  File a bug report.");
289
290      endif
291
292    endif
293  endif
294
295  t = full (t);  # Always return full logical value for Matlab compatibility.
296
297endfunction
298
299
300## test for equality
301%!assert (isequaln (1,1), true)
302%!assert (isequaln (1,1,1), true)
303%!assert (isequaln ({1,2,NaN,4}, {1,2,NaN,4}), true)
304%!assert (isequaln ({1,2,NaN,4}, {1,2,NaN,4}, {1,2,NaN,4}), true)
305%!assert (isequaln ([1,2,NaN,4], [1,2,NaN,4]), true)
306%!assert (isequaln ([1,2,NaN,4], [1,2,NaN,4], [1,2,NaN,4]), true)
307## test for inequality
308%!assert (isequaln (1,2), false)
309%!assert (isequaln (1,1,2), false)
310%!assert (isequaln ([1,2,NaN,4], [1,NaN,3,4]), false)
311%!assert (isequaln ([1,2,NaN,4], [1,2,NaN,4], [1,NaN,3,4]), false)
312%!assert (isequaln ([1,2,NaN,4], [1,2,3,4]), false)
313%!assert (isequaln ([1,2,NaN,4], [1,2,NaN,4], [1,2,3,4]), false)
314## test for equality (struct)
315%!shared st
316%! st = struct ("a",NaN,"b",2);
317%!assert (isequaln (st, st), true)
318%!assert (isequaln (st, st, st), true)
319
320## Input validation
321%!error isequaln ()
322%!error isequaln (1)
323