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

Add dedup, dedup_by and dedup_by_key to the Iterator trait #83748

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6d4e429
Impl dedup
slerpyyy Apr 1, 2021
bf425a0
Update issue in unstable attributes
slerpyyy Apr 1, 2021
d59eb00
Ran cargo fmt
slerpyyy Apr 1, 2021
5e24347
Added feature flag to doc tests
slerpyyy Apr 1, 2021
ca137db
Changed trait bounds
slerpyyy Apr 1, 2021
1ec9aa8
Forgot one
slerpyyy Apr 1, 2021
cc480ba
Moved initialization of last into contructor
slerpyyy Apr 1, 2021
7d9b058
Actually removed checks this time
slerpyyy Apr 1, 2021
87d7f9a
Better size_hint and documentation
slerpyyy Apr 2, 2021
2e1beb1
Fixed the (1, Some(0)) size_hint bug
slerpyyy Apr 2, 2021
d0f9feb
This should work
slerpyyy Apr 3, 2021
d1e6d27
Reduced code duplication and added impl for SourceIter and InPlaceIte…
slerpyyy Apr 26, 2021
1a04987
Run formatter
slerpyyy Apr 26, 2021
69b18f7
Added more documentation
slerpyyy May 7, 2021
d8aebea
Simplified type signature
slerpyyy May 10, 2021
5dec9ce
Defer initial call to next + lots of inline tags
slerpyyy Jul 10, 2021
6cd757a
Make use of `Option::unwrap_unchecked` and prevent user from contruct…
slerpyyy Jul 14, 2021
fcbff04
Update library/core/src/iter/adapters/dedup.rs
slerpyyy Jul 14, 2021
9a906dd
Refactor `Dedup::next` as suggested by @camsteffen
slerpyyy Jul 14, 2021
3fbbfdb
whoops
slerpyyy Jul 30, 2021
9648fb5
Update library/core/src/iter/adapters/dedup.rs
slerpyyy Jan 25, 2022
67d01d5
Fix inplace_iteration related impls
slerpyyy Feb 26, 2022
a822aad
Go back to separate iterators
slerpyyy Aug 21, 2022
f57678c
Remove inplace_iteration traits because of ICE
slerpyyy Aug 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions library/core/src/iter/adapters/dedup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#![unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]

/// An iterator that removes all but the first of consecutive elements in a
/// given iterator according to the [`PartialEq`] trait implementation.
///
/// This `struct` is created by [`Iterator::dedup`].
/// See its documentation for more.
///
/// [`Iterator::dedup`]: Iterator::dedup
#[derive(Debug, Clone)]
pub struct Dedup<I>
where
I: Iterator,
{
inner: I,
last: Option<Option<I::Item>>,
}

impl<I> Dedup<I>
where
I: Iterator,
{
#[inline]
pub(crate) fn new(inner: I) -> Self {
Self { inner, last: None }
}
}

impl<I> Iterator for Dedup<I>
where
I: Iterator,
I::Item: PartialEq,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let Self { inner, last } = self;
let last = last.get_or_insert_with(|| inner.next());
let last_item = last.as_ref()?;
let next = inner.find(|next_item| next_item != last_item);
crate::mem::replace(last, next)
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let min = matches!(self.last, Some(Some(_))).into();
let max = self.inner.size_hint().1.map(|max| max + min);
(min, max)
}
}
slerpyyy marked this conversation as resolved.
Show resolved Hide resolved

/// An iterator that removes all but the first of consecutive elements in a
/// given iterator satisfying a given equality relation.
///
/// This `struct` is created by [`Iterator::dedup_by`].
/// See its documentation for more.
///
/// [`Iterator::dedup_by`]: Iterator::dedup_by
#[derive(Debug, Clone)]
pub struct DedupBy<I, F>
where
I: Iterator,
{
inner: I,
same_bucket: F,
last: Option<Option<I::Item>>,
}

impl<I, F> DedupBy<I, F>
where
I: Iterator,
{
#[inline]
pub(crate) fn new(inner: I, same_bucket: F) -> Self {
Self { inner, same_bucket, last: None }
}
}

