mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-28 15:29:37 +00:00
move comparison functions out of math.scad and arrays.scad into comparisons.scad
rename arrays.scad to lists.scad
This commit is contained in:
parent
8cfb77cf64
commit
71dab62432
8 changed files with 1492 additions and 1216 deletions
782
comparisons.scad
Normal file
782
comparisons.scad
Normal file
|
@ -0,0 +1,782 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: comparisons.scad
|
||||
// Functions for comparisons with lists, ordering and sorting
|
||||
// Includes:
|
||||
// include <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// 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.x<p.y) true])==0;
|
||||
|
||||
|
||||
|
||||
|
||||
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 a<b. Returns >0 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 :
|
||||
(a<b)? -1 : (a>b)? 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<v) li ],
|
||||
equal = [for(li=list) if(li==v) li ]
|
||||
)
|
||||
len(smaller) == k ? smaller :
|
||||
len(smaller)<k && len(smaller)+len(equal) >= 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<len(list),"Bad index in indices.")
|
||||
list[idx1],
|
||||
b = assert(idx2>=0,"Bad index.")
|
||||
assert(idx2<len(list),"Bad index in indices.")
|
||||
list[idx2],
|
||||
eq = (a == b)? true :
|
||||
(a*0 != b*0) || (eps==0)? false :
|
||||
is_num(a) || is_vector(a) ? approx(a, b, eps=eps)
|
||||
: false
|
||||
)
|
||||
if (i==end || !eq) indices[i]
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
// Function: unique()
|
||||
// Usage:
|
||||
// ulist = unique(list);
|
||||
// Topics: List Handling
|
||||
// See Also: shuffle(), sort(), sortidx(), unique_count()
|
||||
// Description:
|
||||
// Given a string or a list returns the sorted string or the sorted list with all repeated items removed.
|
||||
// The sorting order of non homogeneous lists is the function `sort` order.
|
||||
// Arguments:
|
||||
// list = The list to uniquify.
|
||||
// Example:
|
||||
// sorted = unique([5,2,8,3,1,3,8,7,5]); // Returns: [1,2,3,5,7,8]
|
||||
// sorted = unique("axdbxxc"); // Returns: "abcdx"
|
||||
// sorted = unique([true,2,"xba",[1,0],true,[0,0],3,"a",[0,0],2]); // Returns: [true,2,3,"a","xba",[0,0],[1,0]]
|
||||
function unique(list) =
|
||||
assert(is_list(list)||is_string(list), "Invalid input." )
|
||||
is_string(list)? str_join(unique([for (x = list) x])) :
|
||||
len(list)<=1? list :
|
||||
is_homogeneous(list,1) && ! is_list(list[0])
|
||||
? _unique_sort(list)
|
||||
: let( sorted = sort(list))
|
||||
[
|
||||
for (i=[0:1:len(sorted)-1])
|
||||
if (i==0 || (sorted[i] != sorted[i-1]))
|
||||
sorted[i]
|
||||
];
|
||||
|
||||
function _unique_sort(l) =
|
||||
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(
|
||||
_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)<idx || !is_num(entry[idx]) ) 1]==[],
|
||||
"Some entry of the list is a list shorter than `idx` or the indexed entry of it is not a number.")
|
||||
_group_sort_by_index(list,idx);
|
||||
|
||||
|
||||
|
||||
// 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]]
|
||||
]
|
||||
];
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: arrays.scad
|
||||
// List and Array manipulation functions.
|
||||
// LibFile: lists.scad
|
||||
// Functions for constructing and manipulating generic lists.
|
||||
// Includes:
|
||||
// include <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -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<len(list),"Bad index in indices.")
|
||||
list[idx1],
|
||||
b = assert(idx2>=0,"Bad index.")
|
||||
assert(idx2<len(list),"Bad index in indices.")
|
||||
list[idx2],
|
||||
eq = (a == b)? true :
|
||||
(a*0 != b*0) || (eps==0)? false :
|
||||
is_num(a) || is_vector(a) ? approx(a, b, eps=eps)
|
||||
: false
|
||||
)
|
||||
if (i==end || !eq) indices[i]
|
||||
];
|
||||
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
// 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.x<p.y) true])==0;
|
||||
|
||||
|
||||
// Function: unique()
|
||||
// Usage:
|
||||
// ulist = unique(list);
|
||||
// Topics: List Handling
|
||||
// See Also: shuffle(), sort(), sortidx(), unique_count()
|
||||
// Description:
|
||||
// Given a string or a list returns the sorted string or the sorted list with all repeated items removed.
|
||||
// The sorting order of non homogeneous lists is the function `sort` order.
|
||||
// Arguments:
|
||||
// list = The list to uniquify.
|
||||
// Example:
|
||||
// sorted = unique([5,2,8,3,1,3,8,7,5]); // Returns: [1,2,3,5,7,8]
|
||||
// sorted = unique("axdbxxc"); // Returns: "abcdx"
|
||||
// sorted = unique([true,2,"xba",[1,0],true,[0,0],3,"a",[0,0],2]); // Returns: [true,2,3,"a","xba",[0,0],[1,0]]
|
||||
function unique(list) =
|
||||
assert(is_list(list)||is_string(list), "Invalid input." )
|
||||
is_string(list)? str_join(unique([for (x = list) x])) :
|
||||
len(list)<=1? list :
|
||||
is_homogeneous(list,1) && ! is_list(list[0])
|
||||
? _unique_sort(list)
|
||||
: let( sorted = sort(list))
|
||||
[
|
||||
for (i=[0:1:len(sorted)-1])
|
||||
if (i==0 || (sorted[i] != sorted[i-1]))
|
||||
sorted[i]
|
||||
];
|
||||
|
||||
function _unique_sort(l) =
|
||||
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(
|
||||
_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)<idx || !is_num(entry[idx]) ) 1]==[],
|
||||
"Some entry of the list is a list shorter than `idx` or the indexed entry of it is not a number.")
|
||||
_group_sort_by_index(list,idx);
|
||||
|
||||
|
||||
|
||||
// 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<v) li ],
|
||||
equal = [for(li=list) if(li==v) li ]
|
||||
)
|
||||
len(smaller) == k ? smaller :
|
||||
len(smaller)<k && len(smaller)+len(equal) >= 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<len(x)? x[i] : fill]];
|
||||
|
||||
|
||||
|
||||
// Section: Set Manipulation
|
||||
|
||||
// Function: set_union()
|
||||
|
@ -1558,170 +1182,5 @@ function set_intersection(a, b) =
|
|||
|
||||
|
||||
|
||||
// Section: Changing list structure
|
||||
|
||||
|
||||
// Function: array_group()
|
||||
// Usage:
|
||||
// groups = array_group(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 = array_group(v,2) returns [[1,2], [3,4], [5,6]]
|
||||
// b = array_group(v,3) returns [[1,2,3], [4,5,6]]
|
||||
// c = array_group(v,4,0) returns [[1,2,3,4], [5,6,0,0]]
|
||||
function array_group(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];
|
||||
|
||||
|
||||
// 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: 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<len(x)? x[i] : fill]];
|
||||
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
223
math.scad
223
math.scad
|
@ -866,158 +866,6 @@ function convolve(p,q) =
|
|||
|
||||
|
||||
|
||||
// Section: Comparisons and Logic
|
||||
|
||||
// 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: 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 a<b. Returns >0 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 :
|
||||
(a<b)? -1 : (a>b)? 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()
|
||||
|
|
3
std.scad
3
std.scad
|
@ -20,7 +20,8 @@ include <drawing.scad>
|
|||
include <masks.scad>
|
||||
include <paths.scad>
|
||||
include <edges.scad>
|
||||
include <arrays.scad>
|
||||
include <lists.scad>
|
||||
include <comparisons.scad>
|
||||
include <math.scad>
|
||||
include <linalg.scad>
|
||||
include <trigonometry.scad>
|
||||
|
|
280
tests/test_comparisons.scad
Normal file
280
tests/test_comparisons.scad
Normal file
|
@ -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();
|
252
tests/test_linalg.scad
Normal file
252
tests/test_linalg.scad
Normal file
|
@ -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();
|
||||
|
||||
|
|
@ -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() {
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue