////////////////////////////////////////////////////////////////////// // LibFile: arrays.scad // List and Array manipulation functions. // To use, add the following lines to the beginning of your file: // ``` // use // ``` ////////////////////////////////////////////////////////////////////// /* BSD 2-Clause License Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Section: 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. // Section: List Operations // Function: replist() // Usage: // replist(val, n) // Description: // Generates a list or array of `n` copies of the given `list`. // 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: // replist(1, 4); // Returns [1,1,1,1] // replist(8, [2,3]); // Returns [[8,8,8], [8,8,8]] // replist(0, [2,2,3]); // Returns [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]] // replist([1,2,3],3); // Returns [[1,2,3], [1,2,3], [1,2,3]] function replist(val, n, i=0) = is_num(n)? [for(j=[1:n]) val] : (i>=len(n))? val : [for (j=[1:n[i]]) replist(val, n, i+1)]; // Function: in_list() // Description: Returns true if value `x` is in list `l`. // Arguments: // x = The value to search for. // l = The list to search. // idx = If given, searches the given subindexes for matches for `x`. // Example: // in_list("bar", ["foo", "bar", "baz"]); // Returns true. // in_list("bee", ["foo", "bar", "baz"]); // Returns false. // in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; // Function: slice() // Description: // Returns a slice of a list. The first item is index 0. // Negative indexes are counted back from the end. The last item is -1. // Arguments: // arr = The array/list to get the slice of. // st = The index of the first item to return. // end = The index after the last item to return, unless negative, in which case the last item to return. // Example: // slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7] // slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9] // slice([3,4,5,6,7,8,9], 1, 1); // Returns [] // slice([3,4,5,6,7,8,9], 6, -1); // Returns [9] // slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8] function slice(arr,st,end) = let( s=st<0?(len(arr)+st):st, e=end<0?(len(arr)+end+1):end ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; // Function: select() // Description: // Returns a portion of a list, wrapping around past the beginning, if endlength)? list_trim(v,length) : list_pad(v,length,fill); // Function: enumerate() // 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 subindex 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]] function enumerate(l,idx=undef) = (l==[])? [] : (idx==undef)? [for (i=[0:len(l)-1]) [i,l[i]]] : [for (i=[0:len(l)-1]) concat([i], [for (j=idx) l[i][j]])]; // Function: sort() // Usage: // sort(arr, [idx]) // Description: // Sorts the given list using `compare_vals()`. Results are undefined if list elements are not of similar type. // Arguments: // arr = The list to sort. // idx = If given, the index, range, or list of indices of sublist items to compare. // Example: // l = [45,2,16,37,8,3,9,23,89,12,34]; // sorted = sort(l); // Returns [2,3,8,9,12,16,23,34,37,45,89] function sort(arr, idx=undef) = (len(arr)<=1) ? arr : let( pivot = arr[floor(len(arr)/2)], pivotval = idx==undef? pivot : [for (i=idx) pivot[i]], compare = [ for (entry = arr) let( val = idx==undef? entry : [for (i=idx) entry[i]], cmp = compare_vals(val, pivotval) ) cmp ], lesser = [ for (i = [0:len(arr)-1]) if (compare[i] < 0) arr[i] ], equal = [ for (i = [0:len(arr)-1]) if (compare[i] ==0) arr[i] ], greater = [ for (i = [0:len(arr)-1]) if (compare[i] > 0) arr[i] ] ) concat(sort(lesser,idx), equal, sort(greater,idx)); // Function: sortidx() // Description: // Given a list, calculates the sort order of the list, 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. // Example: // lst = ["d","b","e","c"]; // idxs = sortidx(lst); // Returns: [1,3,0,2] // ordered = [for (i=idxs) lst[i]]; // 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(l, idx=undef) = (l==[])? [] : let( ll=enumerate(l,idx=idx), sidx = [1:len(ll[0])-1] ) array_subindex(sort(ll, idx=sidx), 0); // Function: unique() // Usage: // unique(arr); // Description: // Returns a sorted list with all repeated items removed. // Arguments: // arr = The list to uniquify. function unique(arr) = len(arr)<=1? arr : let( sorted = sort(arr) ) [ for (i=[0:len(sorted)-1]) if (i==0 || (sorted[i] != sorted[i-1])) sorted[i] ]; // Section: Array Manipulation // Function: array_subindex() // Description: // For each array item, return the indexed subitem. // Returns a list of the values of each vector at the specfied // index list or range. If the index list or range has // only one entry the output list is flattened. // Arguments: // v = The given list of lists. // idx = The index, list of indices, or range of indices to fetch. // Example: // v = [[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; // array_subindex(v,2); // Returns [3, 7, 11, 15] // array_subindex(v,[2,1]); // Returns [[3, 2], [7, 6], [11, 10], [15, 14]] // array_subindex(v,[1:3]); // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]] function array_subindex(v, idx) = [ for(val=v) let(value=[for(i=idx) val[i]]) len(value)==1 ? value[0] : value ]; // Function: array_zip() // Usage: // array_zip(v1, v2, v3, [fit], [fill]); // array_zip(vecs, [fit], [fill]); // Description: // Zips together corresponding items from two or more lists. // Returns a list of lists, where each sublist contains corresponding // items from each of the input lists. `[[A1, B1, C1], [A2, B2, C2], ...]` // Arguments: // vecs = A list of two or more lists to zipper together. // fit = If `fit=="short"`, the zips together up to the length of the shortest list in vecs. If `fit=="long"`, then pads all lists to the length of the longest, using the value in `fill`. If `fit==false`, then requires all lists to be the same length. Default: false. // fill = The default value to fill in with if one or more lists if short. Default: undef // Example: // v1 = [1,2,3,4]; // v2 = [5,6,7]; // v3 = [8,9,10,11]; // array_zip(v1,v3); // returns [[1,8], [2,9], [3,10], [4,11]] // array_zip([v1,v3]); // returns [[1,8], [2,9], [3,10], [4,11]] // array_zip([v1,v2], fit="short"); // returns [[1,5], [2,6], [3,7]] // array_zip([v1,v2], fit="long"); // returns [[1,5], [2,6], [3,7], [4,undef]] // array_zip([v1,v2], fit="long, fill=0); // returns [[1,5], [2,6], [3,7], [4,0]] // array_zip([v1,v2,v3], fit="long"); // returns [[1,5,8], [2,6,9], [3,7,10], [4,undef,11]] // Example: // v1 = [[1,2,3], [4,5,6], [7,8,9]]; // v2 = [[20,19,18], [17,16,15], [14,13,12]]; // array_zip(v1,v2); // Returns [[1,2,3,20,19,18], [4,5,6,17,16,15], [7,8,9,14,13,12]] function array_zip(vecs, v2, v3, fit=false, fill=undef) = (v3!=undef)? array_zip([vecs,v2,v3], fit=fit, fill=fill) : (v2!=undef)? array_zip([vecs,v2], fit=fit, fill=fill) : let( dummy1 = assert_in_list("fit", fit, [false, "short", "long"]), minlen = list_shortest(vecs), maxlen = list_longest(vecs), dummy2 = (fit==false)? assert(minlen==maxlen, "Input vectors must have the same length") : 0 ) (fit == "long")? [for(i=[0:maxlen-1]) [for(v=vecs) for(x=(i len(dimlist))? 0 : dimlist[depth-1] ); // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap