 # Generate array of all combinations

I want to generate an array of 3 elements

``````[alpha, beta, gamma]
``````

Where each of the 3 elements `alpha, beta, gamma` is between `0` and `1`, with a step of `0.01` in each.

I noticed the Iterator::step_by is working with `integer` increment, so I can do something like:

``````    for i in (0..=10).step_by(5) {
println!("{}", i);
}
``````

But can not do something like:

``````    for i in (0..=1).step_by(0.01) {
println!("{}", i);
}
``````

I've the same code in `Julia lang` as below:

``````arr = [[α,β,γ] for α in 0:0.01:1 for β in 0:0.01:1 for γ in 0:0.01:1]
``````

How can I do the same in Rust?

The `step_by` method doesn't do what you think it does. What it does is: given any sequence, create a new sequence that skips items in a way such that it takes every `n`th item.

It doesn't make sense to talk about the 0.01th item in a sequence which is why it doesn't accept a float.

I recommend doing something like `(0..=100).map(|n| n as f64 / 100.0)`

2 Likes

Thanks, this solved the issue of `step by`, so that I can write:

``````   let alpha: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();
let beta: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();
let gamma: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();
``````

Now the issue of all combinations, to simplify it, let's say we have:

``````   let alpha = vec![1,2];
let beta = vec![4,8];
let gamma = vec![3,7];
``````

Then we are having 8 combinations as:

``````[1, 4, 3]
[1, 4, 7]
[1, 8, 3]
[1, 8, 7]
[2, 4, 3]
[2, 4, 7]
[2, 8, 3]
[2, 8, 7]
``````

How can I get this output with a rust code?

I was able to doit with the below code, but is this the way, or there is a better way:

``````fn main() {
let alpha = vec![1,2];
let beta = vec![4,8];
let gamma = vec![3,7];

let mut all: Vec<[&i32; 3]> = Vec::new();

for i in &alpha {
for j in &beta {
for k in &gamma {
all.push([i, j, k]);
}
}
}

println!("{:?}", all);

}
``````

Accordingly the answer of my question is, but is this the best code I can write for replacing the single line `Julia` code:

``````fn main() {

let alpha: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();
let beta: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();
let gamma: Vec<f64> = (0..=100).map(|n| n as f64 / 100.0).collect();

let mut all: Vec<[&f64; 3]> = Vec::new();

for i in &alpha {
for j in &beta {
for k in &gamma {
all.push([i, j, k]);
}
}
}

println!("{:?}", all.len());

}
``````
1 Like
``````
macro_rules! list {
(\$expr:expr; for \$x:tt in \$range:expr) => {{
let mut _a: Vec<_> = Vec::new();
for \$x in \$range {_a.push(\$expr);}
_a
}};
(\$expr:expr; for \$x:tt in \$xrange:expr;
for \$y:tt in \$yrange:expr
) => {{
let mut _a: Vec<_> = Vec::new();
for \$x in \$xrange {
for \$y in \$yrange {_a.push(\$expr)};
}
_a
}};
(\$expr:expr; for \$x:tt in \$xrange:expr;
for \$y:tt in \$yrange:expr; for \$z:tt in \$zrange:expr
) => {{
let mut _a: Vec<_> = Vec::new();
for \$x in \$xrange {
for \$y in \$yrange {
for \$z in \$zrange {_a.push(\$expr)};
}
}
_a
}};
}

pub fn range(start: f64, end: f64, step: f64) -> Vec<f64> {
let mut buffer: Vec<f64> = Vec::new();
let mut x = start;
while x<end {
buffer.push(x);
x += step;
}
return buffer;
}

fn main() {
let arr = list![[alpha,beta,gamma];
for alpha in range(0.0,1.0,0.01);
for beta  in range(0.0,1.0,0.01);
for gamma in range(0.0,1.0,0.01)];
println!("{:?}",arr);
}
``````
1 Like

As far as I can tell, optimizations may be performed as follows:

``````macro_rules! list {
(\$expr:expr; for \$x:tt in \$range:expr) => {{
let _r = \$range;
let mut _a: Vec<_> = Vec::with_capacity(_r.size_hint().0);
for \$x in _r {_a.push(\$expr);}
_a
}};
}

pub struct F64Range {
x: f64, step: f64, count: usize, len: usize
}

impl Iterator for F64Range {
type Item = f64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let rv = self.x; self.x += self.step; self.count += 1;
if self.count < self.len {Some(rv)} else {None}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len-self.count, Some(self.len-self.count))
}
}

pub fn range(start: f64, end: f64, step: f64) -> F64Range {
let len: usize = ((end-start)/step) as usize+1;
F64Range{x: start, step, count: 0, len}
}
``````
1 Like

I recommend just using three for loops. There are crates that supply functions for trying combinations, but in this case it's much simpler to just make three for loops.

2 Likes

The nevertheless desirable general variadic implementation of the macro can be achieved with this method "TT muncher". That turned out to work pretty well:

``````macro_rules! expand_tail {
(\$expr:tt; \$v:tt; for \$x:tt in \$range:expr) => {
for \$x in \$range {\$v.push(\$expr);}
};
(\$expr:tt; \$v:tt; for \$x:tt in \$range:expr; \$(\$tail:tt)*) => {
for \$x in \$range {expand_tail!(\$expr; \$v; \$(\$tail)*)}
};
}

macro_rules! list {
(\$expr:expr; \$(\$tail:tt)*) => {{
let mut _a = Vec::new();
expand_tail!{\$expr; _a; \$(\$tail)*}
_a
}};
}

fn main() {
let arr = list![[alpha,beta,gamma];
for alpha in 0..2; for beta in 0..2; for gamma in 0..2];
println!("{:?}",arr);
}
``````
1 Like

Here's a more declarative way to do this with itertools:

``````use itertools::Itertools;

use std::iter::once;

fn main() {
// closure to generate a new iterator as needed without using intermediate Vecs
let range = || (0..=2).map(|n| n as f64 / 2.0);

let all: Vec<Vec<f64>> = once(range())
.chain(once(range()))
.chain(once(range()))
.multi_cartesian_product()
.collect();

println!("{:?}", all);
}
``````

For Integers you could also have used `num_iter` for the range generation: http://rust-num.github.io/num/num_iter/fn.range_step_inclusive.html

That doesn't work with floats, though, seemingly for good reason. See: https://stackoverflow.com/a/47868114

3 Likes

Truth be told, I agree with @alice in that I find your solution by far the most obvious and readable, though I would have initialized with `Vec::with_capacity(alpha.len() * beta.len() * gamma.len());`.

Not much shorter than yours is the curiosity below which uses no macros or external crates; but honestly, I still prefer yours. An interesting point I experienced while trying to navigate confusing lifetime errors: we need the `move` to move `a`, `b`, and `c` in by value (hence the `move`), but we want to still need to capture `alpha`, `beta`, and `gamma` by reference, which is why I defined them as such so that `move` captures these references instead of the vectors.

``````let alpha = &vec![1,2];
let beta = &vec![4,8];
let gamma = &vec![3,7];

let all =
alpha.iter().flat_map(move |&a|
beta.iter().flat_map(move |&b|
gamma.iter().map(move |&c| [a, b, c])))
.collect::<Vec<_>>();
``````
2 Likes

Indeed true, yet we can use mathematical knowledge to determine the length of the range in before, including some tweaking. Given the range `[a,b)` we have `a + k*step < b` for the last index `k`. Solving the inequality for `k` results in

``````k < (b-a)/step    (step>0)
``````

Now let

``````n := ⌊(b-a)/step⌋.
``````

By definition of the floor function the inequality `⌊x⌋ ≤ x` holds. Thus forcing `k < n` will satisfy the original inequality.

Let's now consider the range `[0,1)` with step `0.1` and numbers with some small error due to limited precision. By the nature of the floor function there are discontinuities, and they turn out to be at unfortunate points. If `step` is slightly larger than `0.1` or `b` is slightly smaller than `1`, the number `n` jumps from `10` to `9`. However, this problem may be averted be tweaking the computation to be

``````n := ⌊(b-a)/step+0.5⌋.
``````

This isn't really a surprise, as `round(x):=⌊|x|+0.5⌋` is the standard (naive) method of rounding.

But still be warned if you want the length to have a fixed value in every situation. Then it's better to use the concept of `numpy.linspace`.

1 Like