impl<I, F> Iterator for DedupBy<I, F>
where
I: Iterator,
F: FnMut(&I::Item, &I::Item) -> bool,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let Self { inner, last, same_bucket } = self;
let last = last.get_or_insert_with(|| inner.next());
let last_item = last.as_ref()?;
let next = inner.find(|next_item| !(same_bucket)(next_item, last_item));
crate::mem::replace(last, next)
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let min = matches!(self.last, Some(Some(_))).into();
let max = self.inner.size_hint().1.map(|max| max + min);
(min, max)
}
}

/// An iterator that removes all but the first of consecutive elements in a
/// given iterator that resolve to the same key.
///
/// This `struct` is created by [`Iterator::dedup_by_key`].
/// See its documentation for more.
///
/// [`Iterator::dedup_by_key`]: Iterator::dedup_by_key
#[derive(Debug, Clone)]
pub struct DedupByKey<I, F, K>
where
I: Iterator,
F: FnMut(&I::Item) -> K,
{
inner: I,
key: F,
last: Option<Option<I::Item>>,
}

impl<I, F, K> DedupByKey<I, F, K>
where
I: Iterator,
F: FnMut(&I::Item) -> K,
{
#[inline]
pub(crate) fn new(inner: I, key: F) -> Self {
Self { inner, key, last: None }
}
}

impl<I, F, K> Iterator for DedupByKey<I, F, K>
where
I: Iterator,
F: FnMut(&I::Item) -> K,
K: PartialEq,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let Self { inner, last, key } = self;
let last = last.get_or_insert_with(|| inner.next());
let last_item = last.as_ref()?;
let next = inner.find(|next_item| key(next_item) != key(last_item));
crate::mem::replace(last, next)
}
slerpyyy marked this conversation as resolved.
Show resolved Hide resolved

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let min = matches!(self.last, Some(Some(_))).into();
let max = self.inner.size_hint().1.map(|max| max + min);
(min, max)
}
}
6 changes: 5 additions & 1 deletion library/core/src/iter/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod chain;
mod cloned;
mod copied;
mod cycle;
mod dedup;
mod enumerate;
mod filter;
mod filter_map;
Expand Down Expand Up @@ -66,7 +67,10 @@ pub use self::zip::TrustedRandomAccessNoCoerce;
#[stable(feature = "iter_zip", since = "1.59.0")]
pub use self::zip::zip;

/// This trait provides transitive access to source-stage in an iterator-adapter pipeline
#[unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]
pub use self::dedup::{Dedup, DedupBy, DedupByKey};

/// This trait provides transitive access to source-stage in an interator-adapter pipeline
/// under the conditions that
/// * the iterator source `S` itself implements `SourceIter<Source = S>`
/// * there is a delegating implementation of this trait for each adapter in the pipeline between
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ pub use self::adapters::{
Chain, Cycle, Enumerate, Filter, FilterMap, FlatMap, Fuse, Inspect, Map, Peekable, Rev, Scan,
Skip, SkipWhile, Take, TakeWhile, Zip,
};
#[unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]
pub use self::adapters::{Dedup, DedupBy, DedupByKey};
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
pub use self::adapters::{Intersperse, IntersperseWith};

