diff --git a/arrays.scad b/arrays.scad index a6d43d1..d012c94 100644 --- a/arrays.scad +++ b/arrays.scad @@ -19,6 +19,32 @@ // Section: List Query Operations +// Function: is_homogeneous() +// Usage: +// is_homogeneous(list,depth) +// Description: +// Returns true when the list have elements of same type up to the depth `depth`. +// Booleans and numbers are not distinguinshed as of distinct types. +// Arguments: +// list - the list to check +// depth - the lowest level the check is done +// Example: +// is_homogeneous( [[1,["a"]], [2,["b"]]] ) // Returns true +// is_homogeneous( [[1,["a"]], [2,[true]]] ) // Returns false +// is_homogeneous( [[1,["a"]], [2,[true]]], 1 ) // Returns true +// is_homogeneous( [[1,["a"]], [2,[true]]], 2 ) // Returns false +// is_homogeneous( [[1,["a"]], [true,["b"]]] ) // Returns true +function is_homogeneous(l, depth) = + !is_list(l) || l==[] ? false : + let( l0=l[0] ) + [] == [for(i=[1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ]; + +function _same_type(a,b, depth) = + (depth==0) || (a>=b) || (a==b) || (a<=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: select() // Description: // Returns a portion of a list, wrapping around past the beginning, if end=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 ) ) ); + + // Function: shuffle() // Description: // Shuffles the input list into random order. @@ -611,7 +654,8 @@ function shuffle(list) = concat(shuffle(left), shuffle(right)); -// Sort a vector of scalar values +// 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( @@ -623,105 +667,67 @@ function _sort_scalars(arr) = concat( _sort_scalars(lesser), equal, _sort_scalars(greater) ); -// Sort a vector of vectors based on the first entry only of each vector -function _sort_vectors1(arr) = - len(arr)<=1 ? arr : - !(len(arr)>0) ? [] : +// 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)], - lesser = [ for (y = arr) if (y[0] < pivot[0]) y ], - equal = [ for (y = arr) if (y[0] == pivot[0]) y ], - greater = [ for (y = arr) if (y[0] > pivot[0]) y ] - ) - concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) ); + 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 ) ); + - -// Sort a vector of vectors based on the first two entries of each vector -// Lexicographic order, remaining entries of vector ignored -function _sort_vectors2(arr) = - len(arr)<=1 ? arr : - !(len(arr)>0) ? [] : +// 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( - pivot = arr[floor(len(arr)/2)], - lesser = [ for (y = arr) if (y[0] < pivot[0] || (y[0]==pivot[0] && y[1] pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ] - ) - concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) ); + 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 ) ); + - -// Sort a vector of vectors based on the first three entries of each vector -// Lexicographic order, remaining entries of vector ignored -function _sort_vectors3(arr) = - len(arr)<=1 ? arr : let( - pivot = arr[floor(len(arr)/2)], - lesser = [ for (y = arr) - if ( y[0] < pivot[0] - || ( y[0]==pivot[0] - && ( y[1] pivot[0] - || ( y[0]==pivot[0] - && ( y[1] > pivot[1] - || ( y[1]==pivot[1] - && y[2] > pivot[2] )))) - y ] - ) concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) ); - - -// Sort a vector of vectors based on the first four entries of each vector -// Lexicographic order, remaining entries of vector ignored -function _sort_vectors4(arr) = - len(arr)<=1 ? arr : let( - pivot = arr[floor(len(arr)/2)], - lesser = [ for (y = arr) - if ( y[0] < pivot[0] - || ( y[0]==pivot[0] - && ( y[1] pivot[0] - || ( y[0]==pivot[0] - && ( y[1]>pivot[1] - || ( y[1]==pivot[1] - && ( y[2]>pivot[2] - || ( y[2]==pivot[2] - && y[3]>pivot[3] )))))) - y ] - ) concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) ); - - -// when idx==undef, returns the sorted array -// otherwise, returns the indices of the sorted array -function _sort_general(arr, idx=undef) = +// sorting using compare_vals(); returns indexed list when `indexed==true` +function _sort_general(arr, idx=undef, indexed=false) = (len(arr)<=1) ? arr : - is_undef(idx) - ? _sort_scalar(arr) - : let( arrind=[for(k=[0:len(arr)-1], ark=[arr[k]]) [ k, [for (i=idx) ark[i]] ] ] ) - _indexed_sort(arrind); + ! 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) = + 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]] : + 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 ], @@ -731,52 +737,46 @@ function _indexed_sort(arrind) = concat(_indexed_sort(lesser), equal, _indexed_sort(greater)); -// 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 -function _valid_idx(idx,imin,imax) = - is_undef(idx) - || ( is_finite(idx) && idx>=imin && idx< imax ) - || ( is_list(idx) && min(idx)>=imin && max(idx)< imax ) - || ( valid_range(idx) && idx[0]>=imin && idx[2]< imax ); - - // Function: sort() // Usage: // sort(list, [idx]) // Description: -// Sorts the given list using `compare_vals()`, sorting in lexicographic order, with types ordered according to +// 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. -// If the list is a list of vectors whose length is from 1 to 4 and the `idx` parameter is not passed, then -// `sort` uses a much more efficient method for comparisons and will run much faster. In this case, all entries -// in the data are compared using the native comparison operator, so comparisons between types will fail. +// 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: -// 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(list, idx=undef) = +// 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) = !is_list(list) || len(list)<=1 ? list : - is_def(idx) - ? assert( _valid_idx(idx,0,len(list)) , "Invalid indices.") - let( sarr = _sort_general(list,idx) ) - [for(i=[0:len(sarr)-1]) list[sarr[i]] ] - : let(size = array_dim(list)) - len(size)==1 ? _sort_scalars(list) : - len(size)==2 && size[1] <=4 - ? ( - size[1]==0 ? list : - size[1]==1 ? _sort_vectors1(list) : - size[1]==2 ? _sort_vectors2(list) : - size[1]==3 ? _sort_vectors3(list) - /*size[1]==4*/ : _sort_vectors4(list) - ) - : _sort_general(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() // Description: -// Given a list, calculates the sort order of the list, and returns +// 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 @@ -795,31 +795,28 @@ function sort(list, idx=undef) = // 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 to sort." ) - assert( _valid_idx(idx,0,len(list)) , "Invalid indices.") - list==[] ? [] : - let( - size = array_dim(list), - aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4)) - ? zip(list, list_range(len(list))) - : 0 - ) - is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) : - is_undef(idx) && len(size) == 2 && size[1] <=4 - ? ( - size[1]==0 ? list_range(len(arr)) : - size[1]==1 ? subindex(_sort_vectors1(aug),1) : - size[1]==2 ? subindex(_sort_vectors2(aug),2) : - size[1]==3 ? subindex(_sort_vectors3(aug),3) - /*size[1]==4*/ : subindex(_sort_vectors4(aug),4) - ) - : // general case - _sort_general(list,idx); - - -// sort() does not accept strings but sortidx does; isn't inconsistent ? - +function sortidx(list, idx=undef) = + !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: unique() // Usage: @@ -1241,16 +1238,22 @@ function full_flatten(l) = [for(a=l) if(is_list(a)) (each full_flatten(a)) else // Internal. Not exposed. function _array_dim_recurse(v) = !is_list(v[0]) - ? sum( [for(entry=v) is_list(entry) ? 1 : 0] ) == 0 ? [] : [undef] + ? len( [for(entry=v) if(!is_list(entry)) 0] ) == 0 ? [] : [undef] : let( - firstlen = len(v[0]), - first = sum( [for(entry = v) len(entry) == firstlen ? 0 : 1] ) == 0 ? firstlen : undef, + 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: @@ -1281,6 +1284,8 @@ function array_dim(v, depth=undef) = ? len(v) : let( dimlist = _array_dim_recurse(v)) (depth > len(dimlist))? 0 : dimlist[depth-1] ; + + // This function may return undef! diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index 8df23a8..45cd2cd 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -3,6 +3,16 @@ include <../std.scad> // Section: List Query Operations +module test_is_homogeneous(){ + assert(is_homogeneous([[1,["a"]], [2,["b"]]])==true); + assert(is_homogeneous([[1,["a"]], [2,[true]]])==false); + assert(is_homogeneous([[1,["a"]], [2,[true]]],1)==true); + assert(is_homogeneous([[1,["a"]], [2,[true]]],2)==false); + assert(is_homogeneous([[1,["a"]], [true,["b"]]])==true); +} +test_is_homogeneous(); + + module test_select() { l = [3,4,5,6,7,8,9]; assert(select(l, 5, 6) == [8,9]); @@ -46,7 +56,6 @@ test_in_list(); module test_min_index() { assert(min_index([5,3,9,6,2,7,8,2,1])==8); assert(min_index([5,3,9,6,2,7,8,2,7],all=true)==[4,7]); -// assert(min_index([],all=true)==[]); } test_min_index(); @@ -54,7 +63,6 @@ test_min_index(); module test_max_index() { assert(max_index([5,3,9,6,2,7,8,9,1])==2); assert(max_index([5,3,9,6,2,7,8,9,7],all=true)==[2,7]); -// assert(max_index([],all=true)==[]); } test_max_index(); @@ -74,6 +82,7 @@ module test_list_decreasing() { } test_list_decreasing(); + // Section: Basic List Generation module test_repeat() { @@ -155,7 +164,6 @@ module test_list_remove() { } test_list_remove(); - module test_list_remove_values() { animals = ["bat", "cat", "rat", "dog", "bat", "rat"]; assert(list_remove_values(animals, "rat") == ["bat","cat","dog","bat","rat"]); @@ -258,15 +266,24 @@ test_shuffle(); module test_sort() { assert(sort([7,3,9,4,3,1,8]) == [1,3,3,4,7,8,9]); - 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([[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 = ["d","b","e","c"]; + 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], @@ -276,11 +293,22 @@ module test_sortidx() { 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]]; + 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_unique() { assert(unique([]) == []); assert(unique([8]) == [8]); @@ -336,10 +364,8 @@ module test_set_intersection() { test_set_intersection(); - // Arrays - module test_add_scalar() { assert(add_scalar([1,2,3],3) == [4,5,6]); assert(add_scalar([[1,2,3],[3,4,5]],3) == [[4,5,6],[6,7,8]]); @@ -473,6 +499,12 @@ module test_array_dim() { assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0) == 2); assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2) == 3); assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]) == [2,undef,3]); + assert(array_dim([1,2,3,4,5,6,7,8,9]) == [9]); + assert(array_dim([[1],[2],[3],[4],[5],[6],[7],[8],[9]]) == [9,1]); + assert(array_dim([]) == [0]); + assert(array_dim([[]]) == [1,0]); + assert(array_dim([[],[]]) == [2,0]); + assert(array_dim([[],[1]]) == [2,undef]); } test_array_dim(); @@ -486,7 +518,4 @@ module test_transpose() { test_transpose(); -cube(); - - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap