From 60f1449b61a2e118916105d5fc225c005757e42e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 1 Jul 2019 11:49:44 -0700 Subject: [PATCH 1/7] Add Iterator::partition_mut() and is_partitioned() `partition_mut()` swaps `&mut T` items in-place to satisfy the predicate, so all `true` items precede all `false` items. This requires a `DoubleEndedIterator` so we can search from front and back for items that need swapping. `is_partitioned()` checks whether the predicate is already satisfied. --- src/libcore/iter/traits/iterator.rs | 71 +++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index b9a98236f18a7..b5835f19d74d6 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1472,6 +1472,11 @@ pub trait Iterator { /// `partition()` returns a pair, all of the elements for which it returned /// `true`, and all of the elements for which it returned `false`. /// + /// See also [`is_partitioned()`] and [`partition_mut()`]. + /// + /// [`is_partitioned()`]: #method.is_partitioned + /// [`partition_mut()`]: #method.partition_mut + /// /// # Examples /// /// Basic usage: @@ -1506,6 +1511,72 @@ pub trait Iterator { (left, right) } + /// Reorder the elements of this iterator *in-place* according to the given predicate, + /// such that all those that return `true` precede all those that return `false`. + /// + /// The relative order of partitioned items is not maintained. + /// + /// See also [`is_partitioned()`] and [`partition()`]. + /// + /// [`is_partitioned()`]: #method.is_partitioned + /// [`partition()`]: #method.partition + /// + /// # Examples + /// + /// ``` + /// #![feature(iter_partition_mut)] + /// + /// let mut a = [1, 2, 3, 4, 5, 6, 7]; + /// + /// // partition in-place between evens and odds + /// a.iter_mut().partition_mut(|&n| n % 2 == 0); + /// + /// assert!(a[..3].iter().all(|&n| n % 2 == 0)); // evens + /// assert!(a[3..].iter().all(|&n| n % 2 == 1)); // odds + /// ``` + #[unstable(feature = "iter_partition_mut", reason = "new API", issue = "0")] + fn partition_mut<'a, T: 'a, P>(mut self, mut predicate: P) + where + Self: Sized + DoubleEndedIterator, + P: FnMut(&T) -> bool, + { + // Repeatedly find the first `false` and swap it with the last `true`. + while let Some(head) = self.find(|x| !predicate(x)) { + if let Some(tail) = self.rfind(|x| predicate(x)) { + crate::mem::swap(head, tail); + } else { + break; + } + } + } + + /// Checks if the elements of this iterator are partitioned according to the given predicate, + /// such that all those that return `true` precede all those that return `false`. + /// + /// See also [`partition()`] and [`partition_mut()`]. + /// + /// [`partition()`]: #method.partition + /// [`partition_mut()`]: #method.partition_mut + /// + /// # Examples + /// + /// ``` + /// #![feature(iter_is_partitioned)] + /// + /// assert!("Iterator".chars().is_partitioned(char::is_uppercase)); + /// assert!(!"IntoIterator".chars().is_partitioned(char::is_uppercase)); + /// ``` + #[unstable(feature = "iter_is_partitioned", reason = "new API", issue = "0")] + fn is_partitioned

(mut self, mut predicate: P) -> bool + where + Self: Sized, + P: FnMut(Self::Item) -> bool, + { + // Either all items test `true`, or the first clause stops at `false` + // and we check that there are no more `true` items after that. + self.all(&mut predicate) || !self.any(predicate) + } + /// An iterator method that applies a function as long as it returns /// successfully, producing a single, final value. /// From cdeec0a618248b9321aed338b09c94a53338626e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 1 Jul 2019 15:17:39 -0700 Subject: [PATCH 2/7] Capitalize example comment Co-Authored-By: Mazdak Farrokhzad --- src/libcore/iter/traits/iterator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index b5835f19d74d6..e06030aee6b11 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1528,7 +1528,7 @@ pub trait Iterator { /// /// let mut a = [1, 2, 3, 4, 5, 6, 7]; /// - /// // partition in-place between evens and odds + /// // Partition in-place between evens and odds /// a.iter_mut().partition_mut(|&n| n % 2 == 0); /// /// assert!(a[..3].iter().all(|&n| n % 2 == 0)); // evens From cd0ebc43c7f0238d09bdf722409e8d77a41a37db Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 1 Jul 2019 15:34:53 -0700 Subject: [PATCH 3/7] Rename partition_mut to partition_in_place --- src/libcore/iter/traits/iterator.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index e06030aee6b11..45aafb4bd7fc5 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1472,10 +1472,10 @@ pub trait Iterator { /// `partition()` returns a pair, all of the elements for which it returned /// `true`, and all of the elements for which it returned `false`. /// - /// See also [`is_partitioned()`] and [`partition_mut()`]. + /// See also [`is_partitioned()`] and [`partition_in_place()`]. /// /// [`is_partitioned()`]: #method.is_partitioned - /// [`partition_mut()`]: #method.partition_mut + /// [`partition_in_place()`]: #method.partition_in_place /// /// # Examples /// @@ -1524,18 +1524,18 @@ pub trait Iterator { /// # Examples /// /// ``` - /// #![feature(iter_partition_mut)] + /// #![feature(iter_partition_in_place)] /// /// let mut a = [1, 2, 3, 4, 5, 6, 7]; /// /// // Partition in-place between evens and odds - /// a.iter_mut().partition_mut(|&n| n % 2 == 0); + /// a.iter_mut().partition_in_place(|&n| n % 2 == 0); /// /// assert!(a[..3].iter().all(|&n| n % 2 == 0)); // evens /// assert!(a[3..].iter().all(|&n| n % 2 == 1)); // odds /// ``` - #[unstable(feature = "iter_partition_mut", reason = "new API", issue = "0")] - fn partition_mut<'a, T: 'a, P>(mut self, mut predicate: P) + #[unstable(feature = "iter_partition_in_place", reason = "new API", issue = "0")] + fn partition_in_place<'a, T: 'a, P>(mut self, mut predicate: P) where Self: Sized + DoubleEndedIterator, P: FnMut(&T) -> bool, @@ -1553,10 +1553,10 @@ pub trait Iterator { /// Checks if the elements of this iterator are partitioned according to the given predicate, /// such that all those that return `true` precede all those that return `false`. /// - /// See also [`partition()`] and [`partition_mut()`]. + /// See also [`partition()`] and [`partition_in_place()`]. /// /// [`partition()`]: #method.partition - /// [`partition_mut()`]: #method.partition_mut + /// [`partition_in_place()`]: #method.partition_in_place /// /// # Examples /// From 0492f972c7751daaa819a937c75ceadd0cf5326e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 8 Jul 2019 18:25:19 -0700 Subject: [PATCH 4/7] Return the true count from partition_in_place --- src/libcore/iter/traits/iterator.rs | 41 ++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index 45aafb4bd7fc5..b123d4a146999 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1513,6 +1513,7 @@ pub trait Iterator { /// Reorder the elements of this iterator *in-place* according to the given predicate, /// such that all those that return `true` precede all those that return `false`. + /// Returns the number of `true` elements found. /// /// The relative order of partitioned items is not maintained. /// @@ -1529,25 +1530,53 @@ pub trait Iterator { /// let mut a = [1, 2, 3, 4, 5, 6, 7]; /// /// // Partition in-place between evens and odds - /// a.iter_mut().partition_in_place(|&n| n % 2 == 0); + /// let i = a.iter_mut().partition_in_place(|&n| n % 2 == 0); /// - /// assert!(a[..3].iter().all(|&n| n % 2 == 0)); // evens - /// assert!(a[3..].iter().all(|&n| n % 2 == 1)); // odds + /// assert_eq!(i, 3); + /// assert!(a[..i].iter().all(|&n| n % 2 == 0)); // evens + /// assert!(a[i..].iter().all(|&n| n % 2 == 1)); // odds /// ``` #[unstable(feature = "iter_partition_in_place", reason = "new API", issue = "0")] - fn partition_in_place<'a, T: 'a, P>(mut self, mut predicate: P) + fn partition_in_place<'a, T: 'a, P>(mut self, ref mut predicate: P) -> usize where Self: Sized + DoubleEndedIterator, P: FnMut(&T) -> bool, { + // FIXME: should we worry about the count overflowing? The only way to have more than + // `usize::MAX` mutable references is with ZSTs, which aren't useful to partition... + + // These closure "factory" functions exist to avoid genericity in `Self`. + + #[inline] + fn is_false<'a, T>( + predicate: &'a mut impl FnMut(&T) -> bool, + true_count: &'a mut usize, + ) -> impl FnMut(&&mut T) -> bool + 'a { + move |x| { + let p = predicate(&**x); + *true_count += p as usize; + !p + } + } + + #[inline] + fn is_true( + predicate: &mut impl FnMut(&T) -> bool + ) -> impl FnMut(&&mut T) -> bool + '_ { + move |x| predicate(&**x) + } + // Repeatedly find the first `false` and swap it with the last `true`. - while let Some(head) = self.find(|x| !predicate(x)) { - if let Some(tail) = self.rfind(|x| predicate(x)) { + let mut true_count = 0; + while let Some(head) = self.find(is_false(predicate, &mut true_count)) { + if let Some(tail) = self.rfind(is_true(predicate)) { crate::mem::swap(head, tail); + true_count += 1; } else { break; } } + true_count } /// Checks if the elements of this iterator are partitioned according to the given predicate, From 265e3a6230364455dfe55378b59833be93e17084 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 9 Jul 2019 12:39:19 -0700 Subject: [PATCH 5/7] Unit test Iterator::partition_in_place and is_partitioned --- src/libcore/tests/iter.rs | 36 ++++++++++++++++++++++++++++++++++++ src/libcore/tests/lib.rs | 2 ++ 2 files changed, 38 insertions(+) diff --git a/src/libcore/tests/iter.rs b/src/libcore/tests/iter.rs index 4d840ef24c8e6..b7b0849e2129b 100644 --- a/src/libcore/tests/iter.rs +++ b/src/libcore/tests/iter.rs @@ -2460,3 +2460,39 @@ fn test_is_sorted() { assert!(!["c", "bb", "aaa"].iter().is_sorted()); assert!(["c", "bb", "aaa"].iter().is_sorted_by_key(|s| s.len())); } + +#[test] +fn test_partition() { + fn check(xs: &mut [i32], ref p: impl Fn(&i32) -> bool, expected: usize) { + let i = xs.iter_mut().partition_in_place(p); + assert_eq!(expected, i); + assert!(xs[..i].iter().all(p)); + assert!(!xs[i..].iter().any(p)); + assert!(xs.iter().is_partitioned(p)); + if i == 0 || i == xs.len() { + assert!(xs.iter().rev().is_partitioned(p)); + } else { + assert!(!xs.iter().rev().is_partitioned(p)); + } + } + + check(&mut [], |_| true, 0); + check(&mut [], |_| false, 0); + + check(&mut [0], |_| true, 1); + check(&mut [0], |_| false, 0); + + check(&mut [-1, 1], |&x| x > 0, 1); + check(&mut [-1, 1], |&x| x < 0, 1); + + let ref mut xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + check(xs, |_| true, 10); + check(xs, |_| false, 0); + check(xs, |&x| x % 2 == 0, 5); // evens + check(xs, |&x| x % 2 == 1, 5); // odds + check(xs, |&x| x % 3 == 0, 4); // multiple of 3 + check(xs, |&x| x % 4 == 0, 3); // multiple of 4 + check(xs, |&x| x % 5 == 0, 2); // multiple of 5 + check(xs, |&x| x < 3, 3); // small + check(xs, |&x| x > 6, 3); // large +} diff --git a/src/libcore/tests/lib.rs b/src/libcore/tests/lib.rs index 4b48d1225902b..cbb6423d71010 100644 --- a/src/libcore/tests/lib.rs +++ b/src/libcore/tests/lib.rs @@ -31,6 +31,8 @@ #![feature(slice_partition_dedup)] #![feature(int_error_matching)] #![feature(const_fn)] +#![feature(iter_partition_in_place)] +#![feature(iter_is_partitioned)] #![warn(rust_2018_idioms)] extern crate test; From 4c22e48f6e63f4c6274a59283e845ca4a96c5de2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 9 Jul 2019 15:17:47 -0700 Subject: [PATCH 6/7] Tracking issue 62543 for iter_partition_in_place --- src/libcore/iter/traits/iterator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index b123d4a146999..cb93d78af3516 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1536,7 +1536,7 @@ pub trait Iterator { /// assert!(a[..i].iter().all(|&n| n % 2 == 0)); // evens /// assert!(a[i..].iter().all(|&n| n % 2 == 1)); // odds /// ``` - #[unstable(feature = "iter_partition_in_place", reason = "new API", issue = "0")] + #[unstable(feature = "iter_partition_in_place", reason = "new API", issue = "62543")] fn partition_in_place<'a, T: 'a, P>(mut self, ref mut predicate: P) -> usize where Self: Sized + DoubleEndedIterator, From 7171c83ab234926d620c4869eeac90b393833ef2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 9 Jul 2019 15:18:33 -0700 Subject: [PATCH 7/7] Tracking issue 62544 for iter_is_partitioned --- src/libcore/iter/traits/iterator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/iter/traits/iterator.rs b/src/libcore/iter/traits/iterator.rs index cb93d78af3516..6eddac672c158 100644 --- a/src/libcore/iter/traits/iterator.rs +++ b/src/libcore/iter/traits/iterator.rs @@ -1595,7 +1595,7 @@ pub trait Iterator { /// assert!("Iterator".chars().is_partitioned(char::is_uppercase)); /// assert!(!"IntoIterator".chars().is_partitioned(char::is_uppercase)); /// ``` - #[unstable(feature = "iter_is_partitioned", reason = "new API", issue = "0")] + #[unstable(feature = "iter_is_partitioned", reason = "new API", issue = "62544")] fn is_partitioned

(mut self, mut predicate: P) -> bool where Self: Sized,