-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Add delete and delete_by methods to Iterator #2475
Conversation
Does this occur frequently enough?
I would try to fit in the word
You can include a possible implementation. :) |
I think the name should be |
I'm nowhere near versed enough in Rust to be able to do that without a great deal of effort |
Yeah this seems like a better fit for |
Here is a proof of concept implementation: The generalization and optimization (
Perhaps you are both right; However, inclusion in std comes with the benefit of specialization. |
@Diggsey it was mostly to mirror the Haskell implementation, which both include a predicate and a value. fn delete<T: std::cmp::Eq>(&self, item: T) -> Self {
self.delete_by(item, |x, y| x == y)
} |
|
@clarcharr That's a Great name ❤️ |
`delete_by` would work like `delete`, but also take a binary predicate: | ||
```rust | ||
// `result` contains [1, 2, 3, 5, 6, 7, 8, 9], skipping 4 | ||
let result = (1 .. 10).delete_by(4, |x, y| x <= y); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd rather have delete_by(|x| x == 4)
than delete_by(4, |x, y| x <= y)
where 4
is always passed to y
. The current way looks very confusing to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using the example in the Haskell source code verbatim.
The example within uses <=
to show that you can use an operator different from ==
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, I think the signature should be:
fn delete_by<F>(self, f: F) where F: Fn(&Self::Item) -> bool { ... }
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
The `delete` method removes the first occurrence of its input from the iterator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be helpful to add a method signature for delete
. Perhaps:
fn delete<T>(self, x: &T) where T: PartialEq<Self::Item> { ... }
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Maybe `delete` sounds too destructive, could there be a better name? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in the comments, perhaps omit_first
and omit_first_by
might be good names? So, you'd have iter.omit_first(&4)
and iter.omit_first_by(|x| x < 4)
.
In response to everyone who says I took the inspiration for the structure directly from the Haskell original. That being said, since Rust doesn't support currying the way Haskell does, so the purpose of splitting the predicate and the queried value has likely been lost. |
I've refined my implementation further using Some possible optimizations are included as well as documentation and some notes on implementation details. |
|
||
As it stands, there is no _nice_ way of non-destructively removing a single element from an iterator. | ||
There might be a more general way of implementing it that could be more useful in general; however, | ||
as it stands simply using `filter`, `map`, or `fold` won't do the trick when wanting to afect a single item in an unkown spot in the iterator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filter
takes FnMut
, so one can absolutely do this with filter
. For example:
pub fn delete<T: PartialEq<U>, U>(
it: impl Iterator<Item = T>,
item: U,
) -> impl Iterator<Item = T> {
let mut found = false;
it.filter(move |x| {
if !found && x == &item {
found = true;
false
} else {
true
}
})
}
Or abstract out the inners into (using @Centril's Option trick to sometimes use less space)
pub fn not_first<T: Eq>(x: T) -> impl FnMut(&T) -> bool {
let mut needle = Some(x);
move |x| {
if Some(x) == needle.as_ref() {
needle = None;
false
} else {
true
}
}
}
And just use let result = outcomes.iter().filter(not_first(smallest));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice :) I wonder how this will perform compared to my more elaborate implementation, particularly wrt. size_hint
and the folds since we know more about the characteristics of a non-filter based implementation. Someone should bench this ^,-
If you are working with vectors, then you can use Vec::retain let mut vec = vec![1, 2, 3, 4];
vec.retain(|&x| x%2 == 0);
assert_eq!(vec, [2, 4]); |
@nielsle That's not what this method does; it only deletes the first occurrence, and would be closer to Anyway, as stated, this is just for iterators and not actually modifying underlying containers. |
At the risk of further bikeshedding on names, what about Or, as another possibility, what about |
@joshtriplett I think the problem with The idea to extend to n elements is interesting! The perf might be slightly worse, but it could be worth it. |
@Centril Good point, Given that, |
@joshtriplett |
To me, |
So what about the case where the user can specify their own predicate? Would that be |
@joshtriplett Good rationale. 👍 @ElectricCoffee Depends on whether you want to omit |
@Centril I meant in terms of naming, not in terms of features :) I wasn't sure if the predicate versions were out of the picture or not |
Is looking for things that don't match a required behavior, given that we can easily flip the meaning using the predicate ? There'd be little value in adding I'd say only implement a |
Iterators already have |
On June 20, 2018 2:29:46 AM PDT, vincentdephily ***@***.***> wrote:
> filter looks for things that match and this looks for things that
don't match. That does indeed make filter the wrong name.
Is looking for things that don't match a required behavior, given that
we can easily flip the meaning using the predicate ? There'd be little
value in adding `omit()` given that we already have `filter()`, and I
think that the same logic applies here : make this predicate positive
like the others in the API, so that the API is simpler to keep in your
head.
I'd say only implement a `filter_n()` method that takes a predicate and
a `usize`. I can use that for all the usecases, and because I know
`filter()` and recognize `foo_n()` I immediately know what `filter_n()`
does.
filter_n seems ambiguous. Does it limit the number of things that *pass* or that *fail*?
|
My preferred names for this:
That being said, what would this do for an array like: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 5, 6, 5, 6, 6 ]?
Would it be: [ 0, 0, 1, 1, 1, 3, 3, 4, 1, 1, 5, 5, 6, 5, 6, 6 ] or would it be: [ 0, 0, 1, 3, 3, 4, 1, 1, 5, 5, 6, 5, 6, 6 ] What about, if skip_n_while( 1, |x|x=5 )? Would it be: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 6, 6, 6 ] or would it be: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 6, 5, 6, 6 ] What about skip_n_( 2, |x|x=6 )? Would it be: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 5, 5 ] or would it be: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 5, 5, 6 ] or: [ 0, 0, 1, 2, 1, 2, 1, 3, 3, 4, 1, 1, 5, 5, 5, 6, 6 ] In other words, this whole operation seems very ad-hoc. If it is going to only delete the first n occurrences encountered that matches the predicate, regardless of whether or not they are consecutive, then, a name like:
would be better; however, if it will only skip the first n of the first group of consecutive entries that match the predicate, then a name like:
would be better. But, if it will delete the first n from each group of consecutive entries that match the predicate then a name like:
would be better. TL;DR
Still TL;DR
|
Using precedent: So, |
Thank you for the correction @clarcharr I think that fn main() {
let outcomes = vec![5, 2, 2, 6];
let smallest = outcomes.iter().enumerate()
.min_by(|(_,x1), (_, x2)| x1.cmp(x2))
.map(|(i, _)| i)
.unwrap();
let result: Vec<_> = outcomes.iter().enumerate()
.filter(|(i, _)| *i != smallest)
.map(|(_, x)| x)
.collect();
println!("{:?} {:?}", smallest, result);
// prints: 1 [5, 2, 6]
} |
Here is another version, that only performs one iteration let values = vec![1, 1, 2, 3, 4, 4, 5, 6];
let result: Vec<_> = values.iter()
.scan(false, |done, &x|
if *done == false && x == 4 {
*done = true;
Some(None) //
} else {
Some(Some(x))
}
)
.filter_map(|x| x)
.collect();
println!("{:?}", result);
// prints: [1, 1, 2, 3, 4, 5, 6] |
Maybe a bit late to the party, but anyways: The naming of these functions (as in the RFC) is completely non-explanatory. I would expect to delete everything that matches the predicate from the iterator - not good! Also, |
Feel free to come up with a better name, I took these from an existing language |
I'd suggest |
We discussed this proposal in the library api team meeting last week, and we felt that the use case for this addition is too niche to warrant another method on the already very large interface of @rfcbot fcp close |
Team member @m-ou-se has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now closed. |
This RFC requests the addition of two new methods to the
Iterator
trait, calleddelete
anddelete_by
.You can find the render [here].
Note, I didn't know what to put into the Reference-level explanation, as I believe the rest of the document explains the intent well enough.