omdl  v1.0
OpenSCAD Mechanical Design Library
list_inspect.scad
Go to the documentation of this file.
1 //! List data type inspection.
2 /***************************************************************************//**
3  \file
4  \author Roy Allen Sutton
5  \date 2015-2023,2026
6 
7  \copyright
8 
9  This file is part of [omdl] (https://github.com/royasutton/omdl),
10  an OpenSCAD mechanical design library.
11 
12  The \em omdl is free software; you can redistribute it and/or modify
13  it under the terms of the [GNU Lesser General Public License]
14  (http://www.gnu.org/licenses/lgpl.html) as published by the Free
15  Software Foundation; either version 2.1 of the License, or (at
16  your option) any later version.
17 
18  The \em omdl is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  Lesser General Public License for more details.
22 
23  You should have received a copy of the GNU Lesser General Public
24  License along with the \em omdl; if not, write to the Free Software
25  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26  02110-1301, USA; or see <http://www.gnu.org/licenses/>.
27 
28  \details
29 
30  \amu_define group_name (List Inspection)
31  \amu_define group_brief (List inspection operations.)
32 
33  \amu_include (include/amu/doxyg_init_pd_gds_ipg.amu)
34 *******************************************************************************/
35 
36 // auto-tests (add to test results page)
37 /***************************************************************************//**
38  \amu_include (include/amu/validate_log.amu)
39  \amu_include (include/amu/validate_results.amu)
40 *******************************************************************************/
41 
42 // group(s) begin (test summary and includes-required)
43 /***************************************************************************//**
44  \amu_include (include/amu/doxyg_define_in_parent_open.amu)
45  \amu_include (include/amu/validate_summary.amu)
46  \amu_include (include/amu/includes_required.amu)
47 *******************************************************************************/
48 
49 // member-wide reference definitions
50 /***************************************************************************//**
51  \amu_define group_references
52  (
53  )
54 *******************************************************************************/
55 
56 // member-wide documentation and conventions
57 /***************************************************************************//**
58  \addtogroup \amu_eval(${group})
59  \details
60  \anchor \amu_eval(${group})_conventions
61  \par Conventions
62 
63  - Two-list parameters use \p v1 and \p v2.
64  - The decimal-places parameter is always \p p.
65  - almost_eq() applies a relative tolerance of \c eps (the library
66  epsilon constant) by default; \p p overrides the number of significant
67  decimal places used in the comparison.
68  - compare() establishes a total order across OpenSCAD types:
69  undef < number < boolean < string < list < range.
70  Within each type, the natural ordering applies (numeric for numbers,
71  lexicographic for strings, element-wise for lists).
72  - list_get_value() performs a linear key search; for large maps prefer
73  the map group functions which offer O(1) key lookup via search().
74 *******************************************************************************/
75 
76 //----------------------------------------------------------------------------//
77 // members
78 //----------------------------------------------------------------------------//
79 
80 //----------------------------------------------------------------------------//
81 // Iterable
82 //----------------------------------------------------------------------------//
83 
84 //! \name Iterable
85 //! @{
86 
87 //! Test to see if two numerical vectors are sufficiently equal.
88 /***************************************************************************//**
89  \param v1 <vector-n> The n-length vector 1.
90  \param v2 <vector-n> The n-length vector 2.
91  \param p <number> The numerical precision.
92 
93  \returns (1) <boolean> \c true when the distance between \p v1 and
94  \p v2 is less than \p d and \c false otherwise.
95  (2) Returns \c false when either \p v1 or \p v2 are not
96  numerical vectors of the same length.
97 
98  \details
99 
100  Can also compare two scalar numbers. To compare general lists of
101  non-numerical values see almost_eq().
102 *******************************************************************************/
103 function almost_eq_nv
104 (
105  v1,
106  v2,
107  p = 6
108 ) = !all_numbers(concat(v1, v2)) ? false // must be all numbers
109  : all_scalars(concat([v1], [v2])) ? // compare if scalars?
110  ( (sqrt((v1-v2)*(v1-v2)) * pow(10, p)) < 1 ) // -> compare as scalars
111  : !all_lists(concat([v1], [v2])) ? false // requires all lists or false
112  : (len(v1) != len(v2)) ? false // size must be equal
113  : ( (sqrt((v1-v2)*(v1-v2)) * pow(10, p)) < 1 ); // -> compare as vectors
114 
115 //! Test if all elements of two iterable values are sufficiently equal.
116 /***************************************************************************//**
117  \param v1 <iterable> An iterable data type value 1.
118  \param v2 <iterable> An iterable data type value 2.
119  \param p <number> The precision for numerical comparisons.
120 
121  \returns (1) <boolean> \c true when all elements of each iterable
122  value are \em sufficiently equal and \c false
123  otherwise.
124 
125  \details
126 
127  The iterable values can be of mixed data types. All numerical
128  comparisons are performed using the specified precision. All
129  non-numeric comparisons test for equality. When both \p v1 and \p
130  v2 are both numerical vectors, the function almost_eq_nv()
131  provides a more efficient test.
132 *******************************************************************************/
133 function almost_eq
134 (
135  v1,
136  v2,
137  p = 6
138 ) = all_numbers(concat(v1, v2)) ? almost_eq_nv(v1, v2, p) // all numerical
139  : all_scalars(concat([v1], [v2])) ? (v1 == v2) // all single values
140  : (is_string(v1) || is_string(v2)) ? (v1 == v2) // either is a string
141  : !all_iterables(concat([v1], [v2])) ? false // false if either not iterable
142  : !almost_eq(first(v1), first(v2), p) ? false // compare first elements
143  : almost_eq(tailn(v1), tailn(v2), p); // compare remaining elements
144 
145 //! Compare the sort order of any two values.
146 /***************************************************************************//**
147  \param v1 <value> The values 1.
148  \param v2 <value> The values 2.
149  \param s <boolean> Order ranges by their enumerated sum.
150 
151  \returns (1) <integer> An integer value.
152 
153  \details
154 
155  Return values (rv) table.
156 
157  rv | order of values
158  :-------:|:-------------------
159  \c -1 | `(v2 < v1)`
160  \c 0 | `(v2 == v1)`
161  \c +1 | `(v2 > v1)`
162 
163  The following table summarizes how data type values are ordered.
164 
165  order | type | \p s | intra-type ordering
166  :-----:|:----------:|:---------:|:--------------------------------------
167  1 | undef | | (singular)
168  2 | number | | numerical comparison
169  3 | boolean | | false < true
170  4 | string | | lexical comparison
171  5 | list | | compare (1) lengths, then (2) element-wise
172  6 | range | true | compare sum of range elements
173  6 | range | false | compare (1) lengths, then (2) element-wise
174 
175  When comparing two lists of equal length, the comparison continues
176  with successive elements until an ordering can be determined. Two
177  lists are equal when all elements have been compared and no
178  ordering has been determined.
179 
180  \warning The performance of element-wise comparisons degrades
181  exponentially with list size and the sum of a range may
182  exceeded the intermediate variable storage capacity for
183  long ranges.
184 *******************************************************************************/
185 function compare
186 (
187  v1,
188  v2,
189  s = true
190 ) = let( v2_nd = is_undef(v2) )
191  is_undef(v1) ?
192  (
193  v2_nd ? 0
194  : 1 // others are greater
195  )
196  // v1 a number
197  : let( v2_in = is_number(v2) )
198  is_number(v1) ?
199  (
200  v2_nd ? -1
201  : v2_in ?
202  (
203  (v1 > v2) ? -1 // compare numbers
204  : (v2 > v1) ? +1
205  : 0
206  )
207  : 1 // others are greater
208  )
209  // v1 a boolean
210  : let( v2_ib = is_bool(v2) )
211  is_bool(v1) ?
212  (
213  (v2_nd || v2_in) ? -1
214  : v2_ib ?
215  (
216  ((v1 == true) && (v2 == false)) ? -1 // defined: true > false
217  : ((v1 == false) && (v2 == true)) ? +1
218  : 0
219  )
220  : 1 // others are greater
221  )
222  // v1 a string
223  : let( v2_is = is_string(v2) )
224  is_string(v1) ?
225  (
226  (v2_nd || v2_in || v2_ib) ? -1
227  : v2_is ?
228  (
229  (v1 > v2) ? -1 // compare strings
230  : (v2 > v1) ? +1
231  : 0
232  )
233  : 1 // others are greater
234  )
235  // v1 a list
236  : let( v2_il = is_list(v2) )
237  is_list(v1) ?
238  (
239  (v2_nd || v2_in || v2_ib || v2_is) ? -1
240  : v2_il ?
241  (
242  let
243  (
244  n1 = len(v1), // get element count
245  n2 = len(v2)
246  )
247  (n1 > n2) ? -1 // longest list is greater
248  : (n2 > n1) ? +1
249  : ((n1 == 0) && (n2 == 0)) ? 0 // reached end, are equal
250  : let
251  (
252  cf = compare(first(v1), first(v2), s) // compare first elements
253  )
254  (cf != 0) ? cf // not equal, ordering determined
255  : compare(tailn(v1), tailn(v2), s) // equal, check remaining
256  )
257  : 1 // others are greater
258  )
259  // v1 a range.
260  : is_range(v2) ?
261  (
262  (v1 == v2) ? 0 // equal range definitions
263  // compare range sums
264  : (s == true) ?
265  (
266  let
267  (
268  rs1 = sum(v1), // compute range sums
269  rs2 = sum(v2)
270  )
271  (rs1 > rs2) ? -1 // greatest sum is greater
272  : (rs2 > rs1) ? +1
273  : 0 // sums equal
274  )
275  // compare range lists
276  : (
277  let
278  (
279  rv1 = [for (i=v1) i], // convert to lists
280  rv2 = [for (i=v2) i],
281  rl1 = len(rv1), // get lengths
282  rl2 = len(rv2)
283  )
284  (rl1 > rl2) ? -1 // longest range is greater
285  : (rl2 > rl1) ? +1
286  : compare(rv1, rv2, s) // equal so compare as lists
287  )
288  )
289  // v2 not a range so v1 > v2
290  : -1;
291 
292 //! @}
293 
294 //----------------------------------------------------------------------------//
295 // List
296 //----------------------------------------------------------------------------//
297 
298 //! \name List
299 //! @{
300 
301 //! Return the value of an indexed list element with output defaults and list composition.
302 /***************************************************************************//**
303  \param l <list> The input list.
304  \param i <integer> The input list element index.
305 
306  \param dv <value> The default return value.
307 
308  \param s <integer> The output list size.
309 
310  \param de <value> The default element value for output list
311  composition.
312 
313  \param di <integer> The default output element index used when
314  composing an output list from an input element that is not
315  a list. Use `di = -1` to assign the value to all elements.
316 
317  \returns For the input list element indexed as `e = l[i]`:
318  - <b>For \p e a list</b>:
319  -# When `s == 0`, returns (1) \p e[0], or (2a) \p dv if
320  \p e[0] is undefined.
321  -# When either \p de or \p s is undefined, returns (3a) \p e.
322  -# When both \p de and \p s are defined with `s > 0`,
323  returns (4) a list of size \p s in which any undefined
324  elements of \p e are assigned \p de.
325  - <b>For \p e not a list</b>:
326  -# When `s == 0`, returns (2b) \p dv if \p e is undefined,
327  and (3b) \p e otherwise.
328  -# When either \p di or \p s is undefined, returns (2c) \p dv.
329  -# When both \p di and \p s are defined with `s > 0`,
330  returns (5) a list of size \p s with all elements
331  assigned \p de if \p e is undefined, or (6) a list of
332  size \p s with element \p [di] assigned \p e and all
333  other elements assigned \p de.
334 
335  \details
336 
337  #### Return conditions summary
338 
339  no | value | description
340  :----:|:---------:|:---------------------------------------------------
341  1 | \p e[0] | element 0 of the indexed element (remove from list)
342  2 | \p dv | default value
343  3 | \p e | the indexed element
344  4 | composed | assign \p de to all undefined elements of \p e
345  5 | composed | assign \p de to all elements of \p e
346  6 | composed | assign \p e to element(s) \p di and \p de to all other element \p e
347 *******************************************************************************/
348 function list_get_value
349 (
350  l,
351  i,
352  dv,
353 
354  s,
355  de,
356  di = 0
357 ) = !is_list(l) ? undef
358  : let( e = l[i] )
359  is_list( e ) ?
360  // indexed element is a list: (2a), (1), (3a)
361  (
362  (s == 0) ?
363  is_undef( e[0] ) ? dv
364  : e[0]
365 
366  : let
367  (
368  use_iev = !is_integer( s ) || (s<1) || is_undef( de )
369  )
370  use_iev ? e
371 
372  // update output list: (4)
373  : [ for (j = [0:s-1]) if ( !is_undef( e[j] ) ) e[j] else de ]
374  )
375  // indexed element is not a list: (2b), (3b), (2c)
376  : (
377  (s == 0) ?
378  is_undef( e ) ? dv
379  : e
380 
381  : let
382  (
383  use_d = !is_integer( s ) || (s<1) || !is_integer( di )
384  )
385  use_d ? dv
386 
387  // create output list: (5), (6)
388  : is_undef( e ) ?
389  [for (j = [0:s-1]) de]
390  : [for (j = [0:s-1]) if (di == -1 || j == di) e else de]
391  );
392 
393 //! @}
394 
395 //! @}
396 //! @}
397 
398 //----------------------------------------------------------------------------//
399 // openscad-amu auxiliary scripts
400 //----------------------------------------------------------------------------//
401 
402 /*
403 BEGIN_SCOPE validate;
404  BEGIN_OPENSCAD;
405  include <omdl-base.scad>;
406  include <common/validation.scad>;
407 
408  function fmt( id, td, v1, v2, v3 ) = table_validate_fmt(id, td, v1, v2, v3);
409  function v1(db, id) = table_validate_get_v1(db, id);
410  function v2(db, id) = table_validate_get_v2(db, id);
411  t = true; f = false; u = undef; s = validation_skip;
412 
413  tbl_test_values =
414  [
415  fmt("t01", "The undefined value",
416  undef,
417  undef
418  ),
419  fmt("t02", "Integers",
420  2121,
421  2100
422  ),
423  fmt("t03", "Equal srings",
424  "This is a test",
425  "This is a test"
426  ),
427  fmt("t04", "Non-equal srings",
428  "This is test v1",
429  "This is test v2"
430  ),
431  fmt("t05", "Non-equal srings 2",
432  "v1 this is test v1",
433  "v2 this is test v2"
434  ),
435  fmt("t06", "Empty strings",
436  empty_str,
437  empty_str
438  ),
439  fmt("t07", "Empty lists",
440  empty_lst,
441  empty_lst
442  ),
443  fmt("t08", "Non-equal short ranges",
444  [0:9],
445  [-1:9]
446  ),
447  fmt("t09", "Equal ranges",
448  [0:9],
449  [0:9]
450  ),
451  fmt("t10", "Long and short ranges",
452  [0:0.5:9],
453  [0:9]
454  ),
455  fmt("t11", "Lists with num-3 + undef",
456  [1, 2, 3, undef],
457  [undef, 1, 2, 3]
458  ),
459  fmt("t12", "3D vector and scalar",
460  [21, 32, 35],
461  19
462  ),
463  fmt("t13", "Unequal 3D vectors",
464  [1, 2, 3],
465  [3, 2, 1]
466  ),
467  fmt("t14", "equal 5D vectors",
468  [10, 12, 33, 98, 100],
469  [10, 12, 33, 98, 100]
470  ),
471  fmt("t15", "close 5D vectors",
472  [09.999, 11.999, 32.999, 97.999, 99.999],
473  [10.001, 12.001, 33.001, 98.001, 100.001]
474  ),
475  fmt("t16", "Equal tuples list-3",
476  [[1,2,3], [4,5,6], [7,8,9]],
477  [[1,2,3], [4,5,6], [7,8,9]]
478  ),
479  fmt("t17", "Equal mixed-tuples list",
480  [[1,2,3], [4,5,6], [7,8,9], ["a", "b", "c"]],
481  [[1,2,3], [4,5,6], [7,8,9], ["a", "b", "c"]]
482  ),
483  fmt("t18", "List-4, all undef",
484  [undef, undef, undef, undef],
485  [undef, undef, undef, undef]
486  ),
487  fmt("t19", "List of equal num lists-1",
488  [[1], [2], [3], [4], [5]],
489  [[1], [2], [3], [4], [5]]
490  ),
491  fmt("t20", "List-4 of close number pairs",
492  [[1,2], [3,4], [5,6], [7,8]],
493  [[1.001,2.001], [3.001,4.001], [5.001,6.001], [7.001,8.001]]
494  )
495  ];
496 
497  tbl_test_answers =
498  [ // function 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
499  ["almost_eq_nv_p4", f, f, f, f, f, f, f, f, f, f, f, f, f, t, f, f, f, f, f, f],
500  ["almost_eq_nv_p2", f, f, f, f, f, f, f, f, f, f, f, f, f, t, t, f, f, f, f, f],
501  ["almost_eq_p2", t, f, t, f, f, t, t, f, t, f, f, f, f, t, t, t, t, t, t, t],
502  ["compare", 0,-1, 0,+1,+1, 0, 0,-1, 0,-1,-1,-1,+1, 0,+1, 0, 0, 0, 0,+1],
503  ];
504 
505  db = table_validate_init( tbl_test_values, tbl_test_answers );
506 
507  table_validate_start( db );
508  test_ids = table_validate_get_ids( db );
509 
510  for (id=test_ids) table_validate( db, id, "almost_eq_nv_p4", 2, almost_eq_nv( v1(db,id), v2(db,id), 4) );
511  for (id=test_ids) table_validate( db, id, "almost_eq_nv_p2", 2, almost_eq_nv( v1(db,id), v2(db,id), 2) );
512  for (id=test_ids) table_validate( db, id, "almost_eq_p2", 2, almost_eq( v1(db,id), v2(db,id), 2) );
513  for (id=test_ids) table_validate( db, id, "compare", 2, compare( v1(db,id), v2(db,id) ) );
514  // list_get_value()
515 
516  // end_include
517  END_OPENSCAD;
518 
519  BEGIN_MFSCRIPT;
520  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
521  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
522  END_MFSCRIPT;
523 END_SCOPE;
524 */
525 
526 //----------------------------------------------------------------------------//
527 // end of file
528 //----------------------------------------------------------------------------//
function first(v)
Return the first element of an iterable value.
function tailn(v, n=1)
Return a list containing all but the first n elements of an iterable value.
function all_lists(v)
Test if all elements of an iterable value are lists.
function all_scalars(v)
Test if all elements of an iterable value are scalar values.
function all_iterables(v)
Test if all elements of an iterable value are iterable.
function all_numbers(v)
Test if all elements of an iterable value are numbers.
function almost_eq(v1, v2, p=6)
Test if all elements of two iterable values are sufficiently equal.
function list_get_value(l, i, dv, s, de, di=0)
Return the value of an indexed list element with output defaults and list composition.
function almost_eq_nv(v1, v2, p=6)
Test to see if two numerical vectors are sufficiently equal.
function compare(v1, v2, s=true)
Compare the sort order of any two values.
function sum(v, i1, i2)
Compute the sum of a list of numbers.
function is_integer(v)
Test if a value is an integer.
function is_number(v)
Test if a value is a number.
function is_range(v)
Test if a value is a range definition.