From f7b08f1b9d18602d316dbbd78594fa278fedce6e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 16 Oct 2021 23:01:52 -0400 Subject: [PATCH] reorder functions in array.scad list_{de,in}creasing -> is_{de,in}creasing, add strict option to both --- arrays.scad | 510 +++++++++++++++++++++------------------- geometry.scad | 2 +- paths.scad | 2 +- skin.scad | 4 +- tests/test_arrays.scad | 72 +++--- tests/test_math.scad | 2 +- tests/test_vectors.scad | 17 ++ vectors.scad | 4 +- vnf.scad | 12 +- 9 files changed, 341 insertions(+), 284 deletions(-) diff --git a/arrays.scad b/arrays.scad index 728cc47..1e0d44e 100644 --- a/arrays.scad +++ b/arrays.scad @@ -53,36 +53,34 @@ function _same_type(a,b, depth) = && []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) ) 0] ); - - -// Function: list_shortest() +// Function: min_length() // Usage: -// llen = list_shortest(array); +// llen = min_length(array); // Topics: List Handling -// See Also: list_longest() +// See Also: max_length() // Description: // Returns the length of the shortest sublist in a list of lists. // Arguments: // array = A list of lists. // Example: -// slen = list_shortest([[3,4,5],[6,7,8,9]]); // Returns: 3 -function list_shortest(array) = +// slen = min_length([[3,4,5],[6,7,8,9]]); // Returns: 3 +function min_length(array) = assert(is_list(array), "Invalid input." ) min([for (v = array) len(v)]); -// Function: list_longest() +// Function: max_length() // Usage: -// llen = list_longest(array); +// llen = max_length(array); // Topics: List Handling -// See Also: list_shortest() +// See Also: min_length() // Description: // Returns the length of the longest sublist in a list of lists. // Arguments: // array = A list of lists. // Example: -// llen = list_longest([[3,4,5],[6,7,8,9]]); // Returns: 4 -function list_longest(array) = +// llen = max_length([[3,4,5],[6,7,8,9]]); // Returns: 4 +function max_length(array) = assert(is_list(array), "Invalid input." ) max([for (v = array) len(v)]); @@ -111,71 +109,6 @@ function in_list(val,list,idx) = : val==list[s][idx]; -// Function: find_first_match() -// Topics: List Handling -// See Also: in_list() -// Usage: -// idx = find_first_match(val, list, [start=], [eps=]); -// indices = find_first_match(val, list, all=true, [start=], [eps=]); -// Description: -// Finds the first item in `list` that matches `val`, returning the index. -// Arguments: -// val = The value to search for. If given a function literal of signature `function (x)`, uses that function to check list items. Returns true for a match. -// list = The list to search through. -// --- -// start = The index to start searching from. -// all = If true, returns a list of all matching item indices. -// eps = The maximum allowed floating point rounding error for numeric comparisons. -function find_first_match(val, list, start=0, all=false, eps=EPSILON) = - all? [ - for (i=[start:1:len(list)-1]) - if ( - (!is_func(val) && approx(val, list[i], eps=eps)) || - (is_func(val) && val(list[i])) - ) i - ] : - __find_first_match(val, list, eps=eps, i=start); - -function __find_first_match(val, list, eps, i=0) = - i >= len(list)? undef : - ( - (!is_func(val) && approx(val, list[i], eps=eps)) || - (is_func(val) && val(list[i])) - )? i : __find_first_match(val, list, eps=eps, i=i+1); - - -// Function: list_increasing() -// Usage: -// bool = list_increasing(list); -// Topics: List Handling -// See Also: max_index(), min_index(), list_decreasing() -// Description: -// Returns true if the list is (non-strictly) increasing -// Example: -// a = list_increasing([1,2,3,4]); // Returns: true -// b = list_increasing([1,3,2,4]); // Returns: false -// c = list_increasing([4,3,2,1]); // Returns: false -function list_increasing(list) = - assert(is_list(list)||is_string(list)) - len([for (p=pair(list)) if(p.x>p.y) true])==0; - - -// Function: list_decreasing() -// Usage: -// bool = list_decreasing(list); -// Topics: List Handling -// See Also: max_index(), min_index(), list_increasing() -// Description: -// Returns true if the list is (non-strictly) decreasing -// Example: -// a = list_decreasing([1,2,3,4]); // Returns: false -// b = list_decreasing([4,2,3,1]); // Returns: false -// c = list_decreasing([4,3,2,1]); // Returns: true -function list_decreasing(list) = - assert(is_list(list)||is_string(list)) - len([for (p=pair(list)) if(p.x=0,"Bad index.") + assert(idx1=0,"Bad index.") + assert(idx2= len(list)? undef : + approx(val, list[i], eps=eps) + ? i + : __find_approx(val, list, eps=eps, i=i+1); + // Section: List Indexing @@ -373,49 +410,6 @@ function bselect(array,index) = // Section: List Construction -// Function: list() -// Topics: List Handling, Type Conversion -// Usage: -// list = list(l) -// Description: -// Expands a range into a full list. If given a list, returns it verbatim. -// If given a string, explodes it into a list of single letters. -// Arguments: -// l = The value to expand. -// See Also: scalar_vec3(), force_list() -// Example: -// l1 = list([3:2:9]); // Returns: [3,5,7,9] -// l2 = list([3,4,5]); // Returns: [3,4,5] -// l3 = list("Foo"); // Returns: ["F","o","o"] -// l4 = list(23); // Returns: [23] -function list(l) = is_list(l)? l : [for (x=l) x]; - - -// Function: force_list() -// Usage: -// list = force_list(value, [n], [fill]); -// Topics: List Handling -// See Also: scalar_vec3() -// Description: -// Coerces non-list values into a list. Makes it easy to treat a scalar input -// consistently as a singleton list, as well as list inputs. -// - If `value` is a list, then that list is returned verbatim. -// - If `value` is not a list, and `fill` is not given, then a list of `n` copies of `value` will be returned. -// - If `value` is not a list, and `fill` is given, then a list `n` items long will be returned where `value` will be the first item, and the rest will contain the value of `fill`. -// Arguments: -// value = The value or list to coerce into a list. -// n = The number of items in the coerced list. Default: 1 -// fill = The value to pad the coerced list with, after the firt value. Default: undef (pad with copies of `value`) -// Example: -// x = force_list([3,4,5]); // Returns: [3,4,5] -// y = force_list(5); // Returns: [5] -// z = force_list(7, n=3); // Returns: [7,7,7] -// w = force_list(4, n=3, fill=1); // Returns: [4,1,1] -function force_list(value, n=1, fill) = - is_list(value) ? value : - is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill]; - - // Function: repeat() // Usage: // list = repeat(val, n); @@ -493,6 +487,52 @@ function list_bset(indexset, valuelist, dflt=0) = +// Function: list() +// Topics: List Handling, Type Conversion +// Usage: +// list = list(l) +// Description: +// Expands a range into a full list. If given a list, returns it verbatim. +// If given a string, explodes it into a list of single letters. +// Arguments: +// l = The value to expand. +// See Also: scalar_vec3(), force_list() +// Example: +// l1 = list([3:2:9]); // Returns: [3,5,7,9] +// l2 = list([3,4,5]); // Returns: [3,4,5] +// l3 = list("Foo"); // Returns: ["F","o","o"] +// l4 = list(23); // Returns: [23] +function list(l) = is_list(l)? l : [for (x=l) x]; + + +// Function: force_list() +// Usage: +// list = force_list(value, [n], [fill]); +// Topics: List Handling +// See Also: scalar_vec3() +// Description: +// Coerces non-list values into a list. Makes it easy to treat a scalar input +// consistently as a singleton list, as well as list inputs. +// - If `value` is a list, then that list is returned verbatim. +// - If `value` is not a list, and `fill` is not given, then a list of `n` copies of `value` will be returned. +// - If `value` is not a list, and `fill` is given, then a list `n` items long will be returned where `value` will be the first item, and the rest will contain the value of `fill`. +// Arguments: +// value = The value or list to coerce into a list. +// n = The number of items in the coerced list. Default: 1 +// fill = The value to pad the coerced list with, after the firt value. Default: undef (pad with copies of `value`) +// Example: +// x = force_list([3,4,5]); // Returns: [3,4,5] +// y = force_list(5); // Returns: [5] +// z = force_list(7, n=3); // Returns: [7,7,7] +// w = force_list(4, n=3, fill=1); // Returns: [4,1,1] +function force_list(value, n=1, fill) = + is_list(value) ? value : + is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill]; + + + + + // Section: List Modification @@ -548,81 +588,40 @@ function list_rotate(list,n=1) = ) is_string(list)? str_join(elems) : elems; + -// Function: deduplicate() +// Function: shuffle() // Usage: -// list = deduplicate(list, [close], [eps]); +// shuffled = shuffle(list, [seed]); // Topics: List Handling -// See Also: deduplicate_indexed() +// See Also: sort(), sortidx(), unique(), unique_count() // Description: -// Removes consecutive duplicate items in a list. -// When `eps` is zero, the comparison between consecutive items is exact. -// Otherwise, when all list items and subitems are numbers, the comparison is within the tolerance `eps`. -// This is different from `unique()` in that the list is *not* sorted. +// Shuffles the input list into random order. +// If given a string, shuffles the characters within the string. +// If you give a numeric seed value then the permutation +// will be repeatable. // Arguments: -// list = The list to deduplicate. -// closed = If true, drops trailing items if they match the first list item. -// eps = The maximum tolerance between items. +// list = The list to shuffle. +// seed = Optional random number seed for the shuffling. // Example: -// a = deduplicate([8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3,8] -// b = deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3] -// c = deduplicate("Hello"); // Returns: "Helo" -// d = deduplicate([[3,4],[7,2],[7,1.99],[1,4]],eps=0.1); // Returns: [[3,4],[7,2],[1,4]] -// e = deduplicate([[7,undef],[7,undef],[1,4],[1,4+1e-12]],eps=0); // Returns: [[7,undef],[1,4],[1,4+1e-12]] -function deduplicate(list, closed=false, eps=EPSILON) = - assert(is_list(list)||is_string(list)) +// // Spades Hearts Diamonds Clubs +// suits = ["\u2660", "\u2661", "\u2662", "\u2663"]; +// ranks = [2,3,4,5,6,7,8,9,10,"J","Q","K","A"]; +// cards = [for (suit=suits, rank=ranks) str(rank,suit)]; +// deck = shuffle(cards); +function shuffle(list,seed) = + assert(is_list(list)||is_string(list), "Invalid input." ) + is_string(list)? str_join(shuffle([for (x = list) x],seed=seed)) : + len(list)<=1 ? list : let( - l = len(list), - end = l-(closed?0:1) - ) - is_string(list) ? str_join([for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]) : - eps==0 ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]] : - [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]]; + rval = is_num(seed) ? rands(0,1,len(list),seed_value=seed) + : rands(0,1,len(list)), + left = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]], + right = [for (i=[0:len(list)-1]) if (rval[i]>=0.5) list[i]] + ) + concat(shuffle(left), shuffle(right)); -// Function: deduplicate_indexed() -// Usage: -// new_idxs = deduplicate_indexed(list, indices, [closed], [eps]); -// Topics: List Handling -// See Also: deduplicate() -// Description: -// Given a list, and indices into it, removes consecutive indices that -// index to the same values in the list. -// Arguments: -// list = The list that the indices index into. -// indices = The list of indices to deduplicate. -// closed = If true, drops trailing indices if what they index matches what the first index indexes. -// eps = The maximum difference to allow between numbers or vectors. -// Example: -// a = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]); // Returns: [1,4,3,2,0,1] -// b = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true); // Returns: [1,4,3,2,0] -// c = deduplicate_indexed([[7,undef],[7,undef],[1,4],[1,4],[1,4+1e-12]],eps=0); // Returns: [0,2,4] -function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) = - assert(is_list(list)||is_string(list), "Improper list or string.") - indices==[]? [] : - assert(is_vector(indices), "Indices must be a list of numbers.") - let( - ll = len(list), - l = len(indices), - end = l-(closed?0:1) - ) [ - for (i = [0:1:l-1]) let( - idx1 = indices[i], - idx2 = indices[(i+1)%l], - a = assert(idx1>=0,"Bad index.") - assert(idx1=0,"Bad index.") - assert(idx20 && idx[0]>=imin ) || (idx[1]<0 && idx[0]<=imax ) ) && ( is_undef(imax) || (idx[1]>0 && idx[2]<=imax ) || (idx[1]<0 && idx[2]>=imin ) ) ); - - -// Function: shuffle() -// Usage: -// shuffled = shuffle(list, [seed]); -// Topics: List Handling -// See Also: sort(), sortidx(), unique(), unique_count() -// Description: -// Shuffles the input list into random order. -// If given a string, shuffles the characters within the string. -// If you give a numeric seed value then the permutation -// will be repeatable. -// Arguments: -// list = The list to shuffle. -// seed = Optional random number seed for the shuffling. -// Example: -// // Spades Hearts Diamonds Clubs -// suits = ["\u2660", "\u2661", "\u2662", "\u2663"]; -// ranks = [2,3,4,5,6,7,8,9,10,"J","Q","K","A"]; -// cards = [for (suit=suits, rank=ranks) str(rank,suit)]; -// deck = shuffle(cards); -function shuffle(list,seed) = - assert(is_list(list)||is_string(list), "Invalid input." ) - is_string(list)? str_join(shuffle([for (x = list) x],seed=seed)) : - len(list)<=1 ? list : - let( - rval = is_num(seed) ? rands(0,1,len(list),seed_value=seed) - : rands(0,1,len(list)), - left = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]], - right = [for (i=[0:len(list)-1]) if (rval[i]>=0.5) list[i]] - ) - concat(shuffle(left), shuffle(right)); // idx should be an index of the arrays l[i] function _group_sort_by_index(l,idx) = @@ -1132,34 +1099,54 @@ function sortidx(list, idx=undef) = [for(li=lsort) li[0] ] : _sort_general(list,idx,indexed=true) : _sort_general(list,idx,indexed=true); - -// Function: group_sort() + + +// Function: is_increasing() // Usage: -// ulist = group_sort(list); +// bool = is_increasing(list); // Topics: List Handling -// See Also: shuffle(), sort(), sortidx(), unique(), unique_count() +// See Also: max_index(), min_index(), is_decreasing() // Description: -// Given a list of values, returns the sorted list with all repeated items grouped in a list. -// When the list entries are themselves lists, the sorting may be done based on the `idx` entry -// of those entries, that should be numbers. -// The result is always a list of lists. +// Returns true if the list is (non-strictly) increasing, or strictly increasing if strict is set to true. +// The list can be a list of any items that OpenSCAD can compare, or it can be a string which will be +// evaluated character by character. // Arguments: -// list = The list to sort. -// idx = If given, do the comparison based just on the specified index. Default: zero. +// list = list (or string) to check +// strict = set to true to test that list is strictly increasing // Example: -// sorted = group_sort([5,2,8,3,1,3,8,7,5]); // Returns: [[1],[2],[3,3],[5,5],[7],[8,8]] -// sorted2 = group_sort([[5,"a"],[2,"b"], [5,"c"], [3,"d"], [2,"e"] ], idx=0); // Returns: [[[2,"b"],[2,"e"]], [[5,"a"],[5,"c"]], [[3,"d"]] ] -function group_sort(list, idx) = - assert(is_list(list), "Input should be a list." ) - assert(is_undef(idx) || (is_finite(idx) && idx>=0) , "Invalid index." ) - len(list)<=1 ? [list] : - is_vector(list)? _group_sort(list) : - let( idx = is_undef(idx) ? 0 : idx ) - assert( [for(entry=list) if(!is_list(entry) || len(entry)=p.y) true])==0 + : len([for (p=pair(list)) if(p.x>p.y) true])==0; + + +// Function: is_decreasing() +// Usage: +// bool = is_decreasing(list); +// Topics: List Handling +// See Also: max_index(), min_index(), is_increasing() +// Description: +// Returns true if the list is (non-strictly) decreasing, or strictly decreasing if strict is set to true. +// The list can be a list of any items that OpenSCAD can compare, or it can be a string which will be +// evaluated character by character. +// Arguments: +// list = list (or string) to check +// strict = set to true to test that list is strictly decreasing +// Example: +// a = is_decreasing([1,2,3,4]); // Returns: false +// b = is_decreasing([4,2,3,1]); // Returns: false +// c = is_decreasing([4,3,2,1]); // Returns: true +function is_decreasing(list,strict=false) = + assert(is_list(list)||is_string(list)) + strict ? len([for (p=pair(list)) if(p.x<=p.y) true])==0 + : len([for (p=pair(list)) if(p.x=0) , "Invalid index." ) + len(list)<=1 ? [list] : + is_vector(list)? _group_sort(list) : + let( idx = is_undef(idx) ? 0 : idx ) + assert( [for(entry=list) if(!is_list(entry) || len(entry) // vnf = zrot(33,regular_polyhedron_info("vnf", "dodecahedron", side=12)); // vnf_polyhedron(vnf); @@ -549,8 +551,12 @@ function vnf_triangulate(vnf) = // sliced = vnf_slice(vnf, dir, cuts); // Description: // Slice the faces of a VNF along a specified axis direction at a given list -// of cut points. You can use this to refine the faces of a VNF before applying +// of cut points. The cut points can appear in any order. You can use this to refine the faces of a VNF before applying // a nonlinear transformation to its vertex set. +// Arguments: +// vnf = vnf to slice +// dir = normal direction to the slices, either "X", "Y" or "Z" +// cuts = X, Y or Z values where cuts occur // Example(3D): // include // vnf = regular_polyhedron_info("vnf", "dodecahedron", side=12);