Open fritzrehde opened 10 months ago
I'm doing AoC as well and I sure do +1
as much as anybody. But do that would clash with core::iter::Iterator::enumerate
which we won't do here. Obviously, we could change the name to enumerate1
(just an example). It's nice to have shortcuts for AoC but this is a such trivial shortcut that I don't think we would do it.
But what you can do is your own super-trait of Iterator in your utilities, something like
trait IteratorExt: Iterator {
fn enumerate1(self) -> std::iter::Map<std::iter::Enumerate<Self>, fn((usize, Self::Item)) -> (usize, Self::Item)>
where
Self: Sized,
{
self.enumerate().map(|(idx, item)| (idx + 1, item))
}
// EDIT: There is zip I forgot:
fn enumerate_n(self, n: usize) -> std::iter::Zip<std::ops::RangeFrom<usize>, Self>
where
Self: Sized,
{
(n..).zip(self)
}
}
impl<I: Iterator> IteratorExt for I {}
Then use my_utils::IteratorExt;
for your solvers.
~Make a method enumerate_n(n: usize)
does not seem to be that simple though.~
EDIT2: The other alternative is simply .zip(1..)
(but the tuple argument is reversed) instead of a custom enumerate_n(1)
.
This is a neat idea! You can even make it a little more generic:
use core::ops::RangeFrom;
pub fn itemize<I, A>(iter: I, mut start: RangeFrom<A>) -> impl Iterator<Item = (A, I::Item)>
where
I: Iterator,
A: 'static,
RangeFrom<A>: Iterator<Item = A>,
{
iter.map(move |e| (start.next().unwrap(), e))
}
...allowing calls like itemize(iter, 'a'..)
. It's too bad the Step
trait isn't stable — that would allow even more flexibility.
That's like start.zip(self)
(I therefore edited my previous comment).
The downside of your function is that we can't put it in a trait because of the opaque type of the function.
I'm going to go ahead and add this to itertools. The RangeFrom
formulation is compellingly powerful, and the Zip
return type means it has almost no maintenance burden.
I'm not sure but I feel like a "zip" version would be less efficient than .enumerate.map
?
I'm doing AoC as well and I sure do
+1
as much as anybody. But do that would clash withcore::iter::Iterator::enumerate
which we won't do here. Obviously, we could change the name toenumerate1
(just an example). It's nice to have shortcuts for AoC but this is a such trivial shortcut that I don't think we would do it.But what you can do is your own super-trait of Iterator in your utilities, something like
trait IteratorExt: Iterator { fn enumerate1(self) -> std::iter::Map<std::iter::Enumerate<Self>, fn((usize, Self::Item)) -> (usize, Self::Item)> where Self: Sized, { self.enumerate().map(|(idx, item)| (idx + 1, item)) } // EDIT: There is zip I forgot: fn enumerate_n(self, n: usize) -> std::iter::Zip<std::ops::RangeFrom<usize>, Self> where Self: Sized, { (n..).zip(self) } } impl<I: Iterator> IteratorExt for I {}
Then
use my_utils::IteratorExt;
for your solvers.~Make a method
enumerate_n(n: usize)
does not seem to be that simple though.~EDIT2: The other alternative is simply
.zip(1..)
(but the tuple argument is reversed) instead of a customenumerate_n(1)
.
Just to clarify, obviously it shouldn't be named .enumerate()
to conflict with the std lib implementation. I thought of .enumerate_from(n)
, .enumerate_from_index(n)
, .enumerate_starting_from_index(n)
. But not sure what kind of preferences you have on verbosity of method names.
I looked at Enumerate<I>
itself in the std lib, and it's a simple:
pub struct Enumerate<I> {
iter: I,
count: usize,
}
impl<I> Enumerate<I> {
pub(in crate::iter) fn new(iter: I) -> Enumerate<I> {
Enumerate { iter, count: 0 }
}
}
So implementation there would obviously be trivial, just allow starting from count 1. I am also kind of proposing adding this feature in the first place because of very slight efficiency concerns. Of course:
.enumerate()
// The rank starts at 1, not 0.
.map(|(i, hand)| (i + 1, hand))
is just a simple addition, which should't cost much performance, but it makes it a tiny bit less readable and also probably is a tiny bit slower. I don't really see any downsides to adding this to itertools (or stdlib, but that probably wouldn't happen, right?), since implementation should be fairly straightforward and maintainable, right?
Do you think I should make this feature request to the official rust std lib instead? Like detailed in my last message, I think it would be very easy to implement this in the std lib, and it would have zero overhead (no extra zip
or map
).
Well they added enumerate
and some of them probably thought of an alternative start than 0 so I suspect they would not be interested, but it's only a guess.
Coming back to this, I see that (n..).zip(self)
would not be ExactSizeIterator
even self
is because n..
is not. ~But it would with n..=usize::MAX
, except it might be shorter than self
though in some cases.~ (EDIT: too fast, no such implementation) It's so much simpler when n == 0
.
I am currently solving Advent of Code day 07 part 1, and wrote this code:
I needed to give each iterator element a
rank
, starting at1
. Therefore, the only hacky solution I came up with was to.enumerate()
as normal, and then increment each indexi
to get the rank. I think the code would a little cleaner/idiomatic if I could write:Would it be possible to implement something like that in itertools? Thanks for the awesome crate!