From d7cb503ffc05a48fe58c995adacf260387da40a2 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 25 Mar 2021 00:23:36 -0700 Subject: [PATCH] Replace most trivial uses of slice() with faster list_head() and list_tail() --- arrays.scad | 57 ++++++++++++++++++++++++++++++++++++------ beziers.scad | 4 +-- distributors.scad | 16 ++++++------ math.scad | 5 ++-- paths.scad | 10 ++++---- regions.scad | 2 +- rounding.scad | 2 +- shapes2d.scad | 6 ++--- tests/test_arrays.scad | 51 ++++++++++++++++++++++++++----------- turtle3d.scad | 10 ++++---- 10 files changed, 112 insertions(+), 51 deletions(-) diff --git a/arrays.scad b/arrays.scad index 3fd8bbc..e4de121 100644 --- a/arrays.scad +++ b/arrays.scad @@ -142,18 +142,59 @@ function last(list) = list[len(list)-1]; -// Function: delete_last() +// Function: list_head() // Usage: -// list = delete_last(list); +// list = list_head(list,); // Topics: List Handling -// See Also: select(), slice(), subindex(), last() +// See Also: select(), slice(), list_tail(), last() // Description: -// Returns a list with all but the last entry from the input list. If input is empty, returns empty list. -// Example: -// nlist = delete_last(["foo", "bar", "baz"]); // Returns: ["foo", "bar"] -function delete_last(list) = +// 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. +// Examples: +// hlist = list_head(["foo", "bar", "baz"]); // Returns: ["foo", "bar"] +// hlist = list_head(["foo", "bar", "baz"], -3); // Returns: ["foo"] +// hlist = list_head(["foo", "bar", "baz"], 2); // Returns: ["foo","bar"] +// hlist = list_head(["foo", "bar", "baz"], -5); // Returns: [] +// hlist = list_head(["foo", "bar", "baz"], 5); // Returns: ["foo","bar","baz"] +function list_head(list, to=-2) = assert(is_list(list)) - list==[] ? [] : slice(list,0,-2); + assert(is_finite(to)) + to<0? [for (i=[0:1:len(list)+to]) list[i]] : + to); +// Topics: List Handling +// See Also: select(), slice(), list_tail(), last() +// Description: +// Returns the tail of the given list, from the `from` index up until the end of the list, inclusive. +// If the `from` 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 you want it to return the last three items of the list, use `from=-3`. +// Arguments: +// list = The list to get the tail of. +// from = The first index to include. If negative, adds the list length to it. ie: -1 is the last list item. +// Examples: +// tlist = list_tail(["foo", "bar", "baz"]); // Returns: ["bar", "baz"] +// tlist = list_tail(["foo", "bar", "baz"], -1); // Returns: ["baz"] +// tlist = list_tail(["foo", "bar", "baz"], 2); // Returns: ["baz"] +// tlist = list_tail(["foo", "bar", "baz"], -5); // Returns: ["foo","bar","baz"] +// tlist = list_tail(["foo", "bar", "baz"], 5); // Returns: [] +function list_tail(list, from=1) = + assert(is_list(list)) + assert(is_finite(from)) + from>=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: force_list() diff --git a/beziers.scad b/beziers.scad index 2a74ac3..79fe650 100644 --- a/beziers.scad +++ b/beziers.scad @@ -574,7 +574,7 @@ function bezier_line_intersection(curve, line) = // p2 = [30, 30]; // trace_path([p0,p1,p2], showpts=true, size=0.5, color="green"); // fbez = fillet3pts(p0,p1,p2, 10); -// trace_bezier(slice(fbez, 1, -2), size=1); +// trace_bezier(select(fbez,1,-2), size=1); function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let( r = get_radius(r=r,d=d), v0 = unit(p0-p1), @@ -942,7 +942,7 @@ module bezier_polygon(bezier, splinesteps=16, N=3) { assert(is_int(splinesteps)); assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); polypoints=bezier_path(bezier, splinesteps, N); - polygon(points=slice(polypoints, 0, -1)); + polygon(points=polypoints); } diff --git a/distributors.scad b/distributors.scad index 1710f03..fde784d 100644 --- a/distributors.scad +++ b/distributors.scad @@ -294,9 +294,9 @@ module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -339,9 +339,9 @@ module xdistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -384,9 +384,9 @@ module ydistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -429,9 +429,9 @@ module zdistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } diff --git a/math.scad b/math.scad index bf676e4..c63efb8 100644 --- a/math.scad +++ b/math.scad @@ -544,9 +544,8 @@ function _lcm(a,b) = // Computes lcm for a list of values function _lcmlist(a) = - len(a)==1 - ? a[0] - : _lcmlist(concat(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])])); + len(a)==1 ? a[0] : + _lcmlist(concat(lcm(a[0],a[1]),list_tail(a,2))); // Function: lcm() diff --git a/paths.scad b/paths.scad index 5d391c3..036e008 100644 --- a/paths.scad +++ b/paths.scad @@ -878,14 +878,14 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, let( // Found fragment intersects with initial path hitidx = select(hits,-1), - newpath = slice(path,0,hitidx+1), + newpath = list_head(path,0,hitidx), newfrags = concat(len(newpath)>1? [newpath] : [], remainder), outpath = concat(slice(path,hitidx,-2), foundfrag) ) [outpath, newfrags] ) : let( // Path still incomplete. Continue building it. - newpath = concat(path, slice(foundfrag, 1, -1)), + newpath = concat(path, list_tail(foundfrag)), newfrags = concat([newpath], remainder) ) assemble_a_path_from_fragments( @@ -1244,7 +1244,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals list_range(s=sp, step=spacing, e=length) ) : is_def(n) && is_undef(spacing)? ( closed? - let(range=list_range(s=0,e=length, n=n+1)) slice(range,0,-2) : + let(range=list_range(s=0,e=length, n=n+1)) list_head(range) : list_range(s=0, e=length, n=n) ) : ( let( @@ -1426,14 +1426,14 @@ function path_cut(path,cutdist,closed) = cuts = len(cutlist) ) [ - [ each slice(path,0,cutlist[0][1]), + [ each list_head(path,cutlist[0][1]-1), if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0] ], for(i=[0:1:cuts-2]) cutlist[i][0]==cutlist[i+1][0] ? [] : [ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0], - each slice(path,cutlist[i][1], cutlist[i+1][1]), + each slice(path, cutlist[i][1], cutlist[i+1][1]), if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0], ], [ diff --git a/regions.scad b/regions.scad index b4befba..8b5ca6b 100644 --- a/regions.scad +++ b/regions.scad @@ -88,7 +88,7 @@ function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") = is_list(valid_dim) ? str("one of ",valid_dim) : valid_dim ) ) - closed && approx(path[0],select(path,-1))? slice(path,0,-2) : path; + closed && approx(path[0], last(path))? list_head(path) : path; // Function: cleanup_region() diff --git a/rounding.scad b/rounding.scad index afebedb..cf95e7b 100644 --- a/rounding.scad +++ b/rounding.scad @@ -413,7 +413,7 @@ function _rounding_offsets(edgespec,z_dir=1) = assert(argsOK,str("Invalid specification with type ",edgetype)) let( offsets = - edgetype == "profile"? scale([-1,z_dir], p=slice(points,1,-1)) : + edgetype == "profile"? scale([-1,z_dir], p=list_tail(points)) : edgetype == "chamfer"? chamf_width==0 && chamf_height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] : edgetype == "teardrop"? ( radius==0? [] : concat( diff --git a/shapes2d.scad b/shapes2d.scad index 16cd306..34f92ae 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -533,7 +533,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) = assert(is_bool(endpoint)) !endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true") - slice(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true),0,-2) : + list_head(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) : assert(is_undef(N) || is_integer(N), "Number of points must be an integer") // First try for 2D arc specified by width and thickness is_def(width) && is_def(thickness)? ( @@ -851,7 +851,7 @@ function _turtle_command(command, parm, parm2, state, index) = ) list_set( state, [path,step], [ - concat(state[path], slice(arcpath,1,-1)), + concat(state[path], list_tail(arcpath)), rot(lrsign * myangle,p=state[step],planar=true) ] ) : @@ -877,7 +877,7 @@ function _turtle_command(command, parm, parm2, state, index) = ) list_set( state, [path,step], [ - concat(state[path], slice(arcpath,1,-1)), + concat(state[path], list_tail(arcpath)), rot(delta_angle,p=state[step],planar=true) ] ) : diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index c8f86d0..5b460e2 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -27,21 +27,6 @@ module test_select() { } test_select(); -module test_last() { - list = [1,2,3,4]; - assert(last(list)==4); - assert(last([])==undef); -} -test_last(); - -module test_delete_last() { - list = [1,2,3,4]; - assert(delete_last(list) == [1,2,3]); - assert(delete_last([1]) == []); - assert(delete_last([]) == []); -} -test_delete_last(); - module test_slice() { assert(slice([3,4,5,6,7,8,9], 3, 5) == [6,7]); @@ -54,6 +39,42 @@ module test_slice() { test_slice(); +module test_last() { + list = [1,2,3,4]; + assert(last(list)==4); + assert(last([])==undef); +} +test_last(); + + +module test_list_head() { + list = [1,2,3,4]; + assert_equal(list_head(list), [1,2,3]); + assert_equal(list_head([1]), []); + assert_equal(list_head([]), []); + assert_equal(list_head(list,-3), [1,2]); + assert_equal(list_head(list,1), [1,2]); + assert_equal(list_head(list,2), [1,2,3]); + assert_equal(list_head(list,6), [1,2,3,4]); + assert_equal(list_head(list,-6), []); +} +test_list_head(); + + +module test_list_tail() { + list = [1,2,3,4]; + assert_equal(list_tail(list), [2,3,4]); + assert_equal(list_tail([1]), []); + assert_equal(list_tail([]), []); + assert_equal(list_tail(list,-3), [2,3,4]); + assert_equal(list_tail(list,2), [3,4]); + assert_equal(list_tail(list,3), [4]); + assert_equal(list_tail(list,6), []); + assert_equal(list_tail(list,-6), [1,2,3,4]); +} +test_list_tail(); + + module test_in_list() { assert(in_list("bar", ["foo", "bar", "baz"])); assert(!in_list("bee", ["foo", "bar", "baz"])); diff --git a/turtle3d.scad b/turtle3d.scad index 8f9176d..c0631d0 100644 --- a/turtle3d.scad +++ b/turtle3d.scad @@ -540,28 +540,28 @@ function _turtle3d_command(command, parm, parm2, state, index) = command=="addlength" ? list_set(state, movestep, state[movestep]+parm) : command=="arcsteps" ? assert(is_int(parm) && parm>0, str("\"",command,"\" requires a postive integer argument at index ",index)) list_set(state, arcsteps, parm) : - command=="roll" ? list_set(state, trlist, concat(slice(state[trlist],0,-2), [lastT*xrot(parm)])): + command=="roll" ? list_set(state, trlist, concat(list_head(state[trlist]), [lastT*xrot(parm)])): in_list(command,["right","left","up","down"]) ? - list_set(state, trlist, concat(slice(state[trlist],0,-2), [lastT*_turtle3d_rotation(command,default(parm,state[angle]))])): + list_set(state, trlist, concat(list_head(state[trlist]), [lastT*_turtle3d_rotation(command,default(parm,state[angle]))])): in_list(command,["xrot","yrot","zrot"]) ? let( Trot = _rotpart(lastT), // Extract rotational part of lastT shift = _transpart(lastT) // Translation part of lastT ) - list_set(state, trlist, concat(slice(state[trlist],0,-2), + list_set(state, trlist, concat(list_head(state[trlist]), [move(shift)*_turtle3d_rotation(command,default(parm,state[angle])) * Trot])): command=="rot" ? let( Trot = _rotpart(lastT), // Extract rotational part of lastT shift = _transpart(lastT) // Translation part of lastT ) - list_set(state, trlist, concat(slice(state[trlist],0,-2),[move(shift) * parm * Trot])): + list_set(state, trlist, concat(list_head(state[trlist]),[move(shift) * parm * Trot])): command=="setdir" ? let( Trot = _rotpart(lastT), shift = _transpart(lastT) ) - list_set(state, trlist, concat(slice(state[trlist],0,-2), + list_set(state, trlist, concat(list_head(state[trlist]), [move(shift)*rot(from=apply(Trot,RIGHT),to=parm) * Trot ])): in_list(command,["arcleft","arcright","arcup","arcdown"]) ? assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))