////////////////////////////////////////////////////////////////////// // LibFile: arrays.scad // List and Array manipulation functions. // Includes: // include ////////////////////////////////////////////////////////////////////// // Terminology: // **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. // Section: List Query Operations // Function: is_homogeneous() // Alias: is_homogenous() // Usage: // bool = is_homogeneous(list, depth); // Topics: List Handling, Type Checking // See Also: is_vector(), is_matrix() // Description: // Returns true when the list has elements of same type up to the depth `depth`. // Booleans and numbers are not distinguinshed as of distinct types. // Arguments: // l = the list to check // depth = the lowest level the check is done // Example: // a = is_homogeneous([[1,["a"]], [2,["b"]]]); // Returns true // b = is_homogeneous([[1,["a"]], [2,[true]]]); // Returns false // c = is_homogeneous([[1,["a"]], [2,[true]]], 1); // Returns true // d = is_homogeneous([[1,["a"]], [2,[true]]], 2); // Returns false // e = is_homogeneous([[1,["a"]], [true,["b"]]]); // Returns true function is_homogeneous(l, depth=10) = !is_list(l) || l==[] ? false : let( l0=l[0] ) [] == [for(i=[1:1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ]; function is_homogenous(l, depth=10) = is_homogeneous(l, depth); function _same_type(a,b, depth) = (depth==0) || (is_undef(a) && is_undef(b)) || (is_bool(a) && is_bool(b)) || (is_num(a) && is_num(b)) || (is_string(a) && is_string(b)) || (is_list(a) && is_list(b) && len(a)==len(b) && []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) ) 0] ); // Function: min_length() // Usage: // llen = min_length(array); // Topics: List Handling // See Also: max_length() // Description: // Returns the length of the shortest sublist in a list of lists. // Arguments: // array = A list of lists. // Example: // slen = min_length([[3,4,5],[6,7,8,9]]); // Returns: 3 function min_length(array) = assert(is_list(array), "Invalid input." ) min([for (v = array) len(v)]); // Function: max_length() // Usage: // llen = max_length(array); // Topics: List Handling // See Also: min_length() // Description: // Returns the length of the longest sublist in a list of lists. // Arguments: // array = A list of lists. // Example: // llen = max_length([[3,4,5],[6,7,8,9]]); // Returns: 4 function max_length(array) = assert(is_list(array), "Invalid input." ) max([for (v = array) len(v)]); // Function: in_list() // Usage: // bool = in_list(val, list, [idx]); // Topics: List Handling // Description: // Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list. // Arguments: // val = The simple value to search for. // list = The list to search. // idx = If given, searches the given columns for matches for `val`. // Example: // a = in_list("bar", ["foo", "bar", "baz"]); // Returns true. // b = in_list("bee", ["foo", "bar", "baz"]); // Returns false. // c = in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. function in_list(val,list,idx) = assert( is_list(list) && (is_undef(idx) || is_finite(idx)), "Invalid input." ) let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] ) s==[] || s==[[]] ? false : is_undef(idx) ? val==list[s] : val==list[s][idx]; // Function: add_scalar() // Usage: // v = add_scalar(v, s); // Topics: List Handling // Description: // Given a list and a scalar, returns the list with the scalar added to each item in it. // If given a list of arrays, recursively adds the scalar to the each array. // Arguments: // v = The initial array. // s = A scalar value to add to every item in the array. // Example: // a = add_scalar([1,2,3],3); // Returns: [4,5,6] // b = add_scalar([[1,2,3],[3,4,5]],3); // Returns: [[4,5,6],[6,7,8]] function add_scalar(v,s) = is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v; // Section: Operations using approx() // Function: deduplicate() // Usage: // list = deduplicate(list, [close], [eps]); // Topics: List Handling // See Also: deduplicate_indexed() // Description: // Removes consecutive duplicate items in a list. // When `eps` is zero, the comparison between consecutive items is exact. // Otherwise, when all list items and subitems are numbers, the comparison is within the tolerance `eps`. // This is different from `unique()` in that the list is *not* sorted. // Arguments: // list = The list to deduplicate. // closed = If true, drops trailing items if they match the first list item. // eps = The maximum tolerance between items. // Example: // a = deduplicate([8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3,8] // b = deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3] // c = deduplicate("Hello"); // Returns: "Helo" // d = deduplicate([[3,4],[7,2],[7,1.99],[1,4]],eps=0.1); // Returns: [[3,4],[7,2],[1,4]] // e = deduplicate([[7,undef],[7,undef],[1,4],[1,4+1e-12]],eps=0); // Returns: [[7,undef],[1,4],[1,4+1e-12]] function deduplicate(list, closed=false, eps=EPSILON) = assert(is_list(list)||is_string(list)) let( l = len(list), end = l-(closed?0:1) ) is_string(list) ? str_join([for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]) : eps==0 ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]] : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]]; // Function: deduplicate_indexed() // Usage: // new_idxs = deduplicate_indexed(list, indices, [closed], [eps]); // Topics: List Handling // See Also: deduplicate() // Description: // Given a list, and indices into it, removes consecutive indices that // index to the same values in the list. // Arguments: // list = The list that the indices index into. // indices = The list of indices to deduplicate. // closed = If true, drops trailing indices if what they index matches what the first index indexes. // eps = The maximum difference to allow between numbers or vectors. // Example: // a = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]); // Returns: [1,4,3,2,0,1] // b = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true); // Returns: [1,4,3,2,0] // c = deduplicate_indexed([[7,undef],[7,undef],[1,4],[1,4],[1,4+1e-12]],eps=0); // Returns: [0,2,4] function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) = assert(is_list(list)||is_string(list), "Improper list or string.") indices==[]? [] : assert(is_vector(indices), "Indices must be a list of numbers.") let( ll = len(list), l = len(indices), end = l-(closed?0:1) ) [ for (i = [0:1:l-1]) let( idx1 = indices[i], idx2 = indices[(i+1)%l], a = assert(idx1>=0,"Bad index.") assert(idx1=0,"Bad index.") assert(idx2= len(list)? undef : approx(val, list[i], eps=eps) ? i : __find_approx(val, list, eps=eps, i=i+1); // Section: List Indexing // Function: select() // Topics: List Handling // Description: // Returns a portion of a list, wrapping around past the beginning, if end=s) for (i=[s:1:e]) list[i]]; // Function: last() // Usage: // item = last(list); // Topics: List Handling // See Also: select(), slice(), columns() // Description: // Returns the last element of a list, or undef if empty. // Arguments: // list = The list to get the last element of. // Example: // l = [3,4,5,6,7,8,9]; // x = last(l); // Returns 9. function last(list) = list[len(list)-1]; // Function: list_head() // Usage: // list = list_head(list, [to]); // Topics: List Handling // See Also: select(), slice(), list_tail(), last() // Description: // Returns the head of the given list, from the first item up until the `to` index, inclusive. // If the `to` index is negative, then the length of the list is added to it, such that // `-1` is the last list item. `-2` is the second from last. `-3` is third from last, etc. // If the list is shorter than the given index, then the full list is returned. // Arguments: // list = The list to get the head of. // to = The last index to include. If negative, adds the list length to it. ie: -1 is the last list item. // Example: // hlist1 = list_head(["foo", "bar", "baz"]); // Returns: ["foo", "bar"] // hlist2 = list_head(["foo", "bar", "baz"], -3); // Returns: ["foo"] // hlist3 = list_head(["foo", "bar", "baz"], 2); // Returns: ["foo","bar"] // hlist4 = list_head(["foo", "bar", "baz"], -5); // Returns: [] // hlist5 = list_head(["foo", "bar", "baz"], 5); // Returns: ["foo","bar","baz"] function list_head(list, to=-2) = assert(is_list(list)) assert(is_finite(to)) to<0? [for (i=[0:1:len(list)+to]) list[i]] : to=0? [for (i=[from:1:len(list)-1]) list[i]] : let(from = from + len(list)) from>=0? [for (i=[from:1:len(list)-1]) list[i]] : list; // Function: bselect() // Usage: // array = bselect(array, index); // Topics: List Handling // See Also: list_bset() // Description: // Returns the items in `array` whose matching element in `index` is true. // Arguments: // array = Initial list to extract items from. // index = List of booleans. // Example: // a = bselect([3,4,5,6,7], [false,true,true,false,true]); // Returns: [4,5,7] function bselect(array,index) = assert(is_list(array)||is_string(array), "Improper array." ) assert(is_list(index) && len(index)>=len(array) , "Improper index list." ) is_string(array)? str_join(bselect( [for (x=array) x], index)) : [for(i=[0:len(array)-1]) if (index[i]) array[i]]; // Section: List Construction // Function: repeat() // Usage: // list = repeat(val, n); // Topics: List Handling // See Also: count(), lerpn() // Description: // Generates a list or array of `n` copies of the given value `val`. // If the count `n` is given as a list of counts, then this creates a // multi-dimensional array, filled with `val`. // Arguments: // val = The value to repeat to make the list or array. // n = The number of copies to make of `val`. // Example: // a = repeat(1, 4); // Returns [1,1,1,1] // b = repeat(8, [2,3]); // Returns [[8,8,8], [8,8,8]] // c = repeat(0, [2,2,3]); // Returns [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]] // d = repeat([1,2,3],3); // Returns [[1,2,3], [1,2,3], [1,2,3]] function repeat(val, n, i=0) = is_num(n)? [for(j=[1:1:n]) val] : assert( is_list(n), "Invalid count number.") (i>=len(n))? val : [for (j=[1:1:n[i]]) repeat(val, n, i+1)]; // Function: count() // Usage: // list = count(n, [s], [step], [reverse]); // Description: // Creates a list of `n` numbers, starting at `s`, incrementing by `step` each time. // You can also pass a list for n and then the length of the input list is used. // Arguments: // n = The length of the list of numbers to create, or a list to match the length of // s = The starting value of the list of numbers. // step = The amount to increment successive numbers in the list. // reverse = Reverse the list. Default: false. // Example: // nl1 = count(5); // Returns: [0,1,2,3,4] // nl2 = count(5,3); // Returns: [3,4,5,6,7] // nl3 = count(4,3,2); // Returns: [3,5,7,9] // nl4 = count(5,reverse=true); // Returns: [4,3,2,1,0] // nl5 = count(5,3,reverse=true); // Returns: [7,6,5,4,3] function count(n,s=0,step=1,reverse=false) = let(n=is_list(n) ? len(n) : n) reverse? [for (i=[n-1:-1:0]) s+i*step] : [for (i=[0:1:n-1]) s+i*step]; // Function: list_bset() // Usage: // arr = list_bset(indexset, valuelist, [dflt]); // Topics: List Handling // See Also: bselect() // Description: // Opposite of `bselect()`. Returns a list the same length as `indexlist`, where each item will // either be 0 if the corresponding item in `indexset` is false, or the next sequential value // from `valuelist` if the item is true. The number of `true` values in `indexset` must be equal // to the length of `valuelist`. // Arguments: // indexset = A list of boolean values. // valuelist = The list of values to set into the returned list. // dflt = Default value to store when the indexset item is false. // Example: // a = list_bset([false,true,false,true,false], [3,4]); // Returns: [0,3,0,4,0] // b = list_bset([false,true,false,true,false], [3,4], dflt=1); // Returns: [1,3,1,4,1] function list_bset(indexset, valuelist, dflt=0) = assert(is_list(indexset), "The index set is not a list." ) assert(is_list(valuelist), "The `valuelist` is not a list." ) let( trueind = search([true], indexset,0)[0] ) assert( !(len(trueind)>len(valuelist)), str("List `valuelist` too short; its length should be ",len(trueind)) ) assert( !(len(trueind)=0.5) list[i]] ) concat(shuffle(left), shuffle(right)); // Function: repeat_entries() // Usage: // newlist = repeat_entries(list, N, [exact]); // Topics: List Handling // See Also: repeat() // Description: // Takes a list as input and duplicates some of its entries to produce a list // with length `N`. If the requested `N` is not a multiple of the list length then // the entries will be duplicated as uniformly as possible. You can also set `N` to a vector, // in which case len(N) must equal len(list) and the output repeats the ith entry N[i] times. // In either case, the result will be a list of length `N`. The `exact` option requires // that the final length is exactly as requested. If you set it to `false` then the // algorithm will favor uniformity and the output list may have a different number of // entries due to rounding. // . // When applied to a path the output path is the same geometrical shape but has some vertices // repeated. This can be useful when you need to align paths with a different number of points. // (See also subdivide_path for a different way to do that.) // Arguments: // list = list whose entries will be repeated // N = scalar total number of points desired or vector requesting N[i] copies of vertex i. // exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform points that may not match the number of points requested. Default: True // Example: // list = [0,1,2,3]; // a = repeat_entries(list, 6); // Returns: [0,0,1,2,2,3] // b = repeat_entries(list, 6, exact=false); // Returns: [0,0,1,1,2,2,3,3] // c = repeat_entries(list, [1,1,2,1], exact=false); // Returns: [0,1,2,2,3] function repeat_entries(list, N, exact=true) = assert(is_list(list) && len(list)>0, "The list cannot be void.") assert((is_finite(N) && N>0) || is_vector(N,len(list)), "Parameter N must be a number greater than zero or vector with the same length of `list`") let( length = len(list), reps_guess = is_list(N)? N : repeat(N/length,length), reps = exact ? _sum_preserving_round(reps_guess) : [for (val=reps_guess) round(val)] ) [for(i=[0:length-1]) each repeat(list[i],reps[i])]; // Function: list_set() // Usage: // list = list_set(list, indices, values, [dflt], [minlen]); // Topics: List Handling // See Also: list_insert(), list_remove(), list_remove_values() // Description: // Takes the input list and returns a new list such that `list[indices[i]] = values[i]` for all of // the (index,value) pairs supplied and unchanged for other indices. If you supply `indices` that are // beyond the length of the list then the list is extended and filled in with the `dflt` value. // If you set `minlen` then the list is lengthed, if necessary, by padding with `dflt` to that length. // Repetitions in `indices` are not allowed. The lists `indices` and `values` must have the same length. // If `indices` is given as a scalar, then that index of the given `list` will be set to the scalar value of `values`. // Arguments: // list = List to set items in. Default: [] // indices = List of indices into `list` to set. // values = List of values to set. // dflt = Default value to store in sparse skipped indices. // minlen = Minimum length to expand list to. // Example: // a = list_set([2,3,4,5], 2, 21); // Returns: [2,3,21,5] // b = list_set([2,3,4,5], [1,3], [81,47]); // Returns: [2,81,4,47] function list_set(list=[],indices,values,dflt=0,minlen=0) = assert(is_list(list)) !is_list(indices)? ( (is_finite(indices) && indices length ? list_trim(array,length) : list_pad(array,length,fill); // Section: Sorting // returns true for valid index specifications idx in the interval [imin, imax) // note that idx can't have any value greater or EQUAL to imax // this allows imax=INF as a bound to numerical lists function _valid_idx(idx,imin,imax) = is_undef(idx) || ( is_finite(idx) && ( is_undef(imin) || idx>=imin ) && ( is_undef(imax) || idx< imax ) ) || ( is_list(idx) && ( is_undef(imin) || min(idx)>=imin ) && ( is_undef(imax) || max(idx)< imax ) ) || ( is_range(idx) && ( is_undef(imin) || (idx[1]>0 && idx[0]>=imin ) || (idx[1]<0 && idx[0]<=imax ) ) && ( is_undef(imax) || (idx[1]>0 && idx[2]<=imax ) || (idx[1]<0 && idx[2]>=imin ) ) ); // idx should be an index of the arrays l[i] function _group_sort_by_index(l,idx) = len(l) == 0 ? [] : len(l) == 1 ? [l] : let( pivot = l[floor(len(l)/2)][idx], equal = [ for(li=l) if( li[idx]==pivot) li ], lesser = [ for(li=l) if( li[idx]< pivot) li ], greater = [ for(li=l) if( li[idx]> pivot) li ] ) concat( _group_sort_by_index(lesser,idx), [equal], _group_sort_by_index(greater,idx) ); function _group_sort(l) = len(l) == 0 ? [] : len(l) == 1 ? [l] : let( pivot = l[floor(len(l)/2)], equal = [ for(li=l) if( li==pivot) li ], lesser = [ for(li=l) if( li< pivot) li ], greater = [ for(li=l) if( li> pivot) li ] ) concat( _group_sort(lesser), [equal], _group_sort(greater) ); // Sort a vector of scalar values with the native comparison operator // all elements should have the same type. function _sort_scalars(arr) = len(arr)<=1 ? arr : let( pivot = arr[floor(len(arr)/2)], lesser = [ for (y = arr) if (y < pivot) y ], equal = [ for (y = arr) if (y == pivot) y ], greater = [ for (y = arr) if (y > pivot) y ] ) concat( _sort_scalars(lesser), equal, _sort_scalars(greater) ); // lexical sort of a homogeneous list of vectors // uses native comparison operator function _sort_vectors(arr, _i=0) = len(arr)<=1 || _i>=len(arr[0]) ? arr : let( pivot = arr[floor(len(arr)/2)][_i], lesser = [ for (entry=arr) if (entry[_i] < pivot ) entry ], equal = [ for (entry=arr) if (entry[_i] == pivot ) entry ], greater = [ for (entry=arr) if (entry[_i] > pivot ) entry ] ) concat( _sort_vectors(lesser, _i ), _sort_vectors(equal, _i+1 ), _sort_vectors(greater, _i ) ); // lexical sort of a homogeneous list of vectors by the vector components with indices in idxlist // all idxlist indices should be in the range of the vector dimensions // idxlist must be undef or a simple list of numbers // uses native comparison operator function _sort_vectors(arr, idxlist, _i=0) = len(arr)<=1 || ( is_list(idxlist) && _i>=len(idxlist) ) || _i>=len(arr[0]) ? arr : let( k = is_list(idxlist) ? idxlist[_i] : _i, pivot = arr[floor(len(arr)/2)][k], lesser = [ for (entry=arr) if (entry[k] < pivot ) entry ], equal = [ for (entry=arr) if (entry[k] == pivot ) entry ], greater = [ for (entry=arr) if (entry[k] > pivot ) entry ] ) concat( _sort_vectors(lesser, idxlist, _i ), _sort_vectors(equal, idxlist, _i+1), _sort_vectors(greater, idxlist, _i ) ); // sorting using compare_vals(); returns indexed list when `indexed==true` function _sort_general(arr, idx=undef, indexed=false) = (len(arr)<=1) ? arr : ! indexed && is_undef(idx) ? _lexical_sort(arr) : let( arrind = _indexed_sort(enumerate(arr,idx)) ) indexed ? arrind : [for(i=arrind) arr[i]]; // lexical sort using compare_vals() function _lexical_sort(arr) = len(arr)<=1? arr : let( pivot = arr[floor(len(arr)/2)] ) let( lesser = [ for (entry=arr) if (compare_vals(entry, pivot) <0 ) entry ], equal = [ for (entry=arr) if (compare_vals(entry, pivot)==0 ) entry ], greater = [ for (entry=arr) if (compare_vals(entry, pivot) >0 ) entry ] ) concat(_lexical_sort(lesser), equal, _lexical_sort(greater)); // given a list of pairs, return the first element of each pair of the list sorted by the second element of the pair // the sorting is done using compare_vals() function _indexed_sort(arrind) = arrind==[] ? [] : len(arrind)==1? [arrind[0][0]] : let( pivot = arrind[floor(len(arrind)/2)][1] ) let( lesser = [ for (entry=arrind) if (compare_vals(entry[1], pivot) <0 ) entry ], equal = [ for (entry=arrind) if (compare_vals(entry[1], pivot)==0 ) entry[0] ], greater = [ for (entry=arrind) if (compare_vals(entry[1], pivot) >0 ) entry ] ) concat(_indexed_sort(lesser), equal, _indexed_sort(greater)); // Function: sort() // Usage: // slist = sort(list, [idx]); // Topics: List Handling // See Also: shuffle(), sortidx(), unique(), unique_count(), group_sort() // Description: // Sorts the given list in lexicographic order. If the input is a homogeneous simple list or a homogeneous // list of vectors (see function is_homogeneous), the sorting method uses the native comparison operator and is faster. // When sorting non homogeneous list the elements are compared with `compare_vals`, with types ordered according to // `undef < boolean < number < string < list`. Comparison of lists is recursive. // When comparing vectors, homogeneous or not, the parameter `idx` may be used to select the components to compare. // Note that homogeneous lists of vectors may contain mixed types provided that for any two list elements // list[i] and list[j] satisfies type(list[i][k])==type(list[j][k]) for all k. // Strings are allowed as any list element and are compared with the native operators although no substring // comparison is possible. // Arguments: // list = The list to sort. // idx = If given, do the comparison based just on the specified index, range or list of indices. // Example: // // Homogeneous lists // l1 = [45,2,16,37,8,3,9,23,89,12,34]; // sorted1 = sort(l1); // Returns [2,3,8,9,12,16,23,34,37,45,89] // l2 = [["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]]; // sorted2 = sort(l2); // Returns: [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]] // // Non-homegenous list // l3 = [[4,0],[7],[3,9],20,[4],[3,1],[8]]; // sorted3 = sort(l3); // Returns: [20,[3,1],[3,9],[4],[4,0],[7],[8]] function sort(list, idx=undef) = assert(is_list(list)||is_string(list), "Invalid input." ) is_string(list)? str_join(sort([for (x = list) x],idx)) : !is_list(list) || len(list)<=1 ? list : is_homogeneous(list,1) ? let(size = array_dim(list[0])) size==0 ? _sort_scalars(list) : len(size)!=1 ? _sort_general(list,idx) : is_undef(idx) ? _sort_vectors(list) : assert( _valid_idx(idx) , "Invalid indices.") _sort_vectors(list,[for(i=idx) i]) : _sort_general(list,idx); // Function: sortidx() // Usage: // idxlist = sortidx(list, [idx]); // Topics: List Handling // See Also: shuffle(), sort(), group_sort(), unique(), unique_count() // Description: // Given a list, sort it as function `sort()`, and returns // a list of indexes into the original list in that sorted order. // If you iterate the returned list in order, and use the list items // to index into the original list, you will be iterating the original // values in sorted order. // Arguments: // list = The list to sort. // idx = If given, do the comparison based just on the specified index, range or list of indices. // Example: // lst = ["d","b","e","c"]; // idxs = sortidx(lst); // Returns: [1,3,0,2] // ordered = select(lst, idxs); // Returns: ["b", "c", "d", "e"] // Example: // lst = [ // ["foo", 88, [0,0,1], false], // ["bar", 90, [0,1,0], true], // ["baz", 89, [1,0,0], false], // ["qux", 23, [1,1,1], true] // ]; // idxs1 = sortidx(lst, idx=1); // Returns: [3,0,2,1] // idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3] // idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1] function sortidx(list, idx=undef) = assert(is_list(list)||is_string(list), "Invalid input." ) !is_list(list) || len(list)<=1 ? list : is_homogeneous(list,1) ? let( size = array_dim(list[0]), aug = ! (size==0 || len(size)==1) ? 0 // for general sorting : [for(i=[0:len(list)-1]) concat(i,list[i])], // for scalar or vector sorting lidx = size==0? [1] : // scalar sorting len(size)==1 ? is_undef(idx) ? [for(i=[0:len(list[0])-1]) i+1] // vector sorting : [for(i=idx) i+1] // vector sorting : 0 // just to signal ) assert( ! ( size==0 && is_def(idx) ), "The specification of `idx` is incompatible with scalar sorting." ) assert( _valid_idx(idx) , "Invalid indices." ) lidx!=0 ? let( lsort = _sort_vectors(aug,lidx) ) [for(li=lsort) li[0] ] : _sort_general(list,idx,indexed=true) : _sort_general(list,idx,indexed=true); // Function: is_increasing() // Usage: // bool = is_increasing(list); // Topics: List Handling // See Also: max_index(), min_index(), is_decreasing() // Description: // Returns true if the list is (non-strictly) increasing, or strictly increasing if strict is set to true. // The list can be a list of any items that OpenSCAD can compare, or it can be a string which will be // evaluated character by character. // Arguments: // list = list (or string) to check // strict = set to true to test that list is strictly increasing // Example: // a = is_increasing([1,2,3,4]); // Returns: true // b = is_increasing([1,3,2,4]); // Returns: false // c = is_increasing([1,3,3,4]); // Returns: true // d = is_increasing([1,3,3,4],strict=true); // Returns: false // e = is_increasing([4,3,2,1]); // Returns: false function is_increasing(list,strict=false) = assert(is_list(list)||is_string(list)) strict ? len([for (p=pair(list)) if(p.x>=p.y) true])==0 : len([for (p=pair(list)) if(p.x>p.y) true])==0; // Function: is_decreasing() // Usage: // bool = is_decreasing(list); // Topics: List Handling // See Also: max_index(), min_index(), is_increasing() // Description: // Returns true if the list is (non-strictly) decreasing, or strictly decreasing if strict is set to true. // The list can be a list of any items that OpenSCAD can compare, or it can be a string which will be // evaluated character by character. // Arguments: // list = list (or string) to check // strict = set to true to test that list is strictly decreasing // Example: // a = is_decreasing([1,2,3,4]); // Returns: false // b = is_decreasing([4,2,3,1]); // Returns: false // c = is_decreasing([4,3,2,1]); // Returns: true function is_decreasing(list,strict=false) = assert(is_list(list)||is_string(list)) strict ? len([for (p=pair(list)) if(p.x<=p.y) true])==0 : len([for (p=pair(list)) if(p.xpivot) li ] ) concat( _unique_sort(lesser), equal[0], _unique_sort(greater) ); // Function: unique_count() // Usage: // counts = unique_count(list); // Topics: List Handling // See Also: shuffle(), sort(), sortidx(), unique() // Description: // Returns `[sorted,counts]` where `sorted` is a sorted list of the unique items in `list` and `counts` is a list such // that `count[i]` gives the number of times that `sorted[i]` appears in `list`. // Arguments: // list = The list to analyze. // Example: // sorted = unique([5,2,8,3,1,3,8,3,5]); // Returns: [ [1,2,3,5,8], [1,1,3,2,2] ] function unique_count(list) = assert(is_list(list) || is_string(list), "Invalid input." ) list == [] ? [[],[]] : is_homogeneous(list,1) && ! is_list(list[0]) ? let( sorted = _group_sort(list) ) [ [for(s=sorted) s[0] ], [for(s=sorted) len(s) ] ] : let( list = sort(list), ind = [0, for(i=[1:1:len(list)-1]) if (list[i]!=list[i-1]) i] ) [ select(list,ind), deltas( concat(ind,[len(list)]) ) ]; // Function: group_sort() // Usage: // ulist = group_sort(list); // Topics: List Handling // See Also: shuffle(), sort(), sortidx(), unique(), unique_count() // Description: // Given a list of values, returns the sorted list with all repeated items grouped in a list. // When the list entries are themselves lists, the sorting may be done based on the `idx` entry // of those entries, that should be numbers. // The result is always a list of lists. // Arguments: // list = The list to sort. // idx = If given, do the comparison based just on the specified index. Default: zero. // Example: // sorted = group_sort([5,2,8,3,1,3,8,7,5]); // Returns: [[1],[2],[3,3],[5,5],[7],[8,8]] // sorted2 = group_sort([[5,"a"],[2,"b"], [5,"c"], [3,"d"], [2,"e"] ], idx=0); // Returns: [[[2,"b"],[2,"e"]], [[5,"a"],[5,"c"]], [[3,"d"]] ] function group_sort(list, idx) = assert(is_list(list), "Input should be a list." ) assert(is_undef(idx) || (is_finite(idx) && idx>=0) , "Invalid index." ) len(list)<=1 ? [list] : is_vector(list)? _group_sort(list) : let( idx = is_undef(idx) ? 0 : idx ) assert( [for(entry=list) if(!is_list(entry) || len(entry)=0, "k must be nonnegative") let( v = list[rand_int(0,len(list)-1,1)[0]], smaller = [for(li=list) if(li= k ? [ each smaller, for(i=[1:k-len(smaller)]) v ] : len(smaller) > k ? list_smallest(smaller, k) : let( bigger = [for(li=list) if(li>v) li ] ) concat(smaller, equal, list_smallest(bigger, k-len(smaller) -len(equal))); // Function: group_data() // Usage: // groupings = group_data(groups, values); // Topics: Array Handling // See Also: zip(), zip_long(), array_group() // Description: // Given a list of integer group numbers, and an equal-length list of values, // returns a list of groups with the values sorted into the corresponding groups. // Ie: if you have a groups index list of [2,3,2] and values of ["A","B","C"], then // the values "A" and "C" will be put in group 2, and "B" will be in group 3. // Groups that have no values grouped into them will be an empty list. So the // above would return [[], [], ["A","C"], ["B"]] // Arguments: // groups = A list of integer group index numbers. // values = A list of values to sort into groups. // Example: // groups = group_data([1,2,0], ["A","B","C"]); // Returns [["B"],["C"],["A"]] // Example: // groups = group_data([1,3,1], ["A","B","C"]); // Returns [[],["A","C"],[],["B"]] function group_data(groups, values) = assert(all_integer(groups) && all_nonnegative(groups)) assert(is_list(values)) assert(len(groups)==len(values), "The groups and values arguments should be lists of matching length.") let( sorted = _group_sort_by_index(zip(groups,values),0) ) // retrieve values and insert [] [ for (i = idx(sorted)) let( a = i==0? 0 : sorted[i-1][0][0]+1, g0 = sorted[i] ) each [ for (j = [a:1:g0[0][0]-1]) [], [for (g1 = g0) g1[1]] ] ]; // Section: Iteration Helpers // Function: idx() // Usage: // rng = idx(list, [s=], [e=], [step=]); // for(i=idx(list, [s=], [e=], [step=])) ... // Topics: List Handling, Iteration // See Also: enumerate(), pair(), triplet(), combinations(), permutations() // Description: // Returns the range of indexes for the given list. // Arguments: // list = The list to returns the index range of. // s = The starting index. Default: 0 // e = The delta from the end of the list. Default: -1 (end of list) // step = The step size to stride through the list. Default: 1 // Example(2D): // colors = ["red", "green", "blue"]; // for (i=idx(colors)) right(20*i) color(colors[i]) circle(d=10); function idx(list, s=0, e=-1, step=1) = assert(is_list(list)||is_string(list), "Invalid input." ) let( ll = len(list) ) ll == 0 ? [0:1:ll-1] : let( _s = posmod(s,ll), _e = posmod(e,ll) ) [_s : step : _e]; // Function: enumerate() // Usage: // arr = enumerate(l, [idx]); // for (x = enumerate(l, [idx])) ... // x[0] is the index number, x[1] is the item. // Topics: List Handling, Iteration // See Also: idx(), pair(), triplet(), combinations(), permutations() // Description: // Returns a list, with each item of the given list `l` numbered in a sublist. // Something like: `[[0,l[0]], [1,l[1]], [2,l[2]], ...]` // Arguments: // l = List to enumerate. // idx = If given, enumerates just the given columns items of `l`. // Example: // enumerate(["a","b","c"]); // Returns: [[0,"a"], [1,"b"], [2,"c"]] // enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1); // Returns: [[0,"a"], [1,"b"], [2,"c"]] // enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]); // Returns: [[0,"a",12], [1,"b",10], [2,"c",14]] // Example(2D): // colors = ["red", "green", "blue"]; // for (p=enumerate(colors)) right(20*p[0]) color(p[1]) circle(d=10); function enumerate(l,idx=undef) = assert(is_list(l)||is_string(list), "Invalid input." ) assert( _valid_idx(idx,0,len(l)), "Invalid index/indices." ) (idx==undef) ? [for (i=[0:1:len(l)-1]) [i,l[i]]] : [for (i=[0:1:len(l)-1]) [ i, for (j=idx) l[i][j]] ]; // Function: pair() // Usage: // p = pair(list, [wrap]); // for (p = pair(list, [wrap])) ... // On each iteration, p contains a list of two adjacent items. // Topics: List Handling, Iteration // See Also: idx(), enumerate(), triplet(), combinations(), permutations() // Description: // Takes a list, and returns a list of adjacent pairs from it, optionally wrapping back to the front. // Arguments: // list = The list to iterate. // wrap = If true, wrap back to the start from the end. ie: return the last and first items as the last pair. Default: false // Example(2D): Does NOT wrap from end to start, // for (p = pair(circle(d=40, $fn=12))) // stroke(p, endcap2="arrow2"); // Example(2D): Wraps around from end to start. // for (p = pair(circle(d=40, $fn=12), wrap=true)) // stroke(p, endcap2="arrow2"); // Example: // l = ["A","B","C","D"]; // echo([for (p=pair(l)) str(p.y,p.x)]); // Outputs: ["BA", "CB", "DC"] function pair(list, wrap=false) = assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_bool(wrap)) let( ll = len(list) ) wrap ? [for (i=[0:1:ll-1]) [list[i], list[(i+1) % ll]]] : [for (i=[0:1:ll-2]) [list[i], list[i+1]]]; // Function: triplet() // Usage: // list = triplet(list, [wrap]); // for (t = triplet(list, [wrap])) ... // Topics: List Handling, Iteration // See Also: idx(), enumerate(), pair(), combinations(), permutations() // Description: // Takes a list, and returns a list of adjacent triplets from it, optionally wrapping back to the front. // Example: // l = ["A","B","C","D","E"]; // echo([for (p=triplet(l)) str(p.z,p.y,p.x)]); // Outputs: ["CBA", "DCB", "EDC"] // Example(2D): // path = [for (i=[0:24]) polar_to_xy(i*2, i*360/12)]; // for (t = triplet(path)) { // a = t[0]; b = t[1]; c = t[2]; // v = unit(unit(a-b) + unit(c-b)); // translate(b) rot(from=FWD,to=v) anchor_arrow2d(); // } // stroke(path); function triplet(list, wrap=false) = assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_bool(wrap)) let( ll = len(list) ) wrap ? [for (i=[0:1:ll-1]) [ list[i], list[(i+1)%ll], list[(i+2)%ll] ]] : [for (i=[0:1:ll-3]) [ list[i], list[i+1], list[i+2] ]]; // Function: combinations() // Usage: // list = combinations(l, [n]); // Topics: List Handling, Iteration // See Also: idx(), enumerate(), pair(), triplet(), permutations() // Description: // Returns a list of all of the (unordered) combinations of `n` items out of the given list `l`. // For the list `[1,2,3,4]`, with `n=2`, this will return `[[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]]`. // For the list `[1,2,3,4]`, with `n=3`, this will return `[[1,2,3], [1,2,4], [1,3,4], [2,3,4]]`. // Arguments: // l = The list to provide permutations for. // n = The number of items in each permutation. Default: 2 // Example: // pairs = combinations([3,4,5,6]); // Returns: [[3,4],[3,5],[3,6],[4,5],[4,6],[5,6]] // triplets = combinations([3,4,5,6],n=3); // Returns: [[3,4,5],[3,4,6],[3,5,6],[4,5,6]] // Example(2D): // for (p=combinations(regular_ngon(n=7,d=100))) stroke(p); function combinations(l,n=2,_s=0) = assert(is_list(l), "Invalid list." ) assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." ) n==1 ? [for (i=[_s:1:len(l)-1]) [l[i]]] : [for (i=[_s:1:len(l)-n], p=combinations(l,n=n-1,_s=i+1)) concat([l[i]], p)]; // Function: permutations() // Usage: // list = permutations(l, [n]); // Topics: List Handling, Iteration // See Also: idx(), enumerate(), pair(), triplet(), combinations() // Description: // Returns a list of all of the (ordered) permutation `n` items out of the given list `l`. // For the list `[1,2,3]`, with `n=2`, this will return `[[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]` // For the list `[1,2,3]`, with `n=3`, this will return `[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]` // Arguments: // l = The list to provide permutations for. // n = The number of items in each permutation. Default: 2 // Example: // pairs = permutations([3,4,5,6]); // // Returns: [[3,4],[3,5],[3,6],[4,3],[4,5],[4,6],[5,3],[5,4],[5,6],[6,3],[6,4],[6,5]] function permutations(l,n=2) = assert(is_list(l), "Invalid list." ) assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." ) n==1 ? [for (i=[0:1:len(l)-1]) [l[i]]] : [for (i=idx(l), p=permutations([for (j=idx(l)) if (i!=j) l[j]], n=n-1)) concat([l[i]], p)]; // Function: zip() // Usage: // pairs = zip(a,b); // triples = zip(a,b,c); // quads = zip([LIST1,LIST2,LIST3,LIST4]); // Topics: List Handling, Iteration // See Also: zip_long() // Description: // Zips together two or more lists into a single list. For example, if you have two // lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. // The list returned will be as long as the shortest list passed to zip(). // Arguments: // a = The first list, or a list of lists if b and c are not given. // b = The second list, if given. // c = The third list, if given. // Example: // a = [9,8,7,6]; b = [1,2,3]; // for (p=zip(a,b)) echo(p); // // ECHO: [9,1] // // ECHO: [8,2] // // ECHO: [7,3] function zip(a,b,c) = b!=undef? zip([a,b,if (c!=undef) c]) : let(n = min_length(a)) [for (i=[0:1:n-1]) [for (x=a) x[i]]]; // Function: zip_long() // Usage: // pairs = zip_long(a,b); // triples = zip_long(a,b,c); // quads = zip_long([LIST1,LIST2,LIST3,LIST4]); // Topics: List Handling, Iteration // See Also: zip() // Description: // Zips together two or more lists into a single list. For example, if you have two // lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ]. // The list returned will be as long as the longest list passed to zip_long(), with // shorter lists padded by the value in `fill`. // Arguments: // a = The first list, or a list of lists if b and c are not given. // b = The second list, if given. // c = The third list, if given. // fill = The value to pad shorter lists with. Default: undef // Example: // a = [9,8,7,6]; b = [1,2,3]; // for (p=zip_long(a,b,fill=88)) echo(p); // // ECHO: [9,1] // // ECHO: [8,2] // // ECHO: [7,3] // // ECHO: [6,88]] function zip_long(a,b,c,fill) = b!=undef? zip_long([a,b,if (c!=undef) c],fill=fill) : let(n = max_length(a)) [for (i=[0:1:n-1]) [for (x=a) i0) [for(i=[0:1:len(diag)-1]) [for(j=[0:len(diag)-1]) i==j?diag[i] : offdiag]]; // Function: submatrix_set() // Usage: // mat = submatrix_set(M, A, [m], [n]); // Topics: Matrices, Array Handling // See Also: columns(), submatrix() // Description: // Sets a submatrix of M equal to the matrix A. By default the top left corner of M is set to A, but // you can specify offset coordinates m and n. If A (as adjusted by m and n) extends beyond the bounds // of M then the extra entries are ignored. You can pass in A=[[]], a null matrix, and M will be // returned unchanged. Note that the input M need not be rectangular in shape. // Arguments: // M = Original matrix. // A = Sub-matrix of parts to set. // m = Row number of upper-left corner to place A at. // n = Column number of upper-left corner to place A at. function submatrix_set(M,A,m=0,n=0) = assert(is_list(M)) assert(is_list(A)) assert(is_int(m)) assert(is_int(n)) let( badrows = [for(i=idx(A)) if (!is_list(A[i])) i]) assert(badrows==[], str("Input submatrix malformed rows: ",badrows)) [for(i=[0:1:len(M)-1]) assert(is_list(M[i]), str("Row ",i," of input matrix is not a list")) [for(j=[0:1:len(M[i])-1]) i>=m && i =n && j=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: transpose() // Usage: // arr = transpose(arr, [reverse]); // Topics: Matrices, Array Handling // See Also: submatrix(), block_matrix(), hstack(), flatten() // Description: // Returns the transpose of the given input array. The input should be a list of lists that are // all the same length. If you give a vector then transpose returns it unchanged. // When reverse=true, the transpose is done across to the secondary diagonal. (See example below.) // By default, reverse=false. // Example: // arr = [ // ["a", "b", "c"], // ["d", "e", "f"], // ["g", "h", "i"] // ]; // t = transpose(arr); // // Returns: // // [ // // ["a", "d", "g"], // // ["b", "e", "h"], // // ["c", "f", "i"], // // ] // Example: // arr = [ // ["a", "b", "c"], // ["d", "e", "f"] // ]; // t = transpose(arr); // // Returns: // // [ // // ["a", "d"], // // ["b", "e"], // // ["c", "f"], // // ] // Example: // arr = [ // ["a", "b", "c"], // ["d", "e", "f"], // ["g", "h", "i"] // ]; // t = transpose(arr, reverse=true); // // Returns: // // [ // // ["i", "f", "c"], // // ["h", "e", "b"], // // ["g", "d", "a"] // // ] // Example: Transpose on a list of numbers returns the list unchanged // transpose([3,4,5]); // Returns: [3,4,5] function transpose(arr, reverse=false) = assert( is_list(arr) && len(arr)>0, "Input to transpose must be a nonempty list.") is_list(arr[0]) ? let( len0 = len(arr[0]) ) assert([for(a=arr) if(!is_list(a) || len(a)!=len0) 1 ]==[], "Input to transpose has inconsistent row lengths." ) reverse ? [for (i=[0:1:len0-1]) [ for (j=[0:1:len(arr)-1]) arr[len(arr)-1-j][len0-1-i] ] ] : [for (i=[0:1:len0-1]) [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] : assert( is_vector(arr), "Input to transpose must be a vector or list of lists.") arr; // Section: Matrices // Function: is_matrix_symmetric() // Usage: // b = is_matrix_symmetric(A, [eps]) // Description: // Returns true if the input matrix is symmetric, meaning it equals its transpose. // Matrix should have numerical entries. // Arguments: // A = matrix to test // eps = epsilon for comparing equality. Default: 1e-12 function is_matrix_symmetric(A,eps=1e-12) = approx(A,transpose(A), eps); // Function&Module: echo_matrix() // Usage: // echo_matrix(M, [description=], [sig=], [eps=]); // dummy = echo_matrix(M, [description=], [sig=], [eps=]), // Description: // Display a numerical matrix in a readable columnar format with `sig` significant // digits. Values smaller than eps display as zero. If you give a description // it is displayed at the top. function echo_matrix(M,description,sig=4,eps=1e-9) = let( horiz_line = chr(8213), matstr = matrix_strings(M,sig=sig,eps=eps), separator = str_join(repeat(horiz_line,10)), dummy=echo(str(separator," ",is_def(description) ? description : "")) [for(row=matstr) echo(row)] ) echo(separator); module echo_matrix(M,description,sig=4,eps=1e-9) { dummy = echo_matrix(M,description,sig,eps); } // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap