Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: n-dimensional product over the same iterator #264

Open
lorepozo opened this issue Mar 3, 2018 · 9 comments · May be fixed by #286
Open

Feature: n-dimensional product over the same iterator #264

lorepozo opened this issue Mar 3, 2018 · 9 comments · May be fixed by #286

Comments

@lorepozo
Copy link
Contributor

lorepozo commented Mar 3, 2018

So that code like this:

iproduct!(0..4, 0..4, 0..4)

becomes the more succinct:

iproduct!(0..4; 3)

This would mandate vectors instead of tuples for the items.
It would help for when the dimension isn't known until runtime:

fn truth_table(dim: usize)Box<Iterator<Item = Vec<bool>>> {
    Box::new(iproduct!([false, true].iter(); dim))
}
@burdges
Copy link

burdges commented Mar 4, 2018

Is this so common as to warrant an iproduct! like convenience macro? It's easy enough to do this now with std::iter::repeat([fasle, true].iter()).take(dim).multi_cartesian_product() right?

@lorepozo
Copy link
Contributor Author

lorepozo commented Mar 4, 2018

Ah that's a nice solution, thanks! The multi_cartesian_product docs are coarse to read through.

Having this feature in the iproduct! macro could still be nice, but I don't stand by it as strongly after seeing your approach:

fn truth_table(dim: usize)Box<Iterator<Item = Vec<bool>>> {
    Box::new(iter::repeat(vec![false, true]).take(dim).multi_cartesian_product()
}

@bluss
Copy link
Member

bluss commented Mar 4, 2018

I wince a bit at all of this, since any product / flat_map or other "nested loop" iterator will have performance or code gen problems in Rust. But if that is not of primary concern, then it's ok. Demo of the difference is in #222 (comment) (where you can see that fold/for_each, representing internal iteration, don't have to have the problem.)

@bluss
Copy link
Member

bluss commented Mar 4, 2018

I don't mind adding that syntax to the macro, with the caveats that it has to be feasible to implement and that one thinks over what the duplicate syntax/ownership/cloning semantic is for the repetition.

@bluss
Copy link
Member

bluss commented Mar 4, 2018

Maybe I should throw in something really ridiculous and then we can think of what it should result in:

let mut i = 0;
iproduct!( { i += 1; i..10 }; 3);

Note that this might not be so ridiculous, the i += 1 can be replaced with a mutating method and its return value.

@tobz1000
Copy link
Contributor

multi_cartesian_product will have an order-of-magnitude worse performance than iproduct, so if the number of repeats is known at compile time (and small), and the code is at all performance-sensitive, iproduct should be preferred. And as @bluss says, a nested for-loop will be faster still.

I would like to volunteer myself for adding this syntax if work hasn't already started. I haven't done any macro work; this seems like a nice place to start.

@tobz1000 tobz1000 linked a pull request Jun 1, 2018 that will close this issue
@UltraMachine
Copy link

Is there any progress on that?

@akiradeveloper
Copy link

akiradeveloper commented Sep 27, 2021

I want this feature because multi_cartesian_product is too slow.

@Philippe-Cholet
Copy link
Member

This would be a nice and convenient addition.

First, the "repeat parameter" is given literally like iproduct!(it; 3), not iproduct!(it; n).
Then for something like iproduct!(0..4; 3) or let mut i = 0; iproduct!( { i += 1; i..10 }; 3); (cf bluss comment above), we would

($I:expr; 3) => {{
    let it = $crate::iproduct!($I); // into_iter
    $crate::iproduct!(it.clone(), it.clone(), it)
}};
// OR
($I:expr; 3) => (
    $crate::iproduct!($I.clone(), $I.clone(), $I)
);

Clone would not be needed by bluss example but is mandatory for simpler iproduct!(0..4; 3).

The question is when to evalute the expression, once or n times. The second would allow more situations than the first (cf bluss example: iproduct!(1..10, 1..10, 1..10) in the first case, iproduct!(1..10, 2..10, 3..10) in the second).

I think we should choose and document this behavior. And I don't know what's blocking this feature except this choice.

Anyway, both cases relies on cons_tuples which has a limitation of 12 types. So the repeat parameter would be literally given and between 1 and 12.

We currently can't do iproduct!(0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4, 0..4) as it's there 13 times so it does not bother me that we can't do iproduct!(0..4; 13) either.

Both full new patterns (click to expand)
macro_rules! iproduct {
    // current patterns ...
    ($I:expr; 1) => (
        $crate::iproduct!($I)
    );
    ($I:expr; 2) => (
        $crate::iproduct!($I.clone(), $I)
    );
    ($I:expr; 3) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I)
    );
    ($I:expr; 4) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 5) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 6) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 7) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 8) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 9) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 10) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 11) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    ($I:expr; 12) => (
        $crate::iproduct!($I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I.clone(), $I)
    );
    // **OR**
    ($I:expr; 1) => (
        $crate::iproduct!($I)
    );
    ($I:expr; 2) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it)
    }};
    ($I:expr; 3) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it)
    }};
    ($I:expr; 4) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 5) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 6) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 7) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 8) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 9) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 10) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 11) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
    ($I:expr; 12) => {{
        let it = $crate::iproduct!($I);
        $crate::iproduct!(it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it.clone(), it)
    }};
}

Because the first pattern allows more, I would choose it.

I don't see how the macro could be made shorter, maybe you do, but I don't see why it would be a problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants