[Note: Requires version 2015.03]
The list comprehensions provide a flexible way to generate lists using the general syntax
[ list-definition expression ]
The following elements are supported to construct the list definition
[Note: Requires version 2019.05]
The list comprehension syntax is generalized to allow multiple expressions. This allows to easily construct lists from multiple sub lists generated by different list comprehension expressions avoiding concat.
steps = 50; points = [ // first expression generating the points in the positive Y quadrant for (a = [0 : steps]) [ a, 10 * sin(a * 360 / steps) + 10 ], // second expression generating the points in the negative Y quadrant for (a = [steps : -1 : 0]) [ a, 10 * cos(a * 360 / steps) - 20 ], // additional list of fixed points [ 10, -3 ], [ 3, 0 ], [ 10, 3 ] ]; polygon(points);
The for element defines the input values for the list generation. The syntax is the same as used by the for iterator. The sequence to the right of the equals sign can be any list. The for element iterates over all the members of the list. The variable on the left of the equals sign take on the value of each member of the sequence in turn. This value can then be processed in the child of the for element, and each result becomes a member of the final list that is produced.
If the sequence has more than one dimension, for iterates over the first dimension only. Deeper dimensions can be accessed by nesting for elements.
Several common usage patterns are presented here.
Examples
// generate a list with all values defined by a range list1 = [ for (i = [0 : 2 : 10]) i ]; echo(list1); // ECHO: [0, 2, 4, 6, 8, 10]
// extract every second character of a string str = "SomeText"; list2 = [ for (i = [0 : 2 : len(str) - 1]) str[i] ]; echo(list2); // ECHO: ["S", "m", "T", "x"]
// indexed list access, using function to map input values to output values function func(x) = x < 1 ? 0 : x + func(x - 1); input = [1, 3, 5, 8]; output = [for (a = [ 0 : len(input) - 1 ]) func(input[a]) ]; echo(output); // ECHO: [1, 6, 15, 36]
Examples
// iterate over an existing list friends = ["John", "Mary", "Alice", "Bob"]; list = [ for (i = friends) len(i)]; echo(list); // ECHO: [4, 4, 5, 3]
// map input list to output list list = [ for (i = [2, 3, 5, 7, 11]) i * i ]; echo(list); // ECHO: [4, 9, 25, 49, 121]
// calculate Fibonacci numbers function func(x) = x < 3 ? 1 : func(x - 1) + func(x - 2); input = [7, 10, 12]; output = [for (a = input) func(a) ]; echo(output); // ECHO: [13, 55, 144]
[Note: Requires version 2019.05]
Examples
echo([ for (c = "String") c ]); // ECHO: ["S", "t", "r", "i", "n", "g"]
[Note: Requires version 2019.05]
The recursive equivalent of this generator is
function f(a, b, ...) = condition ? concat([expr], f(nexta, nextb, ...)) : []; f(inita, initb, ...)
Examples
echo( [for (a = 0, b = 1;a < 5;a = a + 1, b = b + 2) [ a, b * b ] ] ); // ECHO: [[0, 1], [1, 9], [2, 25], [3, 49], [4, 81]] // Generate fibonacci sequence echo([for (a = 0, b = 1;a < 1000;x = a + b, a = b, b = x) a]); // ECHO: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
[Note: Requires version 2019.05]
each embeds the values of a list given as argument directly, effectively unwrapping the argument list.
// Without using "each", a nested list is generated echo([ for (a = [1 : 4]) [a, a * a] ]); // ECHO: [[1, 1], [2, 4], [3, 9], [4, 16]] // Adding "each" unwraps the inner list, producing a flat list as result echo([ for (a = [1 : 4]) each [a, a * a] ]); // ECHO: [1, 1, 2, 4, 3, 9, 4, 16]
each unwraps ranges and helps to build more general for lists when combined with multiple generator expressions.
A = [-2, each [1:2:5], each [6:-2:0], -1]; echo(A); // ECHO: [-2, 1, 3, 5, 6, 4, 2, 0, -1] echo([ for (a = A) 2 * a ]); // ECHO: [-4, 2, 6, 10, 12, 8, 4, 0, -2]
The if element allows selection if the expression should be allocated and added to the result list or not. In the simplest case this allows filtering of an list.
Example
list = [ for (a = [ 1 : 8 ]) if (a % 2 == 0) a ]; echo(list); // ECHO: [2, 4, 6, 8]
Note that the if element cannot be inside an expression, it should be at the top.
Example
// from the input list include all positive odd numbers // and also all even number divided by 2 list = [-10:5]; echo([for(n=list) if(n%2==0 || n>=0) n%2==0 ? n/2 : n ]); // ECHO: [-5, -4, -3, -2, -1, 0, 1, 1, 3, 2, 5] // echo([for(n=list) n%2==0 ? n/2 : if(n>=0) n ]); // this would generate a syntactical error
[Note: Requires version 2019.05]
The if-else construct is equivalent to the conditional expression ?: except that it can be combined with filter if.
// even numbers are halved, positive odd numbers are preserved, negative odd numbers are eliminated echo([for (a = [-3:5]) if (a % 2 == 0) [a, a/2] else if (a > 0) [a, a] ]); // ECHO: [[-2, -1], [0, 0], [1, 1], [2, 1], [3, 3], [4, 2], [5, 5]];
Note that in the expression above the conditional operator could not substitute if-else. It is possible to express this same filter with the conditional operator but with a more cryptic logic:
// even numbers are halved, positive odd numbers are preserved, negative odd numbers are eliminated echo([for (a = [-3:5]) if (a % 2 == 0 || (a % 2 != 0 && a > 0)) a % 2 == 0 ? [a, a / 2] : [a, a] ]); // ECHO: [[-2, -1], [0, 0], [1, 1], [2, 1], [3, 3], [4, 2], [5, 5]];
To bind an else expression to a specific if, it's possible to use parenthesis.
// even numbers are dropped, multiples of 4 are substituted by -1 echo([for(i=[0:10]) if(i%2==0) (if(i%4==0) -1 ) else i]); // ECHO: [-1, 1, 3, -1, 5, 7, -1, 9] // odd numbers are dropped, multiples of 4 are substituted by -1 echo([for(i=[0:10]) if(i%2==0) if(i%4==0) -1 else i]); // ECHO: [-1, 2, -1, 6, -1, 10]
The let element allows sequential assignment of variables inside a list comprehension definition.
Example
list = [ for (a = [ 1 : 4 ]) let (b = a*a, c = 2 * b) [ a, b, c ] ]; echo(list); // ECHO: [[1, 1, 2], [2, 4, 8], [3, 9, 18], [4, 16, 32]]
There are different ways to define nested loops. Defining multiple loop variables inside one for element and multiple for elements produce both flat result lists. To generate nested result lists an additional [ ] markup is required.
// nested loop using multiple variables flat_result1 = [ for (a = [ 0 : 2 ], b = [ 0 : 2 ]) a == b ? 1 : 0 ]; echo(flat_result1); // ECHO: [1, 0, 0, 0, 1, 0, 0, 0, 1]
// nested loop using multiple for elements flat_result2 = [ for (a = [ 0 : 2 ]) for (b = [0 : 2]) a == b ? 1 : 0 ]; echo(flat_result2); // ECHO: [1, 0, 0, 0, 1, 0, 0, 0, 1]
// nested loop to generate a bi-dimensional matrix identity_matrix = [ for (a = [ 0 : 2 ]) [ for (b = [ 0 : 2 ]) a == b ? 1 : 0 ] ]; echo(identity_matrix); // ECHO: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
This chapter lists some advanced examples, useful idioms and use-cases for the list comprehension syntax.
Using list comprehension, a parametric equation can be calculated at a number of points to approximate many curves, such as the following example for an ellipse (using polygon()):
sma = 20; // semi-minor axis smb = 30; // semi-major axis polygon( [ for (a = [0 : 5 : 359]) [ sma * sin(a), smb * cos(a) ] ] );
List comprehension can be used in a user-defined function to perform tasks on or for vectors. Here is a user-defined function that flattens a nested vector.
// input : nested list // output : list with the outer level nesting removed function flatten(l) = [ for (a = l) for (b = a) b ] ; nested_list = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]; echo(flatten(nested_list)); // ECHO: [1, 2, 3, 4, 5, 6]
Even a complicated algorithm Quicksort becomes doable with for(), if(), let() and recursion:
// input : list of numbers // output : sorted list of numbers function quicksort(arr) = !(len(arr)>0) ? [] : 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( quicksort(lesser), equal, quicksort(greater) ); // use seed in rands() to get reproducible results unsorted = [for (a = rands(0, 10, 6, 3)) ceil(a)]; echo(unsorted); // ECHO: [6, 1, 8, 9, 3, 2] echo(quicksort(unsorted)); // ECHO: [1, 2, 3, 6, 8, 9]
select() performs selection and reordering of elements into a new vector.
function select(vector, indices) = [ for (index = indices) vector[index] ]; vector1 = [[0,0],[1,1],[2,2],[3,3],[4,4]]; selector1 = [4,0,3]; vector2 = select(vector1,selector1); // [[4, 4], [0, 0], [3, 3]] vector3 = select(vector1,[0,2,4,4,2,0]);// [[0, 0], [2, 2], [4, 4],[4, 4], [2, 2], [0, 0]] // range also works as indices vector4 = select(vector1, [4:-1:0]); // [[4, 4], [3, 3], [2, 2], [1, 1], [0, 0]]
Using indices:
function cat(L1, L2) = [for (i=[0:len(L1)+len(L2)-1]) i < len(L1)? L1[i] : L2[i-len(L1)]] ; echo(cat([1,2,3],[4,5])); //concatenates two OpenSCAD lists [1,2,3] and [4,5], giving [1, 2, 3, 4, 5]
Without using indices:
function cat(L1, L2) = [for(L=[L1, L2], a=L) a]; echo(cat([1,2,3],[4,5])); //concatenates two OpenSCAD lists [1,2,3] and [4,5], giving [1, 2, 3, 4, 5]