Expand Down
137 changes: 137 additions & 0 deletions library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::super::try_process;
use super::super::ByRefSized;
use super::super::TrustedRandomAccessNoCoerce;
use super::super::{ArrayChunks, Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
use super::super::{Dedup, DedupBy, DedupByKey};
use super::super::{FlatMap, Flatten};
use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
use super::super::{
Expand Down Expand Up @@ -1689,6 +1690,142 @@ pub trait Iterator {
Inspect::new(self, f)
}

/// Removes all but the first of consecutive repeated elements in the iterator
/// according to the [`PartialEq`] trait implementation.
///
/// For an iterator yielding infinitely many consecutive duplicates,
/// this may result in an infinite loop.
///
/// If the iterator is sorted, this removes all duplicates.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(iter_dedup)]
///
/// let vec = vec![1, 2, 2, 3, 2];
///
/// let mut iter = vec.into_iter().dedup();
///
/// assert_eq!(iter.next(), Some(1));
/// assert_eq!(iter.next(), Some(2));
/// assert_eq!(iter.next(), Some(3));
/// assert_eq!(iter.next(), Some(2));
/// assert_eq!(iter.next(), None);
/// ```
///
/// Example of an infinite loop:
///
/// ```no_run
/// #![feature(iter_dedup)]
///
/// // this will never terminate
/// let _ = std::iter::repeat(2).dedup().next();
/// ```
#[unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]
#[inline]
fn dedup(self) -> Dedup<Self>
where
Self: Sized,
Self::Item: PartialEq,
{
Dedup::new(self)
}

/// Removes all but the first of consecutive elements in the iterator
/// satisfying a given equality relation.
///
/// The `same_bucket` function is passed a references to two elements from
/// the iterator and must determine if the elements compare equal.
///
/// For an iterator yielding infinitely many consecutive duplicates,
/// this may result in an infinite loop.
///
/// If the iterator is sorted, this removes all duplicates.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(iter_dedup)]
///
/// let vec = vec!["foo", "bar", "Bar", "baz", "bar"];
///
/// let mut iter = vec.into_iter().dedup_by(|a, b| a.eq_ignore_ascii_case(b));
///
/// assert_eq!(iter.next(), Some("foo"));
/// assert_eq!(iter.next(), Some("bar"));
/// assert_eq!(iter.next(), Some("baz"));
/// assert_eq!(iter.next(), Some("bar"));
/// assert_eq!(iter.next(), None);
/// ```
///
/// Example of an infinite loop:
///
/// ```no_run
/// #![feature(iter_dedup)]
///
/// // this will never terminate
/// let _ = std::iter::repeat(2).dedup_by(|a, b| a == b).next();
/// ```
#[unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]
#[inline]
fn dedup_by<F>(self, same_bucket: F) -> DedupBy<Self, F>
where
Self: Sized,
F: FnMut(&Self::Item, &Self::Item) -> bool,
{
DedupBy::new(self, same_bucket)
}

/// Removes all but the first of consecutive elements in the iterator
/// that resolve to the same key.
///
/// For an iterator yielding infinitely many consecutive duplicates,
/// this may result in an infinite loop.
///
/// If the iterator is sorted, this removes all duplicates.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(iter_dedup)]
///
/// let vec = vec![10, 20, 21, 30, 20];
///
/// let mut iter = vec.into_iter().dedup_by_key(|&i| i / 10);
///
/// assert_eq!(iter.next(), Some(10));
/// assert_eq!(iter.next(), Some(20));
/// assert_eq!(iter.next(), Some(30));
/// assert_eq!(iter.next(), Some(20));
/// assert_eq!(iter.next(), None);
/// ```
///
/// Example of an infinite loop:
///
/// ```no_run
/// #![feature(iter_dedup)]
///
/// // this will never terminate
/// let _ = std::iter::repeat(2).dedup_by_key(|&n| n).next();
/// ```
#[unstable(feature = "iter_dedup", reason = "recently added", issue = "83748")]
#[inline]
fn dedup_by_key<F, K>(self, key: F) -> DedupByKey<Self, F, K>
where
Self: Sized,
F: FnMut(&Self::Item) -> K,
K: PartialEq,
{
DedupByKey::new(self, key)
Copy link
Contributor

@pickfire pickfire Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DedupByKey::new(self, key)
self.dedup_by(|a, b| key(a) == key(b))

https://doc.rust-lang.org/stable/src/alloc/vec/mod.rs.html#1441

        self.dedup_by(|a, b| key(a) == key(b))

Not sure if this will work though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After that change, what is the signature of the function dedup_by_key?

Copy link
Contributor

@cjgillot cjgillot Apr 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the C++ way to give your closure type a name:

struct EqByKey<F> {
  f: F
}
impl<I, K: PartialEq, F: FnMut(&I) -> K> FnOnce<(&I, &I)> for EqByKey<F> {
  type Output = bool;
  extern "rust-call" fn call_once(mut self, (a, b): (&I, &I)) -> bool {
    (self.f)(a) == (self.f)(b)
  }
}
impl<I, K: PartialEq, F: FnMut(&I) -> K> FnMut<(&I, &I)> for EqByKey<F> {
  extern "rust-call" fn call_mut(&mut self, (a, b): (&I, &I)) -> bool {
    (self.f)(a) == (self.f)(b)
  }
}

Edit: just saw #83748 (comment)

}

/// Borrows an iterator, rather than consuming it.
///
/// This is useful to allow applying iterator adapters while still
Expand Down