List Comprehensions

[Note: Requires version 2015.03]

Basic Syntax

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

for (i = sequence)
Iteration over a range or an existing list
for (init;condition;next)
Simple recursive call represented as C-style for
each
Takes a sequence value as argument, and adds each element to the list being constructed. each x is equivalent to `for (i = x) i`
if (condition)
Selection criteria, when true the expression is calculated and added to the result list
let (x = value)
Local variable assignment

multiple generator expressions

[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);

for

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.

[ for (i = [start : step : end]) i ]
Generate output based on a range definition, this version is mainly useful to calculate list values or access existing lists using the range value as index.

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]
[ for (i = [a, b, c, ...]) i ]
Use list parameter as input, this version can be used to map input values to calculated output values.

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]
[ for (c = "String") c ]
Generate output based on a string, this iterates over each character of the string.

[Note: Requires version 2019.05]

Examples

echo([ for (c = "String") c ]);
// ECHO: ["S", "t", "r", "i", "n", "g"]
[ for (a = inita, b = initb, ...;condition;a = nexta, b = nextb, ...) expr ]
Generator for expressing simple recursive call in a c-style for loop.

[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]

each

[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]

if

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.

[ for (i = list) if (condition(i)) i ]
When the evaluation of the condition returns true, the expression i is added to the result 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

if/else

[Note: Requires version 2019.05]

The if-else construct is equivalent to the conditional expression ?: except that it can be combined with filter if.

[ for (i = list) if (condition(i)) x else y ]
When the evaluation of the condition returns true, the expression x is added to the result list else the expression y.
// 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]

let

The let element allows sequential assignment of variables inside a list comprehension definition.

[ for (i = list) let (assignments) a ]

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]]

Nested loops

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]]


Advanced Examples

This chapter lists some advanced examples, useful idioms and use-cases for the list comprehension syntax.

Generating vertices for a polygon

Result

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) ] ]
);


Flattening a nested vector

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]

Sorting a vector

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]

Selecting elements of a vector

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]]

Concatenating two vectors

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]