From 71dab624320259ff498613fbf171abb8347db0ee Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 26 Oct 2021 23:12:51 -0400 Subject: [PATCH] move comparison functions out of math.scad and arrays.scad into comparisons.scad rename arrays.scad to lists.scad --- comparisons.scad | 782 +++++++++++++++++ arrays.scad => lists.scad | 883 ++++---------------- math.scad | 223 ----- std.scad | 3 +- tests/test_comparisons.scad | 280 +++++++ tests/test_linalg.scad | 252 ++++++ tests/{test_arrays.scad => test_lists.scad} | 149 +--- tests/test_math.scad | 136 --- 8 files changed, 1492 insertions(+), 1216 deletions(-) create mode 100644 comparisons.scad rename arrays.scad => lists.scad (67%) create mode 100644 tests/test_comparisons.scad create mode 100644 tests/test_linalg.scad rename tests/{test_arrays.scad => test_lists.scad} (65%) diff --git a/comparisons.scad b/comparisons.scad new file mode 100644 index 0000000..c7e9b18 --- /dev/null +++ b/comparisons.scad @@ -0,0 +1,782 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: comparisons.scad +// Functions for comparisons with lists, ordering and sorting +// Includes: +// include +////////////////////////////////////////////////////////////////////// + + +// Section: Comparing lists to zero + +// Function: approx() +// Usage: +// test = approx(a, b, [eps]) +// Description: +// Compares two numbers or vectors, and returns true if they are closer than `eps` to each other. +// Arguments: +// a = First value. +// b = Second value. +// eps = The maximum allowed difference between `a` and `b` that will return true. +// Example: +// test1 = approx(-0.3333333333,-1/3); // Returns: true +// test2 = approx(0.3333333333,1/3); // Returns: true +// test3 = approx(0.3333,1/3); // Returns: false +// test4 = approx(0.3333,1/3,eps=1e-3); // Returns: true +// test5 = approx(PI,3.1415926536); // Returns: true +function approx(a,b,eps=EPSILON) = + (a==b && is_bool(a) == is_bool(b)) || + (is_num(a) && is_num(b) && abs(a-b) <= eps) || + (is_list(a) && is_list(b) && len(a) == len(b) && [] == [for (i=idx(a)) if (!approx(a[i],b[i],eps=eps)) 1]); + + +// Function: all_zero() +// Usage: +// x = all_zero(x, [eps]); +// Description: +// Returns true if the finite number passed to it is approximately zero, to within `eps`. +// If passed a list, recursively checks if all items in the list are approximately zero. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// eps = The maximum allowed variance. Default: `EPSILON` (1e-9) +// Example: +// a = all_zero(0); // Returns: true. +// b = all_zero(1e-3); // Returns: false. +// c = all_zero([0,0,0]); // Returns: true. +// d = all_zero([0,0,1e-3]); // Returns: false. +function all_zero(x, eps=EPSILON) = + is_finite(x)? approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_zero(xx,eps=eps)) 1] == []) : + false; + + +// Function: all_nonzero() +// Usage: +// test = all_nonzero(x, [eps]); +// Description: +// Returns true if the finite number passed to it is not almost zero, to within `eps`. +// If passed a list, recursively checks if all items in the list are not almost zero. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// eps = The maximum allowed variance. Default: `EPSILON` (1e-9) +// Example: +// a = all_nonzero(0); // Returns: false. +// b = all_nonzero(1e-3); // Returns: true. +// c = all_nonzero([0,0,0]); // Returns: false. +// d = all_nonzero([0,0,1e-3]); // Returns: false. +// e = all_nonzero([1e-3,1e-3,1e-3]); // Returns: true. +function all_nonzero(x, eps=EPSILON) = + is_finite(x)? !approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonzero(xx,eps=eps)) 1] == []) : + false; + + +// Function: all_positive() +// Usage: +// test = all_positive(x); +// Description: +// Returns true if the finite number passed to it is greater than zero. +// If passed a list, recursively checks if all items in the list are positive. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// a = all_positive(-2); // Returns: false. +// b = all_positive(0); // Returns: false. +// c = all_positive(2); // Returns: true. +// d = all_positive([0,0,0]); // Returns: false. +// e = all_positive([0,1,2]); // Returns: false. +// f = all_positive([3,1,2]); // Returns: true. +// g = all_positive([3,-1,2]); // Returns: false. +function all_positive(x) = + is_num(x)? x>0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : + false; + + +// Function: all_negative() +// Usage: +// test = all_negative(x); +// Description: +// Returns true if the finite number passed to it is less than zero. +// If passed a list, recursively checks if all items in the list are negative. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// a = all_negative(-2); // Returns: true. +// b = all_negative(0); // Returns: false. +// c = all_negative(2); // Returns: false. +// d = all_negative([0,0,0]); // Returns: false. +// e = all_negative([0,1,2]); // Returns: false. +// f = all_negative([3,1,2]); // Returns: false. +// g = all_negative([3,-1,2]); // Returns: false. +// h = all_negative([-3,-1,-2]); // Returns: true. +function all_negative(x) = + is_num(x)? x<0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : + false; + + +// Function: all_nonpositive() +// Usage: +// all_nonpositive(x); +// Description: +// Returns true if the finite number passed to it is less than or equal to zero. +// If passed a list, recursively checks if all items in the list are nonpositive. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// a = all_nonpositive(-2); // Returns: true. +// b = all_nonpositive(0); // Returns: true. +// c = all_nonpositive(2); // Returns: false. +// d = all_nonpositive([0,0,0]); // Returns: true. +// e = all_nonpositive([0,1,2]); // Returns: false. +// f = all_nonpositive([3,1,2]); // Returns: false. +// g = all_nonpositive([3,-1,2]); // Returns: false. +// h = all_nonpositive([-3,-1,-2]); // Returns: true. +function all_nonpositive(x) = + is_num(x)? x<=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : + false; + + +// Function: all_nonnegative() +// Usage: +// all_nonnegative(x); +// Description: +// Returns true if the finite number passed to it is greater than or equal to zero. +// If passed a list, recursively checks if all items in the list are nonnegative. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// a = all_nonnegative(-2); // Returns: false. +// b = all_nonnegative(0); // Returns: true. +// c = all_nonnegative(2); // Returns: true. +// d = all_nonnegative([0,0,0]); // Returns: true. +// e = all_nonnegative([0,1,2]); // Returns: true. +// f = all_nonnegative([0,-1,-2]); // Returns: false. +// g = all_nonnegative([3,1,2]); // Returns: true. +// h = all_nonnegative([3,-1,2]); // Returns: false. +// i = all_nonnegative([-3,-1,-2]); // Returns: false. +function all_nonnegative(x) = + is_num(x)? x>=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : + false; + + +// Function: all_equal() +// Usage: +// b = all_equal(vec, [eps]); +// Description: +// Returns true if all of the entries in vec are equal to each other, or approximately equal to each other if eps is set. +// Arguments: +// vec = vector to check +// eps = Set to tolerance for approximate equality. Default: 0 +function all_equal(vec,eps=0) = + eps==0 ? [for(v=vec) if (v!=vec[0]) v] == [] + : [for(v=vec) if (!approx(v,vec[0])) v] == []; + + + +// Function: is_increasing() +// Usage: +// bool = is_increasing(list); +// Topics: List Handling +// See Also: max_index(), min_index(), is_decreasing() +// Description: +// 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 = list (or string) to check +// strict = set to true to test that list is strictly increasing +// Example: +// a = is_increasing([1,2,3,4]); // Returns: true +// b = is_increasing([1,3,2,4]); // Returns: false +// c = is_increasing([1,3,3,4]); // Returns: true +// d = is_increasing([1,3,3,4],strict=true); // Returns: false +// e = is_increasing([4,3,2,1]); // Returns: false +function is_increasing(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>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.x0 if a>b. Returns 0 if a==b. +// If types are not the same, then undef < bool < nan < num < str < list < range. +// Arguments: +// a = First value to compare. +// b = Second value to compare. +function compare_vals(a, b) = + (a==b)? 0 : + let(t1=_type_num(a), t2=_type_num(b)) (t1!=t2)? (t1-t2) : + is_list(a)? compare_lists(a,b) : + is_nan(a)? 0 : + (ab)? 1 : 0; + + +// Function: compare_lists() +// Usage: +// test = compare_lists(a, b) +// Description: +// Compare contents of two lists using `compare_vals()`. +// Returns <0 if `a`<`b`. +// Returns 0 if `a`==`b`. +// Returns >0 if `a`>`b`. +// Arguments: +// a = First list to compare. +// b = Second list to compare. +function compare_lists(a, b) = + a==b? 0 : + let( + cmps = [ + for (i = [0:1:min(len(a),len(b))-1]) + let( cmp = compare_vals(a[i],b[i]) ) + if (cmp!=0) cmp + ] + ) + cmps==[]? (len(a)-len(b)) : cmps[0]; + + +// Function: list_smallest() +// Usage: +// small = list_smallest(list, k) +// Description: +// Returns a set of the k smallest items in list in arbitrary order. The items must be +// mutually comparable with native OpenSCAD comparison operations. You will get "undefined operation" +// errors if you provide invalid input. +// Arguments: +// list = list to process +// k = number of items to return +function list_smallest(list, k) = + assert(is_list(list)) + assert(is_finite(k) && k>=0, "k must be nonnegative") + let( + v = list[rand_int(0,len(list)-1,1)[0]], + smaller = [for(li=list) if(li= k ? [ each smaller, for(i=[1:k-len(smaller)]) v ] : + len(smaller) > k ? list_smallest(smaller, k) : + let( bigger = [for(li=list) if(li>v) li ] ) + concat(smaller, equal, list_smallest(bigger, k-len(smaller) -len(equal))); + + + +// Section: Dealing with duplicate list entries + + +// Function: find_approx() +// Topics: List Handling +// See Also: in_list() +// Usage: +// idx = find_approx(val, list, [start=], [eps=]); +// indices = find_approx(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_approx(val, list, start=0, all=false, eps=EPSILON) = + all ? [for (i=[start:1:len(list)-1]) if (approx(val, list[i], eps=eps)) i] + : __find_approx(val, list, eps=eps, i=start); + +function __find_approx(val, list, eps, i=0) = + i >= len(list)? undef : + approx(val, list[i], eps=eps) + ? i + : __find_approx(val, list, eps=eps, i=i+1); + + + +// Function: deduplicate() +// Usage: +// list = deduplicate(list, [close], [eps]); +// Topics: List Handling +// See Also: deduplicate_indexed() +// 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`. +// Unlike `unique()` only consecutive duplicates are removed and the list is *not* sorted. +// 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. +// 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)) + 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]]; + + +// Function: deduplicate_indexed() +// Usage: +// new_idxs = deduplicate_indexed(list, indices, [closed], [eps]); +// Topics: List Handling +// See Also: deduplicate() +// Description: +// Given a list, and a list of indices, removes consecutive indices corresponding to list values that are equal +// or approximately equal. +// Arguments: +// list = The list that the indices index into. +// indices = The list of indices to deduplicate. +// closed = If true, drops trailing indices if their list value matches the list value corresponding to the first index. +// 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(idx2pivot) li ] + ) + concat( + _unique_sort(lesser), + equal[0], + _unique_sort(greater) + ); + + +// Function: unique_count() +// Usage: +// counts = unique_count(list); +// Topics: List Handling +// See Also: shuffle(), sort(), sortidx(), unique() +// Description: +// Returns `[sorted,counts]` where `sorted` is a sorted list of the unique items in `list` and `counts` is a list such +// that `count[i]` gives the number of times that `sorted[i]` appears in `list`. +// Arguments: +// list = The list to analyze. +// Example: +// sorted = unique([5,2,8,3,1,3,8,3,5]); // Returns: [ [1,2,3,5,8], [1,1,3,2,2] ] +function unique_count(list) = + assert(is_list(list) || is_string(list), "Invalid input." ) + list == [] ? [[],[]] : + is_homogeneous(list,1) && ! is_list(list[0]) + ? let( sorted = _group_sort(list) ) + [ [for(s=sorted) s[0] ], [for(s=sorted) len(s) ] ] + : let( + list = sort(list), + ind = [0, for(i=[1:1:len(list)-1]) if (list[i]!=list[i-1]) i] + ) + [ select(list,ind), deltas( concat(ind,[len(list)]) ) ]; + + + + + + +// Section: Sorting + + +// returns true for valid index specifications idx in the interval [imin, imax) +// note that idx can't have any value greater or EQUAL to imax +// this allows imax=INF as a bound to numerical lists +function _valid_idx(idx,imin,imax) = + is_undef(idx) + || ( is_finite(idx) + && ( is_undef(imin) || idx>=imin ) + && ( is_undef(imax) || idx< imax ) ) + || ( is_list(idx) + && ( is_undef(imin) || min(idx)>=imin ) + && ( is_undef(imax) || max(idx)< imax ) ) + || ( is_range(idx) + && ( is_undef(imin) || (idx[1]>0 && idx[0]>=imin ) || (idx[1]<0 && idx[0]<=imax ) ) + && ( is_undef(imax) || (idx[1]>0 && idx[2]<=imax ) || (idx[1]<0 && idx[2]>=imin ) ) ); + +// idx should be an index of the arrays l[i] +function _group_sort_by_index(l,idx) = + len(l) == 0 ? [] : + len(l) == 1 ? [l] : + let( + pivot = l[floor(len(l)/2)][idx], + equal = [ for(li=l) if( li[idx]==pivot) li ], + lesser = [ for(li=l) if( li[idx]< pivot) li ], + greater = [ for(li=l) if( li[idx]> pivot) li ] + ) + concat( + _group_sort_by_index(lesser,idx), + [equal], + _group_sort_by_index(greater,idx) + ); + + +function _group_sort(l) = + len(l) == 0 ? [] : + len(l) == 1 ? [l] : + let( + pivot = l[floor(len(l)/2)], + equal = [ for(li=l) if( li==pivot) li ], + lesser = [ for(li=l) if( li< pivot) li ], + greater = [ for(li=l) if( li> pivot) li ] + ) + concat( + _group_sort(lesser), + [equal], + _group_sort(greater) + ); + + +// Sort a vector of scalar values with the native comparison operator +// all elements should have the same type. +function _sort_scalars(arr) = + len(arr)<=1 ? arr : + let( + pivot = arr[floor(len(arr)/2)], + lesser = [ for (y = arr) if (y < pivot) y ], + equal = [ for (y = arr) if (y == pivot) y ], + greater = [ for (y = arr) if (y > pivot) y ] + ) + concat( _sort_scalars(lesser), equal, _sort_scalars(greater) ); + + +// lexical sort of a homogeneous list of vectors +// uses native comparison operator +function _sort_vectors(arr, _i=0) = + len(arr)<=1 || _i>=len(arr[0]) ? arr : + let( + pivot = arr[floor(len(arr)/2)][_i], + lesser = [ for (entry=arr) if (entry[_i] < pivot ) entry ], + equal = [ for (entry=arr) if (entry[_i] == pivot ) entry ], + greater = [ for (entry=arr) if (entry[_i] > pivot ) entry ] + ) + concat( + _sort_vectors(lesser, _i ), + _sort_vectors(equal, _i+1 ), + _sort_vectors(greater, _i ) ); + + +// lexical sort of a homogeneous list of vectors by the vector components with indices in idxlist +// all idxlist indices should be in the range of the vector dimensions +// idxlist must be undef or a simple list of numbers +// uses native comparison operator +function _sort_vectors(arr, idxlist, _i=0) = + len(arr)<=1 || ( is_list(idxlist) && _i>=len(idxlist) ) || _i>=len(arr[0]) ? arr : + let( + k = is_list(idxlist) ? idxlist[_i] : _i, + pivot = arr[floor(len(arr)/2)][k], + lesser = [ for (entry=arr) if (entry[k] < pivot ) entry ], + equal = [ for (entry=arr) if (entry[k] == pivot ) entry ], + greater = [ for (entry=arr) if (entry[k] > pivot ) entry ] + ) + concat( + _sort_vectors(lesser, idxlist, _i ), + _sort_vectors(equal, idxlist, _i+1), + _sort_vectors(greater, idxlist, _i ) ); + + +// sorting using compare_vals(); returns indexed list when `indexed==true` +function _sort_general(arr, idx=undef, indexed=false) = + (len(arr)<=1) ? arr : + ! indexed && is_undef(idx) + ? _lexical_sort(arr) + : let( arrind = _indexed_sort(enumerate(arr,idx)) ) + indexed + ? arrind + : [for(i=arrind) arr[i]]; + +// lexical sort using compare_vals() +function _lexical_sort(arr) = + len(arr)<=1? arr : + let( pivot = arr[floor(len(arr)/2)] ) + let( + lesser = [ for (entry=arr) if (compare_vals(entry, pivot) <0 ) entry ], + equal = [ for (entry=arr) if (compare_vals(entry, pivot)==0 ) entry ], + greater = [ for (entry=arr) if (compare_vals(entry, pivot) >0 ) entry ] + ) + concat(_lexical_sort(lesser), equal, _lexical_sort(greater)); + + +// given a list of pairs, return the first element of each pair of the list sorted by the second element of the pair +// the sorting is done using compare_vals() +function _indexed_sort(arrind) = + arrind==[] ? [] : len(arrind)==1? [arrind[0][0]] : + let( pivot = arrind[floor(len(arrind)/2)][1] ) + let( + lesser = [ for (entry=arrind) if (compare_vals(entry[1], pivot) <0 ) entry ], + equal = [ for (entry=arrind) if (compare_vals(entry[1], pivot)==0 ) entry[0] ], + greater = [ for (entry=arrind) if (compare_vals(entry[1], pivot) >0 ) entry ] + ) + concat(_indexed_sort(lesser), equal, _indexed_sort(greater)); + + +// Function: sort() +// Usage: +// slist = sort(list, [idx]); +// Topics: List Handling +// See Also: shuffle(), sortidx(), unique(), unique_count(), group_sort() +// Description: +// Sorts the given list in lexicographic order. If the input is a homogeneous simple list or a homogeneous +// list of vectors (see function is_homogeneous), the sorting method uses the native comparison operator and is faster. +// When sorting non homogeneous list the elements are compared with `compare_vals`, with types ordered according to +// `undef < boolean < number < string < list`. Comparison of lists is recursive. +// When comparing vectors, homogeneous or not, the parameter `idx` may be used to select the components to compare. +// Note that homogeneous lists of vectors may contain mixed types provided that for any two list elements +// list[i] and list[j] satisfies type(list[i][k])==type(list[j][k]) for all k. +// Strings are allowed as any list element and are compared with the native operators although no substring +// comparison is possible. +// Arguments: +// list = The list to sort. +// idx = If given, do the comparison based just on the specified index, range or list of indices. +// Example: +// // Homogeneous lists +// l1 = [45,2,16,37,8,3,9,23,89,12,34]; +// sorted1 = sort(l1); // Returns [2,3,8,9,12,16,23,34,37,45,89] +// l2 = [["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]]; +// sorted2 = sort(l2); // Returns: [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]] +// // Non-homegenous list +// l3 = [[4,0],[7],[3,9],20,[4],[3,1],[8]]; +// sorted3 = sort(l3); // Returns: [20,[3,1],[3,9],[4],[4,0],[7],[8]] +function sort(list, idx=undef) = + assert(is_list(list)||is_string(list), "Invalid input." ) + is_string(list)? str_join(sort([for (x = list) x],idx)) : + !is_list(list) || len(list)<=1 ? list : + is_homogeneous(list,1) + ? let(size = array_dim(list[0])) + size==0 ? _sort_scalars(list) + : len(size)!=1 ? _sort_general(list,idx) + : is_undef(idx) ? _sort_vectors(list) + : assert( _valid_idx(idx) , "Invalid indices.") + _sort_vectors(list,[for(i=idx) i]) + : _sort_general(list,idx); + + +// Function: sortidx() +// Usage: +// idxlist = sortidx(list, [idx]); +// Topics: List Handling +// See Also: shuffle(), sort(), group_sort(), unique(), unique_count() +// Description: +// Given a list, sort it as function `sort()`, and returns +// a list of indexes into the original list in that sorted order. +// If you iterate the returned list in order, and use the list items +// to index into the original list, you will be iterating the original +// values in sorted order. +// Arguments: +// list = The list to sort. +// idx = If given, do the comparison based just on the specified index, range or list of indices. +// Example: +// lst = ["d","b","e","c"]; +// idxs = sortidx(lst); // Returns: [1,3,0,2] +// ordered = select(lst, idxs); // Returns: ["b", "c", "d", "e"] +// Example: +// lst = [ +// ["foo", 88, [0,0,1], false], +// ["bar", 90, [0,1,0], true], +// ["baz", 89, [1,0,0], false], +// ["qux", 23, [1,1,1], true] +// ]; +// idxs1 = sortidx(lst, idx=1); // Returns: [3,0,2,1] +// idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3] +// idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1] +function sortidx(list, idx=undef) = + assert(is_list(list)||is_string(list), "Invalid input." ) + !is_list(list) || len(list)<=1 ? list : + is_homogeneous(list,1) + ? let( + size = array_dim(list[0]), + aug = ! (size==0 || len(size)==1) ? 0 // for general sorting + : [for(i=[0:len(list)-1]) concat(i,list[i])], // for scalar or vector sorting + lidx = size==0? [1] : // scalar sorting + len(size)==1 + ? is_undef(idx) ? [for(i=[0:len(list[0])-1]) i+1] // vector sorting + : [for(i=idx) i+1] // vector sorting + : 0 // just to signal + ) + assert( ! ( size==0 && is_def(idx) ), + "The specification of `idx` is incompatible with scalar sorting." ) + assert( _valid_idx(idx) , "Invalid indices." ) + lidx!=0 + ? let( lsort = _sort_vectors(aug,lidx) ) + [for(li=lsort) li[0] ] + : _sort_general(list,idx,indexed=true) + : _sort_general(list,idx,indexed=true); + + + + +// Function: group_sort() +// Usage: +// ulist = group_sort(list); +// Topics: List Handling +// See Also: shuffle(), sort(), sortidx(), unique(), unique_count() +// 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. +// Arguments: +// list = The list to sort. +// idx = If given, do the comparison based just on the specified index. Default: zero. +// 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) ////////////////////////////////////////////////////////////////////// @@ -9,7 +9,6 @@ // **List** = An ordered collection of zero or more items. ie: `["a", "b", "c"]` // **Vector** = A list of numbers. ie: `[4, 5, 6]` // **Array** = A nested list of lists, or list of lists of lists, or deeper. ie: `[[2,3], [4,5], [6,7]]` -// **Dimension** = The depth of nesting of lists in an array. A List is 1D. A list of lists is 2D. etc. // **Set** = A list of unique items. @@ -126,110 +125,6 @@ function add_scalar(v,s) = -// Section: Operations using approx() - - -// Function: deduplicate() -// Usage: -// list = deduplicate(list, [close], [eps]); -// Topics: List Handling -// See Also: deduplicate_indexed() -// 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. -// 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. -// 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)) - 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]]; - - -// 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(idx2= len(list)? undef : - approx(val, list[i], eps=eps) - ? i - : __find_approx(val, list, eps=eps, i=i+1); - // Section: List Indexing @@ -529,10 +424,6 @@ function force_list(value, n=1, fill) = is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill]; - - - - // Section: List Modification // Function: reverse() @@ -873,442 +764,6 @@ function list_fit(array, length, fill) = : list_pad(array,length,fill); -// Section: Sorting - - -// returns true for valid index specifications idx in the interval [imin, imax) -// note that idx can't have any value greater or EQUAL to imax -// this allows imax=INF as a bound to numerical lists -function _valid_idx(idx,imin,imax) = - is_undef(idx) - || ( is_finite(idx) - && ( is_undef(imin) || idx>=imin ) - && ( is_undef(imax) || idx< imax ) ) - || ( is_list(idx) - && ( is_undef(imin) || min(idx)>=imin ) - && ( is_undef(imax) || max(idx)< imax ) ) - || ( is_range(idx) - && ( is_undef(imin) || (idx[1]>0 && idx[0]>=imin ) || (idx[1]<0 && idx[0]<=imax ) ) - && ( is_undef(imax) || (idx[1]>0 && idx[2]<=imax ) || (idx[1]<0 && idx[2]>=imin ) ) ); - -// idx should be an index of the arrays l[i] -function _group_sort_by_index(l,idx) = - len(l) == 0 ? [] : - len(l) == 1 ? [l] : - let( - pivot = l[floor(len(l)/2)][idx], - equal = [ for(li=l) if( li[idx]==pivot) li ], - lesser = [ for(li=l) if( li[idx]< pivot) li ], - greater = [ for(li=l) if( li[idx]> pivot) li ] - ) - concat( - _group_sort_by_index(lesser,idx), - [equal], - _group_sort_by_index(greater,idx) - ); - - -function _group_sort(l) = - len(l) == 0 ? [] : - len(l) == 1 ? [l] : - let( - pivot = l[floor(len(l)/2)], - equal = [ for(li=l) if( li==pivot) li ], - lesser = [ for(li=l) if( li< pivot) li ], - greater = [ for(li=l) if( li> pivot) li ] - ) - concat( - _group_sort(lesser), - [equal], - _group_sort(greater) - ); - - -// Sort a vector of scalar values with the native comparison operator -// all elements should have the same type. -function _sort_scalars(arr) = - len(arr)<=1 ? arr : - let( - pivot = arr[floor(len(arr)/2)], - lesser = [ for (y = arr) if (y < pivot) y ], - equal = [ for (y = arr) if (y == pivot) y ], - greater = [ for (y = arr) if (y > pivot) y ] - ) - concat( _sort_scalars(lesser), equal, _sort_scalars(greater) ); - - -// lexical sort of a homogeneous list of vectors -// uses native comparison operator -function _sort_vectors(arr, _i=0) = - len(arr)<=1 || _i>=len(arr[0]) ? arr : - let( - pivot = arr[floor(len(arr)/2)][_i], - lesser = [ for (entry=arr) if (entry[_i] < pivot ) entry ], - equal = [ for (entry=arr) if (entry[_i] == pivot ) entry ], - greater = [ for (entry=arr) if (entry[_i] > pivot ) entry ] - ) - concat( - _sort_vectors(lesser, _i ), - _sort_vectors(equal, _i+1 ), - _sort_vectors(greater, _i ) ); - - -// lexical sort of a homogeneous list of vectors by the vector components with indices in idxlist -// all idxlist indices should be in the range of the vector dimensions -// idxlist must be undef or a simple list of numbers -// uses native comparison operator -function _sort_vectors(arr, idxlist, _i=0) = - len(arr)<=1 || ( is_list(idxlist) && _i>=len(idxlist) ) || _i>=len(arr[0]) ? arr : - let( - k = is_list(idxlist) ? idxlist[_i] : _i, - pivot = arr[floor(len(arr)/2)][k], - lesser = [ for (entry=arr) if (entry[k] < pivot ) entry ], - equal = [ for (entry=arr) if (entry[k] == pivot ) entry ], - greater = [ for (entry=arr) if (entry[k] > pivot ) entry ] - ) - concat( - _sort_vectors(lesser, idxlist, _i ), - _sort_vectors(equal, idxlist, _i+1), - _sort_vectors(greater, idxlist, _i ) ); - - -// sorting using compare_vals(); returns indexed list when `indexed==true` -function _sort_general(arr, idx=undef, indexed=false) = - (len(arr)<=1) ? arr : - ! indexed && is_undef(idx) - ? _lexical_sort(arr) - : let( arrind = _indexed_sort(enumerate(arr,idx)) ) - indexed - ? arrind - : [for(i=arrind) arr[i]]; - -// lexical sort using compare_vals() -function _lexical_sort(arr) = - len(arr)<=1? arr : - let( pivot = arr[floor(len(arr)/2)] ) - let( - lesser = [ for (entry=arr) if (compare_vals(entry, pivot) <0 ) entry ], - equal = [ for (entry=arr) if (compare_vals(entry, pivot)==0 ) entry ], - greater = [ for (entry=arr) if (compare_vals(entry, pivot) >0 ) entry ] - ) - concat(_lexical_sort(lesser), equal, _lexical_sort(greater)); - - -// given a list of pairs, return the first element of each pair of the list sorted by the second element of the pair -// the sorting is done using compare_vals() -function _indexed_sort(arrind) = - arrind==[] ? [] : len(arrind)==1? [arrind[0][0]] : - let( pivot = arrind[floor(len(arrind)/2)][1] ) - let( - lesser = [ for (entry=arrind) if (compare_vals(entry[1], pivot) <0 ) entry ], - equal = [ for (entry=arrind) if (compare_vals(entry[1], pivot)==0 ) entry[0] ], - greater = [ for (entry=arrind) if (compare_vals(entry[1], pivot) >0 ) entry ] - ) - concat(_indexed_sort(lesser), equal, _indexed_sort(greater)); - - -// Function: sort() -// Usage: -// slist = sort(list, [idx]); -// Topics: List Handling -// See Also: shuffle(), sortidx(), unique(), unique_count(), group_sort() -// Description: -// Sorts the given list in lexicographic order. If the input is a homogeneous simple list or a homogeneous -// list of vectors (see function is_homogeneous), the sorting method uses the native comparison operator and is faster. -// When sorting non homogeneous list the elements are compared with `compare_vals`, with types ordered according to -// `undef < boolean < number < string < list`. Comparison of lists is recursive. -// When comparing vectors, homogeneous or not, the parameter `idx` may be used to select the components to compare. -// Note that homogeneous lists of vectors may contain mixed types provided that for any two list elements -// list[i] and list[j] satisfies type(list[i][k])==type(list[j][k]) for all k. -// Strings are allowed as any list element and are compared with the native operators although no substring -// comparison is possible. -// Arguments: -// list = The list to sort. -// idx = If given, do the comparison based just on the specified index, range or list of indices. -// Example: -// // Homogeneous lists -// l1 = [45,2,16,37,8,3,9,23,89,12,34]; -// sorted1 = sort(l1); // Returns [2,3,8,9,12,16,23,34,37,45,89] -// l2 = [["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]]; -// sorted2 = sort(l2); // Returns: [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]] -// // Non-homegenous list -// l3 = [[4,0],[7],[3,9],20,[4],[3,1],[8]]; -// sorted3 = sort(l3); // Returns: [20,[3,1],[3,9],[4],[4,0],[7],[8]] -function sort(list, idx=undef) = - assert(is_list(list)||is_string(list), "Invalid input." ) - is_string(list)? str_join(sort([for (x = list) x],idx)) : - !is_list(list) || len(list)<=1 ? list : - is_homogeneous(list,1) - ? let(size = array_dim(list[0])) - size==0 ? _sort_scalars(list) - : len(size)!=1 ? _sort_general(list,idx) - : is_undef(idx) ? _sort_vectors(list) - : assert( _valid_idx(idx) , "Invalid indices.") - _sort_vectors(list,[for(i=idx) i]) - : _sort_general(list,idx); - - -// Function: sortidx() -// Usage: -// idxlist = sortidx(list, [idx]); -// Topics: List Handling -// See Also: shuffle(), sort(), group_sort(), unique(), unique_count() -// Description: -// Given a list, sort it as function `sort()`, and returns -// a list of indexes into the original list in that sorted order. -// If you iterate the returned list in order, and use the list items -// to index into the original list, you will be iterating the original -// values in sorted order. -// Arguments: -// list = The list to sort. -// idx = If given, do the comparison based just on the specified index, range or list of indices. -// Example: -// lst = ["d","b","e","c"]; -// idxs = sortidx(lst); // Returns: [1,3,0,2] -// ordered = select(lst, idxs); // Returns: ["b", "c", "d", "e"] -// Example: -// lst = [ -// ["foo", 88, [0,0,1], false], -// ["bar", 90, [0,1,0], true], -// ["baz", 89, [1,0,0], false], -// ["qux", 23, [1,1,1], true] -// ]; -// idxs1 = sortidx(lst, idx=1); // Returns: [3,0,2,1] -// idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3] -// idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1] -function sortidx(list, idx=undef) = - assert(is_list(list)||is_string(list), "Invalid input." ) - !is_list(list) || len(list)<=1 ? list : - is_homogeneous(list,1) - ? let( - size = array_dim(list[0]), - aug = ! (size==0 || len(size)==1) ? 0 // for general sorting - : [for(i=[0:len(list)-1]) concat(i,list[i])], // for scalar or vector sorting - lidx = size==0? [1] : // scalar sorting - len(size)==1 - ? is_undef(idx) ? [for(i=[0:len(list[0])-1]) i+1] // vector sorting - : [for(i=idx) i+1] // vector sorting - : 0 // just to signal - ) - assert( ! ( size==0 && is_def(idx) ), - "The specification of `idx` is incompatible with scalar sorting." ) - assert( _valid_idx(idx) , "Invalid indices." ) - lidx!=0 - ? let( lsort = _sort_vectors(aug,lidx) ) - [for(li=lsort) li[0] ] - : _sort_general(list,idx,indexed=true) - : _sort_general(list,idx,indexed=true); - - - -// Function: is_increasing() -// Usage: -// bool = is_increasing(list); -// Topics: List Handling -// See Also: max_index(), min_index(), is_decreasing() -// Description: -// 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 = list (or string) to check -// strict = set to true to test that list is strictly increasing -// Example: -// a = is_increasing([1,2,3,4]); // Returns: true -// b = is_increasing([1,3,2,4]); // Returns: false -// c = is_increasing([1,3,3,4]); // Returns: true -// d = is_increasing([1,3,3,4],strict=true); // Returns: false -// e = is_increasing([4,3,2,1]); // Returns: false -function is_increasing(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>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.xpivot) li ] - ) - concat( - _unique_sort(lesser), - equal[0], - _unique_sort(greater) - ); - - -// Function: unique_count() -// Usage: -// counts = unique_count(list); -// Topics: List Handling -// See Also: shuffle(), sort(), sortidx(), unique() -// Description: -// Returns `[sorted,counts]` where `sorted` is a sorted list of the unique items in `list` and `counts` is a list such -// that `count[i]` gives the number of times that `sorted[i]` appears in `list`. -// Arguments: -// list = The list to analyze. -// Example: -// sorted = unique([5,2,8,3,1,3,8,3,5]); // Returns: [ [1,2,3,5,8], [1,1,3,2,2] ] -function unique_count(list) = - assert(is_list(list) || is_string(list), "Invalid input." ) - list == [] ? [[],[]] : - is_homogeneous(list,1) && ! is_list(list[0]) - ? let( sorted = _group_sort(list) ) - [ [for(s=sorted) s[0] ], [for(s=sorted) len(s) ] ] - : let( - list = sort(list), - ind = [0, for(i=[1:1:len(list)-1]) if (list[i]!=list[i-1]) i] - ) - [ select(list,ind), deltas( concat(ind,[len(list)]) ) ]; - - - -// Function: group_sort() -// Usage: -// ulist = group_sort(list); -// Topics: List Handling -// See Also: shuffle(), sort(), sortidx(), unique(), unique_count() -// 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. -// Arguments: -// list = The list to sort. -// idx = If given, do the comparison based just on the specified index. Default: zero. -// 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)=0, "k must be nonnegative") - let( - v = list[rand_int(0,len(list)-1,1)[0]], - smaller = [for(li=list) if(li= k ? [ each smaller, for(i=[1:k-len(smaller)]) v ] : - len(smaller) > k ? list_smallest(smaller, k) : - let( bigger = [for(li=list) if(li>v) li ] ) - concat(smaller, equal, list_smallest(bigger, k-len(smaller) -len(equal))); - - -// Function: group_data() -// Usage: -// groupings = group_data(groups, values); -// Topics: Array Handling -// See Also: zip(), zip_long(), array_group() -// Description: -// Given a list of integer group numbers, and an equal-length list of values, -// returns a list of groups with the values sorted into the corresponding groups. -// Ie: if you have a groups index list of [2,3,2] and values of ["A","B","C"], then -// the values "A" and "C" will be put in group 2, and "B" will be in group 3. -// Groups that have no values grouped into them will be an empty list. So the -// above would return [[], [], ["A","C"], ["B"]] -// Arguments: -// groups = A list of integer group index numbers. -// values = A list of values to sort into groups. -// Example: -// groups = group_data([1,2,0], ["A","B","C"]); // Returns [["B"],["C"],["A"]] -// Example: -// groups = group_data([1,3,1], ["A","B","C"]); // Returns [[],["A","C"],[],["B"]] -function group_data(groups, values) = - assert(all_integer(groups) && all_nonnegative(groups)) - assert(is_list(values)) - assert(len(groups)==len(values), - "The groups and values arguments should be lists of matching length.") - let( sorted = _group_sort_by_index(zip(groups,values),0) ) - // retrieve values and insert [] - [ - for (i = idx(sorted)) - let( - a = i==0? 0 : sorted[i-1][0][0]+1, - g0 = sorted[i] - ) - each [ - for (j = [a:1:g0[0][0]-1]) [], - [for (g1 = g0) g1[1]] - ] - ]; - - - - // Section: Iteration Helpers // Function: idx() @@ -1471,6 +926,175 @@ function permutations(l,n=2) = + + +// Section: Changing list structure + + +// Internal. Not exposed. +function _array_dim_recurse(v) = + !is_list(v[0]) + ? len( [for(entry=v) if(!is_list(entry)) 0] ) == 0 ? [] : [undef] + : let( + firstlen = is_list(v[0]) ? len(v[0]): undef, + first = len( [for(entry = v) if(! is_list(entry) || (len(entry) != firstlen)) 0 ] ) == 0 ? firstlen : undef, + leveldown = flatten(v) + ) + is_list(leveldown[0]) + ? concat([first],_array_dim_recurse(leveldown)) + : [first]; + +function _array_dim_recurse(v) = + let( alen = [for(vi=v) is_list(vi) ? len(vi): -1] ) + v==[] || max(alen)==-1 ? [] : + let( add = max(alen)!=min(alen) ? undef : alen[0] ) + concat( add, _array_dim_recurse(flatten(v))); + + +// Function: array_dim() +// Usage: +// dims = array_dim(v, [depth]); +// Topics: Matrices, Array Handling +// Description: +// Returns the size of a multi-dimensional array. Returns a list of dimension lengths. The length +// of `v` is the dimension `0`. The length of the items in `v` is dimension `1`. The length of the +// items in the items in `v` is dimension `2`, etc. For each dimension, if the length of items at +// that depth is inconsistent, `undef` will be returned. If no items of that dimension depth exist, +// `0` is returned. Otherwise, the consistent length of items in that dimensional depth is +// returned. +// Arguments: +// v = Array to get dimensions of. +// depth = Dimension to get size of. If not given, returns a list of dimension lengths. +// Example: +// a = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]); // Returns [2,2,3] +// b = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0); // Returns 2 +// c = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2); // Returns 3 +// d = array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]); // Returns [2,undef,3] +function array_dim(v, depth=undef) = + assert( is_undef(depth) || ( is_finite(depth) && depth>=0 ), "Invalid depth.") + ! is_list(v) ? 0 : + (depth == undef) + ? concat([len(v)], _array_dim_recurse(v)) + : (depth == 0) + ? len(v) + : let( dimlist = _array_dim_recurse(v)) + (depth > len(dimlist))? 0 : dimlist[depth-1] ; + + +// Function: list_to_matrix() +// Usage: +// groups = list_to_matrix(v, [cnt], [dflt]); +// Description: +// Takes a flat array of values, and groups items in sets of `cnt` length. +// The opposite of this is `flatten()`. +// Topics: Matrices, Array Handling +// See Also: column(), submatrix(), hstack(), flatten(), full_flatten() +// Arguments: +// v = The list of items to group. +// cnt = The number of items to put in each grouping. Default:2 +// dflt = The default value to fill in with if the list is not a multiple of `cnt` items long. Default: 0 +// Example: +// v = [1,2,3,4,5,6]; +// a = list_to_matrix(v,2) returns [[1,2], [3,4], [5,6]] +// b = list_to_matrix(v,3) returns [[1,2,3], [4,5,6]] +// c = list_to_matrix(v,4,0) returns [[1,2,3,4], [5,6,0,0]] +function list_to_matrix(v, cnt=2, dflt=0) = + [for (i = [0:cnt:len(v)-1]) [for (j = [0:1:cnt-1]) default(v[i+j], dflt)]]; + + + +// Function: flatten() +// Usage: +// list = flatten(l); +// Topics: Matrices, Array Handling +// See Also: column(), submatrix(), hstack(), full_flatten() +// Description: +// Takes a list of lists and flattens it by one level. +// Arguments: +// l = List to flatten. +// Example: +// l = flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,[6,7,8]] +function flatten(l) = + !is_list(l)? l : + [for (a=l) if (is_list(a)) (each a) else a]; + + +// Function: full_flatten() +// Usage: +// list = full_flatten(l); +// Topics: Matrices, Array Handling +// See Also: column(), submatrix(), hstack(), flatten() +// Description: +// Collects in a list all elements recursively found in any level of the given list. +// The output list is ordered in depth first order. +// Arguments: +// l = List to flatten. +// Example: +// l = full_flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,6,7,8] +function full_flatten(l) = + !is_list(l)? l : + [for (a=l) if (is_list(a)) (each full_flatten(a)) else a]; + + + +// Function: zip() +// Usage: +// pairs = zip(a,b); +// triples = zip(a,b,c); +// quads = zip([LIST1,LIST2,LIST3,LIST4]); +// Topics: List Handling, Iteration +// See Also: zip_long() +// Description: +// Zips together two or more lists into a single list. For example, if you have two +// lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. +// The list returned will be as long as the shortest list passed to zip(). +// Arguments: +// a = The first list, or a list of lists if b and c are not given. +// b = The second list, if given. +// c = The third list, if given. +// Example: +// a = [9,8,7,6]; b = [1,2,3]; +// for (p=zip(a,b)) echo(p); +// // ECHO: [9,1] +// // ECHO: [8,2] +// // ECHO: [7,3] +function zip(a,b,c) = + b!=undef? zip([a,b,if (c!=undef) c]) : + let(n = min_length(a)) + [for (i=[0:1:n-1]) [for (x=a) x[i]]]; + + +// Function: zip_long() +// Usage: +// pairs = zip_long(a,b); +// triples = zip_long(a,b,c); +// quads = zip_long([LIST1,LIST2,LIST3,LIST4]); +// Topics: List Handling, Iteration +// See Also: zip() +// Description: +// Zips together two or more lists into a single list. For example, if you have two +// lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. +// The list returned will be as long as the longest list passed to zip_long(), with +// shorter lists padded by the value in `fill`. +// Arguments: +// a = The first list, or a list of lists if b and c are not given. +// b = The second list, if given. +// c = The third list, if given. +// fill = The value to pad shorter lists with. Default: undef +// Example: +// a = [9,8,7,6]; b = [1,2,3]; +// for (p=zip_long(a,b,fill=88)) echo(p); +// // ECHO: [9,1] +// // ECHO: [8,2] +// // ECHO: [7,3] +// // ECHO: [6,88]] +function zip_long(a,b,c,fill) = + b!=undef? zip_long([a,b,if (c!=undef) c],fill=fill) : + let(n = max_length(a)) + [for (i=[0:1:n-1]) [for (x=a) i=0 ), "Invalid depth.") - ! is_list(v) ? 0 : - (depth == undef) - ? concat([len(v)], _array_dim_recurse(v)) - : (depth == 0) - ? len(v) - : let( dimlist = _array_dim_recurse(v)) - (depth > len(dimlist))? 0 : dimlist[depth-1] ; - - -// Function: zip() -// Usage: -// pairs = zip(a,b); -// triples = zip(a,b,c); -// quads = zip([LIST1,LIST2,LIST3,LIST4]); -// Topics: List Handling, Iteration -// See Also: zip_long() -// Description: -// Zips together two or more lists into a single list. For example, if you have two -// lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. -// The list returned will be as long as the shortest list passed to zip(). -// Arguments: -// a = The first list, or a list of lists if b and c are not given. -// b = The second list, if given. -// c = The third list, if given. -// Example: -// a = [9,8,7,6]; b = [1,2,3]; -// for (p=zip(a,b)) echo(p); -// // ECHO: [9,1] -// // ECHO: [8,2] -// // ECHO: [7,3] -function zip(a,b,c) = - b!=undef? zip([a,b,if (c!=undef) c]) : - let(n = min_length(a)) - [for (i=[0:1:n-1]) [for (x=a) x[i]]]; - - -// Function: zip_long() -// Usage: -// pairs = zip_long(a,b); -// triples = zip_long(a,b,c); -// quads = zip_long([LIST1,LIST2,LIST3,LIST4]); -// Topics: List Handling, Iteration -// See Also: zip() -// Description: -// Zips together two or more lists into a single list. For example, if you have two -// lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. -// The list returned will be as long as the longest list passed to zip_long(), with -// shorter lists padded by the value in `fill`. -// Arguments: -// a = The first list, or a list of lists if b and c are not given. -// b = The second list, if given. -// c = The third list, if given. -// fill = The value to pad shorter lists with. Default: undef -// Example: -// a = [9,8,7,6]; b = [1,2,3]; -// for (p=zip_long(a,b,fill=88)) echo(p); -// // ECHO: [9,1] -// // ECHO: [8,2] -// // ECHO: [7,3] -// // ECHO: [6,88]] -function zip_long(a,b,c,fill) = - b!=undef? zip_long([a,b,if (c!=undef) c],fill=fill) : - let(n = max_length(a)) - [for (i=[0:1:n-1]) [for (x=a) i0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : - false; - - -// Function: all_negative() -// Usage: -// test = all_negative(x); -// Description: -// Returns true if the finite number passed to it is less than zero. -// If passed a list, recursively checks if all items in the list are negative. -// Otherwise, returns false. -// Arguments: -// x = The value to check. -// Example: -// a = all_negative(-2); // Returns: true. -// b = all_negative(0); // Returns: false. -// c = all_negative(2); // Returns: false. -// d = all_negative([0,0,0]); // Returns: false. -// e = all_negative([0,1,2]); // Returns: false. -// f = all_negative([3,1,2]); // Returns: false. -// g = all_negative([3,-1,2]); // Returns: false. -// h = all_negative([-3,-1,-2]); // Returns: true. -function all_negative(x) = - is_num(x)? x<0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : - false; - - -// Function: all_nonpositive() -// Usage: -// all_nonpositive(x); -// Description: -// Returns true if the finite number passed to it is less than or equal to zero. -// If passed a list, recursively checks if all items in the list are nonpositive. -// Otherwise, returns false. -// Arguments: -// x = The value to check. -// Example: -// a = all_nonpositive(-2); // Returns: true. -// b = all_nonpositive(0); // Returns: true. -// c = all_nonpositive(2); // Returns: false. -// d = all_nonpositive([0,0,0]); // Returns: true. -// e = all_nonpositive([0,1,2]); // Returns: false. -// f = all_nonpositive([3,1,2]); // Returns: false. -// g = all_nonpositive([3,-1,2]); // Returns: false. -// h = all_nonpositive([-3,-1,-2]); // Returns: true. -function all_nonpositive(x) = - is_num(x)? x<=0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : - false; - - -// Function: all_nonnegative() -// Usage: -// all_nonnegative(x); -// Description: -// Returns true if the finite number passed to it is greater than or equal to zero. -// If passed a list, recursively checks if all items in the list are nonnegative. -// Otherwise, returns false. -// Arguments: -// x = The value to check. -// Example: -// a = all_nonnegative(-2); // Returns: false. -// b = all_nonnegative(0); // Returns: true. -// c = all_nonnegative(2); // Returns: true. -// d = all_nonnegative([0,0,0]); // Returns: true. -// e = all_nonnegative([0,1,2]); // Returns: true. -// f = all_nonnegative([0,-1,-2]); // Returns: false. -// g = all_nonnegative([3,1,2]); // Returns: true. -// h = all_nonnegative([3,-1,2]); // Returns: false. -// i = all_nonnegative([-3,-1,-2]); // Returns: false. -function all_nonnegative(x) = - is_num(x)? x>=0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : - false; - - -// Function all_equal() -// Usage: -// b = all_equal(vec, [eps]); -// Description: -// Returns true if all of the entries in vec are equal to each other, or approximately equal to each other if eps is set. -// Arguments: -// vec = vector to check -// eps = Set to tolerance for approximate equality. Default: 0 -function all_equal(vec,eps=0) = - eps==0 ? [for(v=vec) if (v!=vec[0]) v] == [] - : [for(v=vec) if (!approx(v,vec[0])) v] == []; // Function: all_integer() @@ -1044,76 +892,6 @@ function all_integer(x) = false; -// Function: approx() -// Usage: -// test = approx(a, b, [eps]) -// Description: -// Compares two numbers or vectors, and returns true if they are closer than `eps` to each other. -// Arguments: -// a = First value. -// b = Second value. -// eps = The maximum allowed difference between `a` and `b` that will return true. -// Example: -// test1 = approx(-0.3333333333,-1/3); // Returns: true -// test2 = approx(0.3333333333,1/3); // Returns: true -// test3 = approx(0.3333,1/3); // Returns: false -// test4 = approx(0.3333,1/3,eps=1e-3); // Returns: true -// test5 = approx(PI,3.1415926536); // Returns: true -function approx(a,b,eps=EPSILON) = - (a==b && is_bool(a) == is_bool(b)) || - (is_num(a) && is_num(b) && abs(a-b) <= eps) || - (is_list(a) && is_list(b) && len(a) == len(b) && [] == [for (i=idx(a)) if (!approx(a[i],b[i],eps=eps)) 1]); - - -function _type_num(x) = - is_undef(x)? 0 : - is_bool(x)? 1 : - is_num(x)? 2 : - is_nan(x)? 3 : - is_string(x)? 4 : - is_list(x)? 5 : 6; - - -// Function: compare_vals() -// Usage: -// test = compare_vals(a, b); -// Description: -// Compares two values. Lists are compared recursively. -// Returns <0 if a0 if a>b. Returns 0 if a==b. -// If types are not the same, then undef < bool < nan < num < str < list < range. -// Arguments: -// a = First value to compare. -// b = Second value to compare. -function compare_vals(a, b) = - (a==b)? 0 : - let(t1=_type_num(a), t2=_type_num(b)) (t1!=t2)? (t1-t2) : - is_list(a)? compare_lists(a,b) : - is_nan(a)? 0 : - (ab)? 1 : 0; - - -// Function: compare_lists() -// Usage: -// test = compare_lists(a, b) -// Description: -// Compare contents of two lists using `compare_vals()`. -// Returns <0 if `a`<`b`. -// Returns 0 if `a`==`b`. -// Returns >0 if `a`>`b`. -// Arguments: -// a = First list to compare. -// b = Second list to compare. -function compare_lists(a, b) = - a==b? 0 : - let( - cmps = [ - for (i = [0:1:min(len(a),len(b))-1]) - let( cmp = compare_vals(a[i],b[i]) ) - if (cmp!=0) cmp - ] - ) - cmps==[]? (len(a)-len(b)) : cmps[0]; - // Function: any() // Usage: @@ -1230,7 +1008,6 @@ function _count_true_bool(l, nmax, i=0, out=0) = ); - // Section: Calculus // Function: deriv() diff --git a/std.scad b/std.scad index bde5526..490ed21 100644 --- a/std.scad +++ b/std.scad @@ -20,7 +20,8 @@ include include include include -include +include +include include include include diff --git a/tests/test_comparisons.scad b/tests/test_comparisons.scad new file mode 100644 index 0000000..b7baf10 --- /dev/null +++ b/tests/test_comparisons.scad @@ -0,0 +1,280 @@ +module test_sort() { + assert(sort([7,3,9,4,3,1,8]) == [1,3,3,4,7,8,9]); + assert(sort([[4,0],[7],[3,9],20,[4],[3,1],[8]]) == [20,[3,1],[3,9],[4],[4,0],[7],[8]]); + assert(sort([[4,0],[7],[3,9],20,[4],[3,1],[8]],idx=1) == [[7],20,[4],[8],[4,0],[3,1],[3,9]]); + assert(sort([[8,6],[3,1],[9,2],[4,3],[3,4],[1,5],[8,0]]) == [[1,5],[3,1],[3,4],[4,3],[8,0],[8,6],[9,2]]); + assert(sort([[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]],idx=1) == [[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]]); + assert(sort(["cat", "oat", "sat", "bat", "vat", "rat", "pat", "mat", "fat", "hat", "eat"]) + == ["bat", "cat", "eat", "fat", "hat", "mat", "oat", "pat", "rat", "sat", "vat"]); + assert(sort(enumerate([[2,3,4],[1,2,3],[2,4,3]]),idx=1)==[[1,[1,2,3]], [0,[2,3,4]], [2,[2,4,3]]]); + assert(sort([0,"1",[1,0],2,"a",[1]])== [0,2,"1","a",[1],[1,0]]); + assert(sort([["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]])== [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]]); +} +test_sort(); + + +module test_sortidx() { + lst1 = ["da","bax","eaw","cav"]; + assert(sortidx(lst1) == [1,3,0,2]); + lst5 = [3,5,1,7]; + assert(sortidx(lst5) == [2,0,1,3]); + lst2 = [ + ["foo", 88, [0,0,1], false], + ["bar", 90, [0,1,0], true], + ["baz", 89, [1,0,0], false], + ["qux", 23, [1,1,1], true] + ]; + assert(sortidx(lst2, idx=1) == [3,0,2,1]); + assert(sortidx(lst2, idx=0) == [1,2,0,3]); + assert(sortidx(lst2, idx=[1,3]) == [3,0,2,1]); + lst3 = [[-4,0,0],[0,0,-4],[0,-4,0],[-4,0,0],[0,-4,0],[0,0,4], + [0,0,-4],[0,4,0],[4,0,0],[0,0,4],[0,4,0],[4,0,0]]; + assert(sortidx(lst3)==[0,3,2,4,1,6,5,9,7,10,8,11]); + assert(sortidx([[4,0],[7],[3,9],20,[4],[3,1],[8]]) == [3,5,2,4,0,1,6]); + assert(sortidx([[4,0],[7],[3,9],20,[4],[3,1],[8]],idx=1) == [1,3,4,6,0,5,2]); + lst4=[0,"1",[1,0],2,"a",[1]]; + assert(sortidx(lst4)== [0,3,1,4,5,2]); + assert(sortidx(["cat","oat","sat","bat","vat","rat","pat","mat","fat","hat","eat"]) + == [3,0,10,8,9,7,1,6,5,2,4]); + assert(sortidx([["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]])== [3,2,1,4,0]); + assert(sortidx(["Belfry", "OpenScad", "Library", "Documentation"])==[0,3,2,1]); + assert(sortidx(["x",1,[],0,"abc",true])==[5,3,1,4,0,2]); +} +test_sortidx(); + +module test_group_sort() { + assert_equal(group_sort([]), [[]]); + assert_equal(group_sort([8]), [[8]]); + assert_equal(group_sort([7,3,9,4,3,1,8]), [[1], [3, 3], [4], [7], [8], [9]]); + assert_equal(group_sort([[5,"a"],[2,"b"], [5,"c"], [3,"d"], [2,"e"] ], idx=0), [[[2, "b"], [2, "e"]], [[3, "d"]], [[5, "a"], [5, "c"]]]); + assert_equal(group_sort([["a",5],["b",6], ["c",1], ["d",2], ["e",6] ], idx=1), [[["c", 1]], [["d", 2]], [["a", 5]], [["b", 6], ["e", 6]]] ); +} +test_group_sort(); + + +module test_unique() { + assert_equal(unique([]), []); + assert_equal(unique([8]), [8]); + assert_equal(unique([7,3,9,4,3,1,8]), [1,3,4,7,8,9]); + assert_equal(unique(["A","B","R","A","C","A","D","A","B","R","A"]), ["A", "B", "C", "D", "R"]); +} +test_unique(); + + +module test_unique_count() { + assert_equal( + unique_count([3,1,4,1,5,9,2,6,5,3,5,8,9,7,9,3,2,3,6]), + [[1,2,3,4,5,6,7,8,9],[2,2,4,1,3,2,1,1,3]] + ); + assert_equal( + unique_count(["A","B","R","A","C","A","D","A","B","R","A"]), + [["A","B","C","D","R"],[5,2,1,1,2]] + ); +} +test_unique_count(); + + + +module test_is_increasing() { + assert(is_increasing([1,2,3,4]) == true); + assert(is_increasing([1,2,2,2]) == true); + assert(is_increasing([1,3,2,4]) == false); + assert(is_increasing([4,3,2,1]) == false); + assert(is_increasing([1,2,3,4],strict=true) == true); + assert(is_increasing([1,2,2,2],strict=true) == false); + assert(is_increasing([1,3,2,4],strict=true) == false); + assert(is_increasing([4,3,2,1],strict=true) == false); + assert(is_increasing(["AB","BC","DF"]) == true); + assert(is_increasing(["AB","DC","CF"]) == false); + assert(is_increasing([[1,2],[1,4],[2,3],[2,2]])==false); + assert(is_increasing([[1,2],[1,4],[2,3],[2,3]])==true); + assert(is_increasing([[1,2],[1,4],[2,3],[2,3]],strict=true)==false); + assert(is_increasing("ABCFZ")==true); + assert(is_increasing("ZYWRA")==false); +} +test_is_increasing(); + + +module test_is_decreasing() { + assert(is_decreasing([1,2,3,4]) == false); + assert(is_decreasing([4,2,3,1]) == false); + assert(is_decreasing([4,2,2,1]) == true); + assert(is_decreasing([4,3,2,1]) == true); + assert(is_decreasing([1,2,3,4],strict=true) == false); + assert(is_decreasing([4,2,3,1],strict=true) == false); + assert(is_decreasing([4,2,2,1],strict=true) == false); + assert(is_decreasing([4,3,2,1],strict=true) == true); + assert(is_decreasing(reverse(["AB","BC","DF"])) == true); + assert(is_decreasing(reverse(["AB","DC","CF"])) == false); + assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,2]]))==false); + assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,3]]))==true); + assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,3]]),strict=true)==false); + assert(is_decreasing("ABCFZ")==false); + assert(is_decreasing("ZYWRA")==true); +} +test_is_decreasing(); + + +module test_find_approx() { + assert(find_approx(1, [2,3,1.05,4,1,2,.99], eps=.1)==2); + assert(find_approx(1, [2,3,1.05,4,1,2,.99], all=true, eps=.1)==[2,4,6]); +} +test_find_approx(); + + + +module test_deduplicate() { + assert_equal(deduplicate([8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3,8]); + assert_equal(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3]); + assert_equal(deduplicate("Hello"), "Helo"); + assert_equal(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1), [[3,4],[7,2],[1,4]]); + assert_equal(deduplicate([], closed=true), []); + assert_equal(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]]), [[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]); +} +test_deduplicate(); + + +module test_deduplicate_indexed() { + assert(deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]) == [1,4,1,2,0,1]); + assert(deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true) == [1,4,1,2,0]); +} +test_deduplicate_indexed(); + +module test_all_zero() { + assert(all_zero(0)); + assert(all_zero([0,0,0])); + assert(all_zero([[0,0,0],[0,0]])); + assert(all_zero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(!all_zero(1e-3)); + assert(!all_zero([0,0,1e-3])); + assert(!all_zero([EPSILON*10,0,0])); + assert(!all_zero([0,EPSILON*10,0])); + assert(!all_zero([0,0,EPSILON*10])); + assert(!all_zero(true)); + assert(!all_zero(false)); + assert(!all_zero(INF)); + assert(!all_zero(-INF)); + assert(!all_zero(NAN)); + assert(!all_zero("foo")); + assert(!all_zero([])); + assert(!all_zero([0:1:2])); +} +test_all_zero(); + + +module test_all_nonzero() { + assert(!all_nonzero(0)); + assert(!all_nonzero([0,0,0])); + assert(!all_nonzero([[0,0,0],[0,0]])); + assert(!all_nonzero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(all_nonzero(1e-3)); + assert(!all_nonzero([0,0,1e-3])); + assert(!all_nonzero([EPSILON*10,0,0])); + assert(!all_nonzero([0,EPSILON*10,0])); + assert(!all_nonzero([0,0,EPSILON*10])); + assert(all_nonzero([1e-3,1e-3,1e-3])); + assert(all_nonzero([EPSILON*10,EPSILON*10,EPSILON*10])); + assert(!all_nonzero(true)); + assert(!all_nonzero(false)); + assert(!all_nonzero(INF)); + assert(!all_nonzero(-INF)); + assert(!all_nonzero(NAN)); + assert(!all_nonzero("foo")); + assert(!all_nonzero([])); + assert(!all_nonzero([0:1:2])); +} +test_all_nonzero(); + + +module test_all_positive() { + assert(!all_positive(-2)); + assert(!all_positive(0)); + assert(all_positive(2)); + assert(!all_positive([0,0,0])); + assert(!all_positive([0,1,2])); + assert(all_positive([3,1,2])); + assert(!all_positive([3,-1,2])); + assert(!all_positive([])); + assert(!all_positive(true)); + assert(!all_positive(false)); + assert(!all_positive("foo")); + assert(!all_positive([0:1:2])); +} +test_all_positive(); + + +module test_all_negative() { + assert(all_negative(-2)); + assert(!all_negative(0)); + assert(!all_negative(2)); + assert(!all_negative([0,0,0])); + assert(!all_negative([0,1,2])); + assert(!all_negative([3,1,2])); + assert(!all_negative([3,-1,2])); + assert(all_negative([-3,-1,-2])); + assert(!all_negative([-3,1,-2])); + assert(all_negative([[-5,-7],[-3,-1,-2]])); + assert(!all_negative([[-5,-7],[-3,1,-2]])); + assert(!all_negative([])); + assert(!all_negative(true)); + assert(!all_negative(false)); + assert(!all_negative("foo")); + assert(!all_negative([0:1:2])); +} +test_all_negative(); + + +module test_all_nonpositive() { + assert(all_nonpositive(-2)); + assert(all_nonpositive(0)); + assert(!all_nonpositive(2)); + assert(all_nonpositive([0,0,0])); + assert(!all_nonpositive([0,1,2])); + assert(all_nonpositive([0,-1,-2])); + assert(!all_nonpositive([3,1,2])); + assert(!all_nonpositive([3,-1,2])); + assert(!all_nonpositive([])); + assert(!all_nonpositive(true)); + assert(!all_nonpositive(false)); + assert(!all_nonpositive("foo")); + assert(!all_nonpositive([0:1:2])); +} +test_all_nonpositive(); + + +module test_all_nonnegative() { + assert(!all_nonnegative(-2)); + assert(all_nonnegative(0)); + assert(all_nonnegative(2)); + assert(all_nonnegative([0,0,0])); + assert(all_nonnegative([0,1,2])); + assert(all_nonnegative([3,1,2])); + assert(!all_nonnegative([3,-1,2])); + assert(!all_nonnegative([-3,-1,-2])); + assert(!all_nonnegative([[-5,-7],[-3,-1,-2]])); + assert(!all_nonnegative([[-5,-7],[-3,1,-2]])); + assert(!all_nonnegative([[5,7],[3,-1,2]])); + assert(all_nonnegative([[5,7],[3,1,2]])); + assert(!all_nonnegative([])); + assert(!all_nonnegative(true)); + assert(!all_nonnegative(false)); + assert(!all_nonnegative("foo")); + assert(!all_nonnegative([0:1:2])); +} +test_all_nonnegative(); + + +module test_approx() { + assert_equal(approx(PI, 3.141592653589793236), true); + assert_equal(approx(PI, 3.1415926), false); + assert_equal(approx(PI, 3.1415926, eps=1e-6), true); + assert_equal(approx(-PI, -3.141592653589793236), true); + assert_equal(approx(-PI, -3.1415926), false); + assert_equal(approx(-PI, -3.1415926, eps=1e-6), true); + assert_equal(approx(1/3, 0.3333333333), true); + assert_equal(approx(-1/3, -0.3333333333), true); + assert_equal(approx(10*[cos(30),sin(30)], 10*[sqrt(3)/2, 1/2]), true); + assert_equal(approx([1,[1,undef]], [1+1e-12,[1,true]]), false); + assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true); +} +test_approx(); diff --git a/tests/test_linalg.scad b/tests/test_linalg.scad new file mode 100644 index 0000000..bfde6d7 --- /dev/null +++ b/tests/test_linalg.scad @@ -0,0 +1,252 @@ +include <../std.scad> + +module test_qr_factor() { + // Check that R is upper triangular + function is_ut(R) = + let(bad = [for(i=[1:1:len(R)-1], j=[0:min(i-1, len(R[0])-1)]) if (!approx(R[i][j],0)) 1]) + bad == []; + + // Test the R is upper trianglar, Q is orthogonal and qr=M + function qrok(qr,M) = + is_ut(qr[1]) && approx(qr[0]*transpose(qr[0]), ident(len(qr[0]))) && approx(qr[0]*qr[1],M) && qr[2]==ident(len(qr[2])); + + // Test the R is upper trianglar, Q is orthogonal, R diagonal non-increasing and qrp=M + function qrokpiv(qr,M) = + is_ut(qr[1]) + && approx(qr[0]*transpose(qr[0]), ident(len(qr[0]))) + && approx(qr[0]*qr[1]*transpose(qr[2]),M) + && is_decreasing([for(i=[0:1:min(len(qr[1]),len(qr[1][0]))-1]) abs(qr[1][i][i])]); + + + M = [[1,2,9,4,5], + [6,7,8,19,10], + [11,12,13,14,15], + [1,17,18,19,20], + [21,22,10,24,25]]; + + assert(qrok(qr_factor(M),M)); + assert(qrok(qr_factor(select(M,0,3)),select(M,0,3))); + assert(qrok(qr_factor(transpose(select(M,0,3))),transpose(select(M,0,3)))); + + A = [[1,2,9,4,5], + [6,7,8,19,10], + [0,0,0,0,0], + [1,17,18,19,20], + [21,22,10,24,25]]; + assert(qrok(qr_factor(A),A)); + + B = [[1,2,0,4,5], + [6,7,0,19,10], + [0,0,0,0,0], + [1,17,0,19,20], + [21,22,0,24,25]]; + + assert(qrok(qr_factor(B),B)); + assert(qrok(qr_factor([[7]]), [[7]])); + assert(qrok(qr_factor([[1,2,3]]), [[1,2,3]])); + assert(qrok(qr_factor([[1],[2],[3]]), [[1],[2],[3]])); + + + assert(qrokpiv(qr_factor(M,pivot=true),M)); + assert(qrokpiv(qr_factor(select(M,0,3),pivot=true),select(M,0,3))); + assert(qrokpiv(qr_factor(transpose(select(M,0,3)),pivot=true),transpose(select(M,0,3)))); + assert(qrokpiv(qr_factor(B,pivot=true),B)); + assert(qrokpiv(qr_factor([[7]],pivot=true), [[7]])); + assert(qrokpiv(qr_factor([[1,2,3]],pivot=true), [[1,2,3]])); + assert(qrokpiv(qr_factor([[1],[2],[3]],pivot=true), [[1],[2],[3]])); +} +test_qr_factor(); + + +module test_matrix_inverse() { + assert_approx(matrix_inverse(rot([20,30,40])), [[0.663413948169,0.556670399226,-0.5,0],[-0.47302145844,0.829769465589,0.296198132726,0],[0.579769465589,0.0400087565481,0.813797681349,0],[0,0,0,1]]); +} +test_matrix_inverse(); + + +module test_det2() { + assert_equal(det2([[6,-2], [1,8]]), 50); + assert_equal(det2([[4,7], [3,2]]), -13); + assert_equal(det2([[4,3], [3,4]]), 7); +} +test_det2(); + + +module test_det3() { + M = [ [6,4,-2], [1,-2,8], [1,5,7] ]; + assert_equal(det3(M), -334); +} +test_det3(); + + +module test_determinant() { + M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ]; + assert_equal(determinant(M), 2267); +} +test_determinant(); + + +module test_matrix_trace() { + M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ]; + assert_equal(matrix_trace(M), 6-2+7+1); +} +test_matrix_trace(); + + + +module test_norm_fro(){ + assert_approx(norm_fro([[2,3,4],[4,5,6]]), 10.29563014098700); + +} test_norm_fro(); + + +module test_linear_solve(){ + M = [[-2,-5,-1,3], + [3,7,6,2], + [6,5,-1,-6], + [-7,1,2,3]]; + assert_approx(linear_solve(M, [-3,43,-11,13]), [1,2,3,4]); + assert_approx(linear_solve(M, [[-5,8],[18,-61],[4,7],[-1,-12]]), [[1,-2],[1,-3],[1,-4],[1,-5]]); + assert_approx(linear_solve([[2]],[4]), [2]); + assert_approx(linear_solve([[2]],[[4,8]]), [[2, 4]]); + assert_approx(linear_solve(select(M,0,2), [2,4,4]), [ 2.254871220604705e+00, + -8.378819388897780e-01, + 2.330507118860985e-01, + 8.511278195488737e-01]); + assert_approx(linear_solve(submatrix(M,idx(M),[0:2]), [2,4,4,4]), + [-2.457142857142859e-01, + 5.200000000000000e-01, + 7.428571428571396e-02]); + assert_approx(linear_solve([[1,2,3,4]], [2]), [0.066666666666666, 0.13333333333, 0.2, 0.266666666666]); + assert_approx(linear_solve([[1],[2],[3],[4]], [4,3,2,1]), [2/3]); + rd = [[-2,-5,-1,3], + [3,7,6,2], + [3,7,6,2], + [-7,1,2,3]]; + assert_equal(linear_solve(rd,[1,2,3,4]),[]); + assert_equal(linear_solve(select(rd,0,2), [2,4,4]), []); + assert_equal(linear_solve(transpose(select(rd,0,2)), [2,4,3,4]), []); +} +test_linear_solve(); + + + +module test_null_space(){ + assert_equal(null_space([[3,2,1],[3,6,3],[3,9,-3]]),[]); + + function nullcheck(A,dim) = + let(v=null_space(A)) + len(v)==dim && all_zero(A*transpose(v),eps=1e-12); + + A = [[-1, 2, -5, 2],[-3,-1,3,-3],[5,0,5,0],[3,-4,11,-4]]; + assert(nullcheck(A,1)); + + B = [ + [ 4, 1, 8, 6, -2, 3], + [ 10, 5, 10, 10, 0, 5], + [ 8, 1, 8, 8, -6, 1], + [ -8, -8, 6, -1, -8, -1], + [ 2, 2, 0, 1, 2, 1], + [ 2, -3, 10, 6, -8, 1], + ]; + assert(nullcheck(B,3)); +} +test_null_space(); + + +module test_column() { + v = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; + assert(column(v,2) == [3, 7, 11, 15]); + data = [[1,[3,4]], [3, [9,3]], [4, [3,1]]]; // Matrix with non-numeric entries + assert_equal(column(data,0), [1,3,4]); + assert_equal(column(data,1), [[3,4],[9,3],[3,1]]); +} +test_column(); + + +// Need decision about behavior for out of bounds ranges, empty ranges +module test_submatrix(){ + M = [[1,2,3,4,5], + [6,7,8,9,10], + [11,12,13,14,15], + [16,17,18,19,20], + [21,22,23,24,25]]; + assert_equal(submatrix(M,[1:2], [3:4]), [[9,10],[14,15]]); + assert_equal(submatrix(M,[1], [3,4]), [[9,10]]); + assert_equal(submatrix(M,1, [3,4]), [[9,10]]); + assert_equal(submatrix(M, [3,4],1), [[17],[22]]); + assert_equal(submatrix(M, [1,3],[2,4]), [[8,10],[18,20]]); + assert_equal(submatrix(M, 1,3), [[9]]); + A = [[true, 17, "test"], + [[4,2], 91, false], + [6, [3,4], undef]]; + assert_equal(submatrix(A,[0,2],[1,2]),[[17, "test"], [[3, 4], undef]]); +} +test_submatrix(); + + + +module test_hstack() { + M = ident(3); + v1 = [2,3,4]; + v2 = [5,6,7]; + v3 = [8,9,10]; + a = hstack(v1,v2); + b = hstack(v1,v2,v3); + c = hstack([M,v1,M]); + d = hstack(column(M,0), submatrix(M,idx(M),[1,2])); + assert_equal(a,[[2, 5], [3, 6], [4, 7]]); + assert_equal(b,[[2, 5, 8], [3, 6, 9], [4, 7, 10]]); + assert_equal(c,[[1, 0, 0, 2, 1, 0, 0], [0, 1, 0, 3, 0, 1, 0], [0, 0, 1, 4, 0, 0, 1]]); + assert_equal(d,M); + strmat = [["three","four"], ["five","six"]]; + assert_equal(hstack(strmat,strmat), [["three", "four", "three", "four"], ["five", "six", "five", "six"]]); + strvec = ["one","two"]; + assert_equal(hstack(strvec,strmat),[["o", "n", "e", "three", "four"], ["t", "w", "o", "five", "six"]]); +} +test_hstack(); + + +module test_block_matrix() { + A = [[1,2],[3,4]]; + B = ident(2); + assert_equal(block_matrix([[A,B],[B,A],[A,B]]), [[1,2,1,0],[3,4,0,1],[1,0,1,2],[0,1,3,4],[1,2,1,0],[3,4,0,1]]); + assert_equal(block_matrix([[A,B],ident(4)]), [[1,2,1,0],[3,4,0,1],[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); + text = [["aa","bb"],["cc","dd"]]; + assert_equal(block_matrix([[text,B]]), [["aa","bb",1,0],["cc","dd",0,1]]); +} +test_block_matrix(); + + +module test_diagonal_matrix() { + assert_equal(diagonal_matrix([1,2,3]), [[1,0,0],[0,2,0],[0,0,3]]); + assert_equal(diagonal_matrix([1,"c",2]), [[1,0,0],[0,"c",0],[0,0,2]]); + assert_equal(diagonal_matrix([1,"c",2],"X"), [[1,"X","X"],["X","c","X"],["X","X",2]]); + assert_equal(diagonal_matrix([[1,1],[2,2],[3,3]], [0,0]), [[ [1,1],[0,0],[0,0]], [[0,0],[2,2],[0,0]], [[0,0],[0,0],[3,3]]]); +} +test_diagonal_matrix(); + +module test_submatrix_set() { + test = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]; + ragged = [[1,2,3,4,5],[6,7,8,9,10],[11,12], [16,17]]; + assert_equal(submatrix_set(test,[[9,8],[7,6]]), [[9,8,3,4,5],[7,6,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,7],[8,6]],1),[[1,2,3,4,5],[9,7,8,9,10],[8,6,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],n=1), [[1,9,8,4,5],[6,7,6,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],1,2), [[1,2,3,4,5],[6,7,9,8,10],[11,12,7,6,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],-1,-1), [[6,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],n=4), [[1,2,3,4,9],[6,7,8,9,7],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],7,7), [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(ragged, [["a","b"],["c","d"]], 1, 1), [[1,2,3,4,5],[6,"a","b",9,10],[11,"c"], [16,17]]); + assert_equal(submatrix_set(test, [[]]), test); +} +test_submatrix_set(); + +module test_transpose() { + assert(transpose([[1,2,3],[4,5,6],[7,8,9]]) == [[1,4,7],[2,5,8],[3,6,9]]); + assert(transpose([[1,2,3],[4,5,6]]) == [[1,4],[2,5],[3,6]]); + assert(transpose([[1,2,3],[4,5,6]],reverse=true) == [[6,3], [5,2], [4,1]]); + assert(transpose([3,4,5]) == [3,4,5]); +} +test_transpose(); + + diff --git a/tests/test_arrays.scad b/tests/test_lists.scad similarity index 65% rename from tests/test_arrays.scad rename to tests/test_lists.scad index 20cdc73..bbfc541 100644 --- a/tests/test_arrays.scad +++ b/tests/test_lists.scad @@ -91,52 +91,6 @@ module test_in_list() { test_in_list(); -module test_is_increasing() { - assert(is_increasing([1,2,3,4]) == true); - assert(is_increasing([1,2,2,2]) == true); - assert(is_increasing([1,3,2,4]) == false); - assert(is_increasing([4,3,2,1]) == false); - assert(is_increasing([1,2,3,4],strict=true) == true); - assert(is_increasing([1,2,2,2],strict=true) == false); - assert(is_increasing([1,3,2,4],strict=true) == false); - assert(is_increasing([4,3,2,1],strict=true) == false); - assert(is_increasing(["AB","BC","DF"]) == true); - assert(is_increasing(["AB","DC","CF"]) == false); - assert(is_increasing([[1,2],[1,4],[2,3],[2,2]])==false); - assert(is_increasing([[1,2],[1,4],[2,3],[2,3]])==true); - assert(is_increasing([[1,2],[1,4],[2,3],[2,3]],strict=true)==false); - assert(is_increasing("ABCFZ")==true); - assert(is_increasing("ZYWRA")==false); -} -test_is_increasing(); - - -module test_is_decreasing() { - assert(is_decreasing([1,2,3,4]) == false); - assert(is_decreasing([4,2,3,1]) == false); - assert(is_decreasing([4,2,2,1]) == true); - assert(is_decreasing([4,3,2,1]) == true); - assert(is_decreasing([1,2,3,4],strict=true) == false); - assert(is_decreasing([4,2,3,1],strict=true) == false); - assert(is_decreasing([4,2,2,1],strict=true) == false); - assert(is_decreasing([4,3,2,1],strict=true) == true); - assert(is_decreasing(reverse(["AB","BC","DF"])) == true); - assert(is_decreasing(reverse(["AB","DC","CF"])) == false); - assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,2]]))==false); - assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,3]]))==true); - assert(is_decreasing(reverse([[1,2],[1,4],[2,3],[2,3]]),strict=true)==false); - assert(is_decreasing("ABCFZ")==false); - assert(is_decreasing("ZYWRA")==true); -} -test_is_decreasing(); - - -module test_find_approx() { - assert(find_approx(1, [2,3,1.05,4,1,2,.99], eps=.1)==2); - assert(find_approx(1, [2,3,1.05,4,1,2,.99], all=true, eps=.1)==[2,4,6]); -} -test_find_approx(); - // Section: Basic List Generation @@ -183,23 +137,6 @@ module test_list_rotate() { test_list_rotate(); -module test_deduplicate() { - assert_equal(deduplicate([8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3,8]); - assert_equal(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3]); - assert_equal(deduplicate("Hello"), "Helo"); - assert_equal(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1), [[3,4],[7,2],[1,4]]); - assert_equal(deduplicate([], closed=true), []); - assert_equal(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]]), [[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]); -} -test_deduplicate(); - - -module test_deduplicate_indexed() { - assert(deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]) == [1,4,1,2,0,1]); - assert(deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true) == [1,4,1,2,0]); -} -test_deduplicate_indexed(); - module test_list_set() { assert_equal(list_set([2,3,4,5], 2, 21), [2,3,21,5]); @@ -332,82 +269,6 @@ module test_shuffle() { test_shuffle(); -module test_sort() { - assert(sort([7,3,9,4,3,1,8]) == [1,3,3,4,7,8,9]); - assert(sort([[4,0],[7],[3,9],20,[4],[3,1],[8]]) == [20,[3,1],[3,9],[4],[4,0],[7],[8]]); - assert(sort([[4,0],[7],[3,9],20,[4],[3,1],[8]],idx=1) == [[7],20,[4],[8],[4,0],[3,1],[3,9]]); - assert(sort([[8,6],[3,1],[9,2],[4,3],[3,4],[1,5],[8,0]]) == [[1,5],[3,1],[3,4],[4,3],[8,0],[8,6],[9,2]]); - assert(sort([[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]],idx=1) == [[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]]); - assert(sort(["cat", "oat", "sat", "bat", "vat", "rat", "pat", "mat", "fat", "hat", "eat"]) - == ["bat", "cat", "eat", "fat", "hat", "mat", "oat", "pat", "rat", "sat", "vat"]); - assert(sort(enumerate([[2,3,4],[1,2,3],[2,4,3]]),idx=1)==[[1,[1,2,3]], [0,[2,3,4]], [2,[2,4,3]]]); - assert(sort([0,"1",[1,0],2,"a",[1]])== [0,2,"1","a",[1],[1,0]]); - assert(sort([["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]])== [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]]); -} -test_sort(); - - -module test_sortidx() { - lst1 = ["da","bax","eaw","cav"]; - assert(sortidx(lst1) == [1,3,0,2]); - lst5 = [3,5,1,7]; - assert(sortidx(lst5) == [2,0,1,3]); - lst2 = [ - ["foo", 88, [0,0,1], false], - ["bar", 90, [0,1,0], true], - ["baz", 89, [1,0,0], false], - ["qux", 23, [1,1,1], true] - ]; - assert(sortidx(lst2, idx=1) == [3,0,2,1]); - assert(sortidx(lst2, idx=0) == [1,2,0,3]); - assert(sortidx(lst2, idx=[1,3]) == [3,0,2,1]); - lst3 = [[-4,0,0],[0,0,-4],[0,-4,0],[-4,0,0],[0,-4,0],[0,0,4], - [0,0,-4],[0,4,0],[4,0,0],[0,0,4],[0,4,0],[4,0,0]]; - assert(sortidx(lst3)==[0,3,2,4,1,6,5,9,7,10,8,11]); - assert(sortidx([[4,0],[7],[3,9],20,[4],[3,1],[8]]) == [3,5,2,4,0,1,6]); - assert(sortidx([[4,0],[7],[3,9],20,[4],[3,1],[8]],idx=1) == [1,3,4,6,0,5,2]); - lst4=[0,"1",[1,0],2,"a",[1]]; - assert(sortidx(lst4)== [0,3,1,4,5,2]); - assert(sortidx(["cat","oat","sat","bat","vat","rat","pat","mat","fat","hat","eat"]) - == [3,0,10,8,9,7,1,6,5,2,4]); - assert(sortidx([["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]])== [3,2,1,4,0]); - assert(sortidx(["Belfry", "OpenScad", "Library", "Documentation"])==[0,3,2,1]); - assert(sortidx(["x",1,[],0,"abc",true])==[5,3,1,4,0,2]); -} -test_sortidx(); - -module test_group_sort() { - assert_equal(group_sort([]), [[]]); - assert_equal(group_sort([8]), [[8]]); - assert_equal(group_sort([7,3,9,4,3,1,8]), [[1], [3, 3], [4], [7], [8], [9]]); - assert_equal(group_sort([[5,"a"],[2,"b"], [5,"c"], [3,"d"], [2,"e"] ], idx=0), [[[2, "b"], [2, "e"]], [[3, "d"]], [[5, "a"], [5, "c"]]]); - assert_equal(group_sort([["a",5],["b",6], ["c",1], ["d",2], ["e",6] ], idx=1), [[["c", 1]], [["d", 2]], [["a", 5]], [["b", 6], ["e", 6]]] ); -} -test_group_sort(); - - -module test_unique() { - assert_equal(unique([]), []); - assert_equal(unique([8]), [8]); - assert_equal(unique([7,3,9,4,3,1,8]), [1,3,4,7,8,9]); - assert_equal(unique(["A","B","R","A","C","A","D","A","B","R","A"]), ["A", "B", "C", "D", "R"]); -} -test_unique(); - - -module test_unique_count() { - assert_equal( - unique_count([3,1,4,1,5,9,2,6,5,3,5,8,9,7,9,3,2,3,6]), - [[1,2,3,4,5,6,7,8,9],[2,2,4,1,3,2,1,1,3]] - ); - assert_equal( - unique_count(["A","B","R","A","C","A","D","A","B","R","A"]), - [["A","B","C","D","R"],[5,2,1,1,2]] - ); -} -test_unique_count(); - - // Sets @@ -509,13 +370,13 @@ module test_zip() { test_zip(); -module test_array_group() { +module test_list_to_matrix() { v = [1,2,3,4,5,6]; - assert(array_group(v,2) == [[1,2], [3,4], [5,6]]); - assert(array_group(v,3) == [[1,2,3], [4,5,6]]); - assert(array_group(v,4,0) == [[1,2,3,4], [5,6,0,0]]); + assert(list_to_matrix(v,2) == [[1,2], [3,4], [5,6]]); + assert(list_to_matrix(v,3) == [[1,2,3], [4,5,6]]); + assert(list_to_matrix(v,4,0) == [[1,2,3,4], [5,6,0,0]]); } -test_array_group(); +test_list_to_matrix(); module test_group_data() { diff --git a/tests/test_math.scad b/tests/test_math.scad index c5820f5..6ad4129 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -109,128 +109,6 @@ module test_is_matrix() { test_is_matrix(); -module test_all_zero() { - assert(all_zero(0)); - assert(all_zero([0,0,0])); - assert(all_zero([[0,0,0],[0,0]])); - assert(all_zero([EPSILON/2,EPSILON/2,EPSILON/2])); - assert(!all_zero(1e-3)); - assert(!all_zero([0,0,1e-3])); - assert(!all_zero([EPSILON*10,0,0])); - assert(!all_zero([0,EPSILON*10,0])); - assert(!all_zero([0,0,EPSILON*10])); - assert(!all_zero(true)); - assert(!all_zero(false)); - assert(!all_zero(INF)); - assert(!all_zero(-INF)); - assert(!all_zero(NAN)); - assert(!all_zero("foo")); - assert(!all_zero([])); - assert(!all_zero([0:1:2])); -} -test_all_zero(); - - -module test_all_nonzero() { - assert(!all_nonzero(0)); - assert(!all_nonzero([0,0,0])); - assert(!all_nonzero([[0,0,0],[0,0]])); - assert(!all_nonzero([EPSILON/2,EPSILON/2,EPSILON/2])); - assert(all_nonzero(1e-3)); - assert(!all_nonzero([0,0,1e-3])); - assert(!all_nonzero([EPSILON*10,0,0])); - assert(!all_nonzero([0,EPSILON*10,0])); - assert(!all_nonzero([0,0,EPSILON*10])); - assert(all_nonzero([1e-3,1e-3,1e-3])); - assert(all_nonzero([EPSILON*10,EPSILON*10,EPSILON*10])); - assert(!all_nonzero(true)); - assert(!all_nonzero(false)); - assert(!all_nonzero(INF)); - assert(!all_nonzero(-INF)); - assert(!all_nonzero(NAN)); - assert(!all_nonzero("foo")); - assert(!all_nonzero([])); - assert(!all_nonzero([0:1:2])); -} -test_all_nonzero(); - - -module test_all_positive() { - assert(!all_positive(-2)); - assert(!all_positive(0)); - assert(all_positive(2)); - assert(!all_positive([0,0,0])); - assert(!all_positive([0,1,2])); - assert(all_positive([3,1,2])); - assert(!all_positive([3,-1,2])); - assert(!all_positive([])); - assert(!all_positive(true)); - assert(!all_positive(false)); - assert(!all_positive("foo")); - assert(!all_positive([0:1:2])); -} -test_all_positive(); - - -module test_all_negative() { - assert(all_negative(-2)); - assert(!all_negative(0)); - assert(!all_negative(2)); - assert(!all_negative([0,0,0])); - assert(!all_negative([0,1,2])); - assert(!all_negative([3,1,2])); - assert(!all_negative([3,-1,2])); - assert(all_negative([-3,-1,-2])); - assert(!all_negative([-3,1,-2])); - assert(all_negative([[-5,-7],[-3,-1,-2]])); - assert(!all_negative([[-5,-7],[-3,1,-2]])); - assert(!all_negative([])); - assert(!all_negative(true)); - assert(!all_negative(false)); - assert(!all_negative("foo")); - assert(!all_negative([0:1:2])); -} -test_all_negative(); - - -module test_all_nonpositive() { - assert(all_nonpositive(-2)); - assert(all_nonpositive(0)); - assert(!all_nonpositive(2)); - assert(all_nonpositive([0,0,0])); - assert(!all_nonpositive([0,1,2])); - assert(all_nonpositive([0,-1,-2])); - assert(!all_nonpositive([3,1,2])); - assert(!all_nonpositive([3,-1,2])); - assert(!all_nonpositive([])); - assert(!all_nonpositive(true)); - assert(!all_nonpositive(false)); - assert(!all_nonpositive("foo")); - assert(!all_nonpositive([0:1:2])); -} -test_all_nonpositive(); - - -module test_all_nonnegative() { - assert(!all_nonnegative(-2)); - assert(all_nonnegative(0)); - assert(all_nonnegative(2)); - assert(all_nonnegative([0,0,0])); - assert(all_nonnegative([0,1,2])); - assert(all_nonnegative([3,1,2])); - assert(!all_nonnegative([3,-1,2])); - assert(!all_nonnegative([-3,-1,-2])); - assert(!all_nonnegative([[-5,-7],[-3,-1,-2]])); - assert(!all_nonnegative([[-5,-7],[-3,1,-2]])); - assert(!all_nonnegative([[5,7],[3,-1,2]])); - assert(all_nonnegative([[5,7],[3,1,2]])); - assert(!all_nonnegative([])); - assert(!all_nonnegative(true)); - assert(!all_nonnegative(false)); - assert(!all_nonnegative("foo")); - assert(!all_nonnegative([0:1:2])); -} -test_all_nonnegative(); module test_all_integer() { @@ -253,20 +131,6 @@ module test_all_integer() { test_all_integer(); -module test_approx() { - assert_equal(approx(PI, 3.141592653589793236), true); - assert_equal(approx(PI, 3.1415926), false); - assert_equal(approx(PI, 3.1415926, eps=1e-6), true); - assert_equal(approx(-PI, -3.141592653589793236), true); - assert_equal(approx(-PI, -3.1415926), false); - assert_equal(approx(-PI, -3.1415926, eps=1e-6), true); - assert_equal(approx(1/3, 0.3333333333), true); - assert_equal(approx(-1/3, -0.3333333333), true); - assert_equal(approx(10*[cos(30),sin(30)], 10*[sqrt(3)/2, 1/2]), true); - assert_equal(approx([1,[1,undef]], [1+1e-12,[1,true]]), false); - assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true); -} -test_approx(); module test_min_index() {