omdl  v0.9.5
OpenSCAD Mechanical Design Library
map.scad
Go to the documentation of this file.
1 //! Map data structure and operations.
2 /***************************************************************************//**
3  \file
4  \author Roy Allen Sutton
5  \date 2015-2024
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 (Maps)
31  \amu_define group_brief (Map data type and operations.)
32 
33  \amu_include (include/amu/pgid_path_pstem_pg.amu)
34 *******************************************************************************/
35 
36 //----------------------------------------------------------------------------//
37 // group.
38 //----------------------------------------------------------------------------//
39 
40 /***************************************************************************//**
41  \amu_include (include/amu/group_in_parent_start.amu)
42  \amu_include (include/amu/includes_required.amu)
43 
44  \details
45 
46  \amu_define title (Map use)
47  \amu_define scope_id (example_use)
48  \amu_include (include/amu/scope.amu)
49 *******************************************************************************/
50 
51 //----------------------------------------------------------------------------//
52 
53 //! Return the index of a map key.
54 /***************************************************************************//**
55  \param m <map> A list of N key-value map pairs.
56  \param k <string> A map key.
57 
58  \returns <integer> The index of the map entry if it exists.
59  Returns \b undef if \p key is not a string or does not exists.
60 *******************************************************************************/
61 function map_get_index
62 (
63  m,
64  k
65 ) = !is_string(k) ? undef
66  : let(i = first(search([k], m, 1, 0 )))
67  (i == empty_lst) ? undef : i;
68 
69 //! Test if a key exists.
70 /***************************************************************************//**
71  \param m <map> A list of N key-value map pairs.
72  \param k <string> A map key.
73 
74  \returns <boolean> \b true when the key exists and \b false otherwise.
75 *******************************************************************************/
76 function map_exists
77 (
78  m,
79  k
80 ) = is_defined(map_get_index(m, k));
81 
82 //! Get the map value associated with a key.
83 /***************************************************************************//**
84  \param m <map> A list of N key-value map pairs.
85  \param k <string> A map key.
86 
87  \returns <value> The value associated with \p key.
88  Returns \b undef if \p key does not exists.
89 *******************************************************************************/
90 function map_get_value
91 (
92  m,
93  k
94 ) = second(m[map_get_index(m, k)]);
95 
96 //! Get a list of all map keys.
97 /***************************************************************************//**
98  \param m <map> A list of N key-value map pairs.
99 
100  \returns <string-list-N> A list of key strings for all N map entries.
101 *******************************************************************************/
102 function map_get_keys
103 (
104  m
105 ) = select_e(m, f=true);
106 
107 //! Get a list of all map values.
108 /***************************************************************************//**
109  \param m <map> A list of N key-value map pairs.
110 
111  \returns <list-N> A list of values for all N map entries.
112 *******************************************************************************/
113 function map_get_values
114 (
115  m
116 ) = select_e(m, l=true);
117 
118 //! Get the the first value associated with an existing key in one of two maps.
119 /***************************************************************************//**
120  \param m1 <map> A list of N key-value map pairs.
121  \param m2 <map> A list of N key-value map pairs.
122  \param k <string> A map key.
123  \param d <value> A default return value.
124 
125  \returns <value> The first value associated with \p key that exists
126  in maps \p m1 or \p m2, otherwise return \p d when it does
127  not exist in either.
128 *******************************************************************************/
129 function map_get_firstof2_or
130 (
131  m1,
132  m2,
133  k,
134  d
135 ) = map_exists(m1, k) ? map_get_value(m1, k)
136  : map_exists(m2, k) ? map_get_value(m2, k)
137  : d;
138 
139 //! Get the number of map entries.
140 /***************************************************************************//**
141  \param m <map> A list of N key-value map pairs.
142 
143  \returns <integer> The number of map entries.
144 *******************************************************************************/
145 function map_get_size
146 (
147  m
148 ) = len(m);
149 
150 //! Merge the unique key-value pairs of a second map with those of a first.
151 /***************************************************************************//**
152  \param m1 <map> A list of N key-value map pairs.
153  \param m2 <map> A list of N key-value map pairs.
154 
155  \returns <value> The key value-pairs of \p m1 together with the
156  unique key value-pairs of \p m2 that are absent in \p m1.
157 *******************************************************************************/
158 function map_merge
159 (
160  m1,
161  m2
162 ) = [
163  // output all key-value pairs of 'm1'
164  for (k = map_get_keys(m1) )
165  [k, map_get_value(m1, k)],
166 
167  // output all key-value pairs of 'm2' not present in 'm1'
168  for (k = map_get_keys(m2) )
169  if ( !map_exists(m1, k) )
170  [k, map_get_value(m2, k)]
171  ];
172 
173 //! Perform basic format checks on a map and return errors.
174 /***************************************************************************//**
175  \param m <map> A list of N key-value map pairs.
176 
177  \returns <list-N> A list of map format errors.
178 
179  \details
180 
181  Check that: (1) each entry has key-value 2-tuple, (2) each key is a
182  string, and (3) key identifiers are unique. When there are no
183  errors, the \b empty_lst is returned.
184 *******************************************************************************/
185 function map_errors
186 (
187  m
188 ) =
189  let
190  (
191  // (1) each entry has key-value 2-tuple.
192  ec1 =
193  [
194  for ( i = [0:map_get_size(m)-1] )
195  let ( entry = m[i], key = first(entry) )
196  if ( 2 != len(entry) )
197  str
198  (
199  "map index ", i,
200  ", entry=", entry,
201  ", has incorrect count=[", len(entry),"]"
202  )
203  ],
204 
205  // (2) each key must be a string.
206  ec2 =
207  [
208  for ( i = [0:map_get_size(m)-1] )
209  let ( entry = m[i], key = first(entry) )
210  if ( is_string(key) == false )
211  str
212  (
213  "map index ", i,
214  ", entry=", entry,
215  ", key=[", key,"] is not a string."
216  )
217  ],
218 
219  // (3) no repeat key identifiers.
220  ec3 =
221  [
222  for ( i = [0:map_get_size(m)-1] )
223  let ( entry = m[i], key = first(entry) )
224  if ( len(first(search([key], m, 0, 0))) > 1 )
225  str
226  (
227  "map index ", i,
228  ", key=[", key,"] not unique."
229  )
230  ]
231  )
232  concat(ec1, ec2, ec3);
233 
234 //! Perform basic format checks on a map and output errors to console.
235 /***************************************************************************//**
236  \param m <map> A list of N key-value map pairs.
237 
238  \param verbose <boolean> Be verbose during check.
239 
240  \details
241 
242  Check that: (1) each entry has key-value 2-tuple, (2) each key is a
243  string, and (3) key identifiers are unique.
244 *******************************************************************************/
245 module map_check
246 (
247  m,
248  verbose = false
249 )
250 {
251  if (verbose) log_info("begin map check");
252 
253  if (verbose) log_info ("checking map format and keys.");
254 
255  if ( map_get_size(m) > 0 )
256  for ( i = [0:map_get_size(m)-1] )
257  {
258  entry = m[i];
259  key = first(entry);
260 
261  // (1) each entry has key-value 2-tuple.
262  assert
263  (
264  len(entry) == 2,
265  str
266  (
267  "map index ", i,
268  ", entry=", entry,
269  ", has incorrect count=[", len(entry),"]"
270  )
271  );
272 
273  // (2) each key must be a string.
274  assert
275  (
276  is_string(key),
277  str
278  (
279  "map index ", i,
280  ", entry=", entry,
281  ", key=[", key,"] is not a string."
282  )
283  );
284 
285  // (3) no repeat key identifiers.
286  if ( len(first(search([key], m, 0, 0))) > 1 )
288  (
289  str
290  (
291  "map index ", i,
292  ", key=[", key,"] not unique."
293  )
294  );
295  }
296 
297  if (verbose)
298  {
299  log_info
300  (
301  str
302  (
303  "map size: ",
304  map_get_size(m), " entries."
305  )
306  );
307 
308  log_info("end map check");
309  }
310 }
311 
312 //! Dump each map entry to the console.
313 /***************************************************************************//**
314  \param m <map> A list of N key-value map pairs.
315  \param sort <boolean> Sort the output by key.
316  \param number <boolean> Output index number.
317  \param p <integer> Number of places for zero-padded numbering.
318 *******************************************************************************/
319 module map_dump
320 (
321  m,
322  sort = false,
323  number = true,
324  p = 3
325 )
326 {
327  if ( map_get_size(m) > 0 )
328  {
329  keys = map_get_keys(m);
330  maxl = max( [for (i = keys) len(i)] ) + 1;
331 
332  for (key = (sort == true) ? sort_q(keys) : keys)
333  {
334  idx = map_get_index(m, key);
335 
336  log_echo
337  (
338  str
339  (
340  number ? chr(consts(p-len(str(idx)), 48)) : empty_str,
341  number ? str(idx, ": ") : empty_str,
342  chr(consts(maxl-len(key), 32)), "'", key, "' = ",
343  "'", map_get_value(m, key), "'"
344  )
345  );
346  }
347  }
348 
349  if ( number )
350  log_echo(str("map size: ", map_get_size(m), " entries."));
351 }
352 
353 //! Write formatted map entries to the console.
354 /***************************************************************************//**
355  \param m <map> A list of N key-value map pairs.
356  \param ks <string-list> A list of selected keys.
357  \param sort <boolean> Sort the output by key.
358  \param number <boolean> Output index number.
359  \param fs <string> A field separator.
360  \param thn <string> Column heading for numbered row output.
361  \param index_tags <string-list> List of html formatting tags.
362  \param key_tags <string-list> List of html formatting tags.
363  \param value_tags <string-list> List of html formatting tags.
364 
365  \details
366 
367  Output map keys and values the console. To output only select keys,
368  assign the desired key identifiers to \p ks. For example to output
369  only 'key1' and 'key2', assign <tt>ks = ["key1", "key2"]</tt>. The
370  output can then be processed to produce documentation tables as
371  shown in the example below.
372 
373  \amu_define title (Map write)
374  \amu_define scope_id (example_table)
375  \amu_include (include/amu/scope_table.amu)
376 *******************************************************************************/
377 module map_write
378 (
379  m,
380  ks,
381  sort = false,
382  number = false,
383  fs = "^",
384  thn = "idx",
385  index_tags = empty_lst,
386  key_tags = ["b"],
387  value_tags = empty_lst
388 )
389 {
390  if ( map_get_size(m) > 0 )
391  {
392  // heading
393  log_echo
394  (
395  str
396  (
397  number ? str(thn,fs) : empty_str,
398  "key", fs,
399  "value"
400  )
401  );
402 
403  // data
404  keys = map_get_keys(m);
405  maxl = max( [for (i = keys) len(i)] ) + 1;
406 
407  for (key = (sort == true) ? sort_q(keys) : keys)
408  {
409  idx = map_get_index(m, key);
410 
411  if
412  (
413  is_undef( ks ) ||
414  is_number( first( search( [key], ks, 1, 0 ) ) )
415  )
416  log_echo
417  (
418  str
419  (
420  (number == true) ?
421  str(strl_html([idx], p=[index_tags]),fs)
422  : empty_str,
423  strl_html(key, p=[key_tags]), fs,
424  strl_html([map_get_value(m, key)], p=[value_tags]), fs
425  )
426  );
427  }
428  }
429 }
430 
431 //! @}
432 //! @}
433 
434 //----------------------------------------------------------------------------//
435 // openscad-amu auxiliary scripts
436 //----------------------------------------------------------------------------//
437 
438 /*
439 BEGIN_SCOPE example_use;
440  BEGIN_OPENSCAD;
441  include <omdl-base.scad>;
442 
443  map =
444  [
445  ["part1", ["screw10", [10, 11, 13]]],
446  ["part2", ["screw12", [20, 21, 30]]],
447  ["part3", ["screw10", [10, 10, -12]]],
448  ["config", ["top", "front", "rear"]],
449  ["version", [21, 5, 0]],
450  ["runid", 10]
451  ];
452 
453  echo( "### map_check ###" );
454  map_check(map, true);
455 
456  echo( "### map_exists ###" );
457  echo( str("is part0 = ", map_exists(map, "part0")) );
458  echo( str("is part1 = ", map_exists(map, "part1")) );
459 
460  echo( "### map_get_value ###" );
461  p1 = map_get_value(map, "part1");
462  echo( c=second(p1) );
463 
464  keys = map_get_keys(map);
465  parts = delete(keys, mv=["config", "version", "runid"]);
466 
467  echo( "### map_delete ###" );
468  for ( p = parts )
469  echo
470  (
471  n=p,
472  p=first(map_get_value(map, p)),
473  l=second(map_get_value(map, p))
474  );
475 
476  echo( "### map_dump ###" );
477  map_dump(map);
478 
479  // end_include
480  END_OPENSCAD;
481 
482  BEGIN_MFSCRIPT;
483  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
484  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
485  END_MFSCRIPT;
486 END_SCOPE;
487 
488 BEGIN_SCOPE example_table;
489  BEGIN_OPENSCAD;
490  include <omdl-base.scad>;
491 
492  map =
493  [
494  ["part1", ["screw10", [10, 11, 13]]],
495  ["part2", ["screw12", [20, 21, 30]]],
496  ["part3", ["screw10", [10, 10, -12]]],
497  ["config", ["top", "front", "rear"]],
498  ["version", [21, 5, 0]],
499  ["runid", 10]
500  ];
501 
502  map_write(map, index_tags=["center","i"]);
503 
504  // end_include
505  END_OPENSCAD;
506 
507  BEGIN_MFSCRIPT;
508  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
509  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
510  END_MFSCRIPT;
511 END_SCOPE;
512 */
513 
514 //----------------------------------------------------------------------------//
515 // end of file
516 //----------------------------------------------------------------------------//
module log_warn(m)
Output warning message to console.
Definition: console.scad:333
module log_echo(m)
Output message to console.
Definition: console.scad:272
module log_info(m)
Output information message to console.
Definition: console.scad:318
empty_str
<string> A string with no characters (the empty string).
Definition: constants.scad:301
empty_lst
<list> A list with no values (the empty list).
Definition: constants.scad:304
function second(v)
Return the second element of an iterable value.
function first(v)
Return the first element of an iterable value.
function sort_q(v, i, r=false)
Sort the elements of an iterable value using quick sort.
function consts(l, v, u=false)
Create a list of constant or incrementing elements.
function strl_html(v, b, p, a, f, d=false)
Convert a list of values to a concatenated HTML-formatted string.
function select_e(v, i, f, l)
Select each element at an index position of a list of iterable values.
function map_get_value(m, k)
Get the map value associated with a key.
module map_write(m, ks, sort=false, number=false, fs="^", thn="idx", index_tags=empty_lst, key_tags=["b"], value_tags=empty_lst)
Write formatted map entries to the console.
Definition: map.scad:672
function map_get_keys(m)
Get a list of all map keys.
module map_check(m, verbose=false)
Perform basic format checks on a map and output errors to console.
Definition: map.scad:431
module map_dump(m, sort=false, number=true, p=3)
Dump each map entry to the console.
Definition: map.scad:505
function map_get_index(m, k)
Return the index of a map key.
function map_get_values(m)
Get a list of all map values.
function map_get_firstof2_or(m1, m2, k, d)
Get the the first value associated with an existing key in one of two maps.
function map_merge(m1, m2)
Merge the unique key-value pairs of a second map with those of a first.
function map_exists(m, k)
Test if a key exists.
function map_get_size(m)
Get the number of map entries.
function map_errors(m)
Perform basic format checks on a map and return errors.
function is_defined(v)
Test if a value is defined.
function is_number(v)
Test if a value is a number.