mirror of
https://github.com/rust-lang/rust
synced 2024-09-05 08:37:14 +00:00
Add Iterator::map_windows
This is inherited from the old PR. This method returns an iterator over mapped windows of the starting iterator. Adding the more straight-forward `Iterator::windows` is not easily possible right now as the items are stored in the iterator type, meaning the `next` call would return references to `self`. This is not allowed by the current `Iterator` trait design. This might change once GATs have landed. The idea has been brought up by @m-ou-se here: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Iterator.3A.3A.7Bpairwise.2C.20windows.7D/near/224587771 Co-authored-by: Lukas Kalbertodt <lukas.kalbertodt@gmail.com>
This commit is contained in:
parent
faee636ebf
commit
97c953f561
293
library/core/src/iter/adapters/map_windows.rs
Normal file
293
library/core/src/iter/adapters/map_windows.rs
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
use crate::{
|
||||||
|
fmt,
|
||||||
|
iter::{ExactSizeIterator, FusedIterator},
|
||||||
|
mem::{self, MaybeUninit},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An iterator over the mapped windows of another iterator.
|
||||||
|
///
|
||||||
|
/// This `struct` is created by the [`Iterator::map_windows`]. See its
|
||||||
|
/// documentation for more information.
|
||||||
|
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
pub struct MapWindows<I: Iterator, F, const N: usize> {
|
||||||
|
f: F,
|
||||||
|
inner: MapWindowsInner<I, N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MapWindowsInner<I: Iterator, const N: usize> {
|
||||||
|
// We fuse the inner iterator because there shouldn't be "holes" in
|
||||||
|
// the sliding window. Once the iterator returns a `None`, we make
|
||||||
|
// our `MapWindows` iterator return `None` forever.
|
||||||
|
iter: Option<I>,
|
||||||
|
// Since iterators are assumed lazy, i.e. it only yields an item when
|
||||||
|
// `Iterator::next()` is called, and `MapWindows` is not an exception.
|
||||||
|
//
|
||||||
|
// Before the first iteration, we keep the buffer `None`. When the user
|
||||||
|
// first call `next` or other methods that makes the iterator advance,
|
||||||
|
// we collect the first `N` items yielded from the inner iterator and
|
||||||
|
// put it into the buffer.
|
||||||
|
//
|
||||||
|
// When the inner iterator has returned a `None` (i.e. fused), we take
|
||||||
|
// away this `buffer` and leave it `None` to reclaim its resources.
|
||||||
|
//
|
||||||
|
// FIXME: should we shrink the size of `buffer` using niche optimization?
|
||||||
|
buffer: Option<Buffer<I::Item, N>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Buffer` uses two times of space to reduce moves among the iterations.
|
||||||
|
// `Buffer<T, N>` is semantically `[MaybeUninit<T>; 2 * N]`. However, due
|
||||||
|
// to limitations of const generics, we use this different type. Note that
|
||||||
|
// it has the same underlying memory layout.
|
||||||
|
struct Buffer<T, const N: usize> {
|
||||||
|
// Invariant: `self.buffer[self.start..self.start + N]` is initialized,
|
||||||
|
// with all other elements being uninitialized. This also
|
||||||
|
// implies that `self.start <= N`.
|
||||||
|
buffer: [[MaybeUninit<T>; N]; 2],
|
||||||
|
start: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator, F, const N: usize> MapWindows<I, F, N> {
|
||||||
|
pub(in crate::iter) fn new(iter: I, f: F) -> Self {
|
||||||
|
assert!(N != 0, "array in `Iterator::map_windows` must contain more than 0 elements");
|
||||||
|
|
||||||
|
// Only ZST arrays' length can be so large.
|
||||||
|
if mem::size_of::<I::Item>() == 0 {
|
||||||
|
assert!(
|
||||||
|
N.checked_mul(2).is_some(),
|
||||||
|
"array size of `Iterator::map_windows` is too large"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { inner: MapWindowsInner::new(iter), f }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator, const N: usize> MapWindowsInner<I, N> {
|
||||||
|
#[inline]
|
||||||
|
fn new(iter: I) -> Self {
|
||||||
|
Self { iter: Some(iter), buffer: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_window(&mut self) -> Option<&[I::Item; N]> {
|
||||||
|
let iter = self.iter.as_mut()?;
|
||||||
|
match self.buffer {
|
||||||
|
// It is the first time to advance. We collect
|
||||||
|
// the first `N` items from `self.iter` to initialize `self.buffer`.
|
||||||
|
None => self.buffer = Buffer::try_from_iter(iter),
|
||||||
|
Some(ref mut buffer) => match iter.next() {
|
||||||
|
None => {
|
||||||
|
// Fuse the inner iterator since it yields a `None`.
|
||||||
|
self.iter.take();
|
||||||
|
self.buffer.take();
|
||||||
|
}
|
||||||
|
// Advance the iterator. We first call `next` before changing our buffer
|
||||||
|
// at all. This means that if `next` panics, our invariant is upheld and
|
||||||
|
// our `Drop` impl drops the correct elements.
|
||||||
|
Some(item) => buffer.push(item),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.buffer.as_ref().map(Buffer::as_array_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let Some(ref iter) = self.iter else { return (0, Some(0)) };
|
||||||
|
let (lo, hi) = iter.size_hint();
|
||||||
|
if self.buffer.is_some() {
|
||||||
|
// If the first `N` items are already yielded by the inner iterator,
|
||||||
|
// the size hint is then equal to the that of the inner iterator's.
|
||||||
|
(lo, hi)
|
||||||
|
} else {
|
||||||
|
// If the first `N` items are not yet yielded by the inner iterator,
|
||||||
|
// the first `N` elements should be counted as one window, so both bounds
|
||||||
|
// should subtract `N - 1`.
|
||||||
|
(lo.saturating_sub(N - 1), hi.map(|hi| hi.saturating_sub(N - 1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Buffer<T, N> {
|
||||||
|
fn try_from_iter(iter: &mut impl Iterator<Item = T>) -> Option<Self> {
|
||||||
|
let first_half = crate::array::iter_next_chunk(iter).ok()?;
|
||||||
|
let buffer = [MaybeUninit::new(first_half).transpose(), MaybeUninit::uninit_array()];
|
||||||
|
Some(Self { buffer, start: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn buffer_ptr(&self) -> *const MaybeUninit<T> {
|
||||||
|
self.buffer.as_ptr().cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn buffer_mut_ptr(&mut self) -> *mut MaybeUninit<T> {
|
||||||
|
self.buffer.as_mut_ptr().cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_array_ref(&self) -> &[T; N] {
|
||||||
|
debug_assert!(self.start + N <= 2 * N);
|
||||||
|
|
||||||
|
// SAFETY: our invariant guarantees these elements are initialized.
|
||||||
|
unsafe { &*self.buffer_ptr().add(self.start).cast() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_uninit_array_mut(&mut self) -> &mut MaybeUninit<[T; N]> {
|
||||||
|
debug_assert!(self.start + N <= 2 * N);
|
||||||
|
|
||||||
|
// SAFETY: our invariant guarantees these elements are in bounds.
|
||||||
|
unsafe { &mut *self.buffer_mut_ptr().add(self.start).cast() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new item `next` to the back, and pops the front-most one.
|
||||||
|
///
|
||||||
|
/// All the elements will be shifted to the front end when pushing reaches
|
||||||
|
/// the back end.
|
||||||
|
fn push(&mut self, next: T) {
|
||||||
|
let buffer_mut_ptr = self.buffer_mut_ptr();
|
||||||
|
debug_assert!(self.start + N <= 2 * N);
|
||||||
|
|
||||||
|
let to_drop = if self.start == N {
|
||||||
|
// We have reached the end of our buffer and have to copy
|
||||||
|
// everything to the start. Example layout for N = 3.
|
||||||
|
//
|
||||||
|
// 0 1 2 3 4 5 0 1 2 3 4 5
|
||||||
|
// ┌───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐
|
||||||
|
// │ - │ - │ - │ a │ b │ c │ -> │ b │ c │ n │ - │ - │ - │
|
||||||
|
// └───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘
|
||||||
|
// ↑ ↑
|
||||||
|
// start start
|
||||||
|
|
||||||
|
// SAFETY: the two pointers are valid for reads/writes of N -1
|
||||||
|
// elements because our array's size is semantically 2 * N. The
|
||||||
|
// regions also don't overlap for the same reason.
|
||||||
|
//
|
||||||
|
// We leave the old elements in place. As soon as `start` is set
|
||||||
|
// to 0, we treat them as uninitialized and treat their copies
|
||||||
|
// as initialized.
|
||||||
|
let to_drop = unsafe {
|
||||||
|
ptr::copy_nonoverlapping(buffer_mut_ptr.add(self.start + 1), buffer_mut_ptr, N - 1);
|
||||||
|
(*buffer_mut_ptr.add(N - 1)).write(next);
|
||||||
|
buffer_mut_ptr.add(self.start)
|
||||||
|
};
|
||||||
|
self.start = 0;
|
||||||
|
to_drop
|
||||||
|
} else {
|
||||||
|
// SAFETY: `self.start` is < N as guaranteed by the invariant
|
||||||
|
// plus the check above. Even if the drop at the end panics,
|
||||||
|
// the invariant is upheld.
|
||||||
|
//
|
||||||
|
// Example layout for N = 3:
|
||||||
|
//
|
||||||
|
// 0 1 2 3 4 5 0 1 2 3 4 5
|
||||||
|
// ┌───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐
|
||||||
|
// │ - │ a │ b │ c │ - │ - │ -> │ - │ - │ b │ c │ n │ - │
|
||||||
|
// └───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘
|
||||||
|
// ↑ ↑
|
||||||
|
// start start
|
||||||
|
//
|
||||||
|
let to_drop = unsafe {
|
||||||
|
(*buffer_mut_ptr.add(self.start + N)).write(next);
|
||||||
|
buffer_mut_ptr.add(self.start)
|
||||||
|
};
|
||||||
|
self.start += 1;
|
||||||
|
to_drop
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: the index is valid and this is element `a` in the
|
||||||
|
// diagram above and has not been dropped yet.
|
||||||
|
unsafe { ptr::drop_in_place(to_drop.cast::<T>()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone, const N: usize> Clone for Buffer<T, N> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let mut buffer = Buffer {
|
||||||
|
buffer: [MaybeUninit::uninit_array(), MaybeUninit::uninit_array()],
|
||||||
|
start: self.start,
|
||||||
|
};
|
||||||
|
buffer.as_uninit_array_mut().write(self.as_array_ref().clone());
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, const N: usize> Clone for MapWindowsInner<I, N>
|
||||||
|
where
|
||||||
|
I: Iterator + Clone,
|
||||||
|
I::Item: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { iter: self.iter.clone(), buffer: self.buffer.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Drop for Buffer<T, N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: our invariant guarantees that N elements starting from
|
||||||
|
// `self.start` are initialized. We drop them here.
|
||||||
|
unsafe {
|
||||||
|
let initialized_part: *mut [T] = crate::ptr::slice_from_raw_parts_mut(
|
||||||
|
self.buffer_mut_ptr().add(self.start).cast(),
|
||||||
|
N,
|
||||||
|
);
|
||||||
|
ptr::drop_in_place(initialized_part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
impl<I, F, R, const N: usize> Iterator for MapWindows<I, F, N>
|
||||||
|
where
|
||||||
|
I: Iterator,
|
||||||
|
F: FnMut(&[I::Item; N]) -> R,
|
||||||
|
{
|
||||||
|
type Item = R;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let window = self.inner.next_window()?;
|
||||||
|
let out = (self.f)(window);
|
||||||
|
Some(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.inner.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that even if the inner iterator not fused, the `MapWindows` is still fused,
|
||||||
|
// because we don't allow "holes" in the mapping window.
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
impl<I, F, R, const N: usize> FusedIterator for MapWindows<I, F, N>
|
||||||
|
where
|
||||||
|
I: Iterator,
|
||||||
|
F: FnMut(&[I::Item; N]) -> R,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
impl<I, F, R, const N: usize> ExactSizeIterator for MapWindows<I, F, N>
|
||||||
|
where
|
||||||
|
I: ExactSizeIterator,
|
||||||
|
F: FnMut(&[I::Item; N]) -> R,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
impl<I: Iterator + fmt::Debug, F, const N: usize> fmt::Debug for MapWindows<I, F, N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("MapWindows").field("iter", &self.inner.iter).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
impl<I, F, const N: usize> Clone for MapWindows<I, F, N>
|
||||||
|
where
|
||||||
|
I: Iterator + Clone,
|
||||||
|
F: Clone,
|
||||||
|
I::Item: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { f: self.f.clone(), inner: self.inner.clone() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
mod intersperse;
|
mod intersperse;
|
||||||
mod map;
|
mod map;
|
||||||
mod map_while;
|
mod map_while;
|
||||||
|
mod map_windows;
|
||||||
mod peekable;
|
mod peekable;
|
||||||
mod rev;
|
mod rev;
|
||||||
mod scan;
|
mod scan;
|
||||||
|
@ -57,6 +58,9 @@
|
||||||
#[stable(feature = "iter_map_while", since = "1.57.0")]
|
#[stable(feature = "iter_map_while", since = "1.57.0")]
|
||||||
pub use self::map_while::MapWhile;
|
pub use self::map_while::MapWhile;
|
||||||
|
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
pub use self::map_windows::MapWindows;
|
||||||
|
|
||||||
#[unstable(feature = "trusted_random_access", issue = "none")]
|
#[unstable(feature = "trusted_random_access", issue = "none")]
|
||||||
pub use self::zip::TrustedRandomAccess;
|
pub use self::zip::TrustedRandomAccess;
|
||||||
|
|
||||||
|
|
|
@ -440,6 +440,8 @@ fn $fold<AAA, FFF>(mut self, init: AAA, fold: FFF) -> AAA
|
||||||
pub use self::adapters::Flatten;
|
pub use self::adapters::Flatten;
|
||||||
#[stable(feature = "iter_map_while", since = "1.57.0")]
|
#[stable(feature = "iter_map_while", since = "1.57.0")]
|
||||||
pub use self::adapters::MapWhile;
|
pub use self::adapters::MapWhile;
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
pub use self::adapters::MapWindows;
|
||||||
#[unstable(feature = "inplace_iteration", issue = "none")]
|
#[unstable(feature = "inplace_iteration", issue = "none")]
|
||||||
pub use self::adapters::SourceIter;
|
pub use self::adapters::SourceIter;
|
||||||
#[stable(feature = "iterator_step_by", since = "1.28.0")]
|
#[stable(feature = "iterator_step_by", since = "1.28.0")]
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
use super::super::{FlatMap, Flatten};
|
use super::super::{FlatMap, Flatten};
|
||||||
use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
|
use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
|
||||||
use super::super::{
|
use super::super::{
|
||||||
Inspect, Map, MapWhile, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile,
|
Inspect, Map, MapWhile, MapWindows, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take,
|
||||||
|
TakeWhile,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn _assert_is_object_safe(_: &dyn Iterator<Item = ()>) {}
|
fn _assert_is_object_safe(_: &dyn Iterator<Item = ()>) {}
|
||||||
|
@ -1591,6 +1592,163 @@ fn flatten(self) -> Flatten<Self>
|
||||||
Flatten::new(self)
|
Flatten::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calls the given function `f` for each contiguous window of size `N` over
|
||||||
|
/// `self` and returns an iterator over the outputs of `f`. Like [`slice::windows()`],
|
||||||
|
/// the windows during mapping overlap as well.
|
||||||
|
///
|
||||||
|
/// In the following example, the closure is called three times with the
|
||||||
|
/// arguments `&['a', 'b']`, `&['b', 'c']` and `&['c', 'd']` respectively.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// let strings = "abcd".chars()
|
||||||
|
/// .map_windows(|[x, y]| format!("{}+{}", x, y))
|
||||||
|
/// .collect::<Vec<String>>();
|
||||||
|
///
|
||||||
|
/// assert_eq!(strings, vec!["a+b", "b+c", "c+d"]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that the const parameter `N` is usually inferred by the
|
||||||
|
/// destructured argument in the closure.
|
||||||
|
///
|
||||||
|
/// The returned iterator yields 𝑘 − `N` + 1 items (where 𝑘 is the number of
|
||||||
|
/// items yielded by `self`). If 𝑘 is less than `N`, this method yields an
|
||||||
|
/// empty iterator.
|
||||||
|
///
|
||||||
|
/// The returned iterator implements [`FusedIterator`], because once `self`
|
||||||
|
/// returns `None`, even if it returns a `Some(T)` again in the next iterations,
|
||||||
|
/// we cannot put it into a contigious array buffer, and thus the returned iterator
|
||||||
|
/// should be fused.
|
||||||
|
///
|
||||||
|
/// [`slice::windows()`]: slice::windows
|
||||||
|
/// [`FusedIterator`]: crate::iter::FusedIterator
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `N` is 0. This check will most probably get changed to a
|
||||||
|
/// compile time error before this method gets stabilized.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// let iter = std::iter::repeat(0).map_windows(|&[]| ());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Building the sums of neighboring numbers.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// let mut it = [1, 3, 8, 1].iter().map_windows(|&[a, b]| a + b);
|
||||||
|
/// assert_eq!(it.next(), Some(4)); // 1 + 3
|
||||||
|
/// assert_eq!(it.next(), Some(11)); // 3 + 8
|
||||||
|
/// assert_eq!(it.next(), Some(9)); // 8 + 1
|
||||||
|
/// assert_eq!(it.next(), None);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Since the elements in the following example implement `Copy`, we can
|
||||||
|
/// just copy the array and get an iterator over the windows.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// let mut it = "ferris".chars().map_windows(|w: &[_; 3]| *w);
|
||||||
|
/// assert_eq!(it.next(), Some(['f', 'e', 'r']));
|
||||||
|
/// assert_eq!(it.next(), Some(['e', 'r', 'r']));
|
||||||
|
/// assert_eq!(it.next(), Some(['r', 'r', 'i']));
|
||||||
|
/// assert_eq!(it.next(), Some(['r', 'i', 's']));
|
||||||
|
/// assert_eq!(it.next(), None);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can also use this function to check the sortedness of an iterator.
|
||||||
|
/// For the simple case, rather use [`Iterator::is_sorted`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// let mut it = [0.5, 1.0, 3.5, 3.0, 8.5, 8.5, f32::NAN].iter()
|
||||||
|
/// .map_windows(|[a, b]| a <= b);
|
||||||
|
///
|
||||||
|
/// assert_eq!(it.next(), Some(true)); // 0.5 <= 1.0
|
||||||
|
/// assert_eq!(it.next(), Some(true)); // 1.0 <= 3.5
|
||||||
|
/// assert_eq!(it.next(), Some(false)); // 3.5 <= 3.0
|
||||||
|
/// assert_eq!(it.next(), Some(true)); // 3.0 <= 8.5
|
||||||
|
/// assert_eq!(it.next(), Some(true)); // 8.5 <= 8.5
|
||||||
|
/// assert_eq!(it.next(), Some(false)); // 8.5 <= NAN
|
||||||
|
/// assert_eq!(it.next(), None);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For non-fused iterators, they are fused after `map_windows`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(iter_map_windows)]
|
||||||
|
///
|
||||||
|
/// #[derive(Default)]
|
||||||
|
/// struct NonFusedIterator {
|
||||||
|
/// state: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Iterator for NonFusedIterator {
|
||||||
|
/// type Item = i32;
|
||||||
|
///
|
||||||
|
/// fn next(&mut self) -> Option<i32> {
|
||||||
|
/// let val = self.state;
|
||||||
|
/// self.state = self.state + 1;
|
||||||
|
///
|
||||||
|
/// // yields `0..5` first, then only even numbers since `6..`.
|
||||||
|
/// if val < 5 || val % 2 == 0 {
|
||||||
|
/// Some(val)
|
||||||
|
/// } else {
|
||||||
|
/// None
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// let mut iter = NonFusedIterator::default();
|
||||||
|
///
|
||||||
|
/// // yields 0..5 first.
|
||||||
|
/// assert_eq!(iter.next(), Some(0));
|
||||||
|
/// assert_eq!(iter.next(), Some(1));
|
||||||
|
/// assert_eq!(iter.next(), Some(2));
|
||||||
|
/// assert_eq!(iter.next(), Some(3));
|
||||||
|
/// assert_eq!(iter.next(), Some(4));
|
||||||
|
/// // then we can see our iterator going back and forth
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
/// assert_eq!(iter.next(), Some(6));
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
/// assert_eq!(iter.next(), Some(8));
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
///
|
||||||
|
/// // however, with `.map_windows()`, it is fused.
|
||||||
|
/// let mut iter = NonFusedIterator::default()
|
||||||
|
/// .map_windows(|arr: &[_; 2]| *arr);
|
||||||
|
///
|
||||||
|
/// assert_eq!(iter.next(), Some([0, 1]));
|
||||||
|
/// assert_eq!(iter.next(), Some([1, 2]));
|
||||||
|
/// assert_eq!(iter.next(), Some([2, 3]));
|
||||||
|
/// assert_eq!(iter.next(), Some([3, 4]));
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
///
|
||||||
|
/// // it will always return `None` after the first time.
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
/// assert_eq!(iter.next(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
|
||||||
|
#[rustc_do_not_const_check]
|
||||||
|
fn map_windows<F, R, const N: usize>(self, f: F) -> MapWindows<Self, F, N>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
F: FnMut(&[Self::Item; N]) -> R,
|
||||||
|
{
|
||||||
|
MapWindows::new(self, f)
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates an iterator which ends after the first [`None`].
|
/// Creates an iterator which ends after the first [`None`].
|
||||||
///
|
///
|
||||||
/// After an iterator returns [`None`], future calls may or may not yield
|
/// After an iterator returns [`None`], future calls may or may not yield
|
||||||
|
|
283
library/core/tests/iter/adapters/map_windows.rs
Normal file
283
library/core/tests/iter/adapters/map_windows.rs
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||||
|
|
||||||
|
#[cfg(not(panic = "abort"))]
|
||||||
|
mod drop_checks {
|
||||||
|
//! These tests mainly make sure the elements are correctly dropped.
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DropInfo {
|
||||||
|
dropped_twice: AtomicBool,
|
||||||
|
alive_count: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropInfo {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self { dropped_twice: AtomicBool::new(false), alive_count: AtomicUsize::new(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn check(&self) {
|
||||||
|
assert!(!self.dropped_twice.load(SeqCst), "a value was dropped twice");
|
||||||
|
assert_eq!(self.alive_count.load(SeqCst), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DropCheck<'a> {
|
||||||
|
info: &'a DropInfo,
|
||||||
|
was_dropped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DropCheck<'a> {
|
||||||
|
fn new(info: &'a DropInfo) -> Self {
|
||||||
|
info.alive_count.fetch_add(1, SeqCst);
|
||||||
|
|
||||||
|
Self { info, was_dropped: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DropCheck<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.was_dropped {
|
||||||
|
self.info.dropped_twice.store(true, SeqCst);
|
||||||
|
}
|
||||||
|
self.was_dropped = true;
|
||||||
|
|
||||||
|
self.info.alive_count.fetch_sub(1, SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(info: &DropInfo, len: usize, panic_at: usize) -> impl Iterator<Item = DropCheck<'_>> {
|
||||||
|
(0..len).map(move |i| {
|
||||||
|
if i == panic_at {
|
||||||
|
panic!("intended panic");
|
||||||
|
}
|
||||||
|
DropCheck::new(info)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn check<const N: usize>(len: usize, panic_at: usize) {
|
||||||
|
check_drops(|info| {
|
||||||
|
iter(info, len, panic_at).map_windows(|_: &[_; N]| {}).last();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn check_drops(f: impl FnOnce(&DropInfo)) {
|
||||||
|
let info = DropInfo::new();
|
||||||
|
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||||
|
f(&info);
|
||||||
|
}));
|
||||||
|
info.check();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_iter_panic_n1() {
|
||||||
|
check::<1>(0, 100);
|
||||||
|
check::<1>(1, 100);
|
||||||
|
check::<1>(2, 100);
|
||||||
|
check::<1>(13, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_iter_panic_n2() {
|
||||||
|
check::<2>(0, 100);
|
||||||
|
check::<2>(1, 100);
|
||||||
|
check::<2>(2, 100);
|
||||||
|
check::<2>(3, 100);
|
||||||
|
check::<2>(13, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_iter_panic_n5() {
|
||||||
|
check::<5>(0, 100);
|
||||||
|
check::<5>(1, 100);
|
||||||
|
check::<5>(2, 100);
|
||||||
|
check::<5>(13, 100);
|
||||||
|
check::<5>(30, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panic_in_first_batch() {
|
||||||
|
check::<1>(7, 0);
|
||||||
|
|
||||||
|
check::<2>(7, 0);
|
||||||
|
check::<2>(7, 1);
|
||||||
|
|
||||||
|
check::<3>(7, 0);
|
||||||
|
check::<3>(7, 1);
|
||||||
|
check::<3>(7, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panic_in_middle() {
|
||||||
|
check::<1>(7, 1);
|
||||||
|
check::<1>(7, 5);
|
||||||
|
check::<1>(7, 6);
|
||||||
|
|
||||||
|
check::<2>(7, 2);
|
||||||
|
check::<2>(7, 5);
|
||||||
|
check::<2>(7, 6);
|
||||||
|
|
||||||
|
check::<5>(13, 5);
|
||||||
|
check::<5>(13, 8);
|
||||||
|
check::<5>(13, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn len_equals_n() {
|
||||||
|
check::<1>(1, 100);
|
||||||
|
check::<1>(1, 0);
|
||||||
|
|
||||||
|
check::<2>(2, 100);
|
||||||
|
check::<2>(2, 0);
|
||||||
|
check::<2>(2, 1);
|
||||||
|
|
||||||
|
check::<5>(5, 100);
|
||||||
|
check::<5>(5, 0);
|
||||||
|
check::<5>(5, 1);
|
||||||
|
check::<5>(5, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_n1() {
|
||||||
|
assert_eq!("".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec![]);
|
||||||
|
assert_eq!("x".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec!['x']);
|
||||||
|
assert_eq!("abcd".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec!['a', 'b', 'c', 'd']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_n2() {
|
||||||
|
assert_eq!(
|
||||||
|
"".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(),
|
||||||
|
<Vec<[char; 2]>>::new(),
|
||||||
|
);
|
||||||
|
assert_eq!("ab".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(), vec![['a', 'b']]);
|
||||||
|
assert_eq!(
|
||||||
|
"abcd".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(),
|
||||||
|
vec![['a', 'b'], ['b', 'c'], ['c', 'd']],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_case_from_pr_82413_comment() {
|
||||||
|
for () in std::iter::repeat("0".to_owned()).map_windows(|_: &[_; 3]| {}).take(4) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic = "array in `Iterator::map_windows` must contain more than 0 elements"]
|
||||||
|
fn check_zero_window() {
|
||||||
|
let _ = std::iter::repeat(0).map_windows(|_: &[_; 0]| ());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_sized_type() {
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct Data;
|
||||||
|
let data: Vec<_> =
|
||||||
|
std::iter::repeat(Data).take(10).map_windows(|arr: &[Data; 5]| *arr).collect();
|
||||||
|
assert_eq!(data, [[Data; 5]; 6]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic = "array size of `Iterator::map_windows` is too large"]
|
||||||
|
fn test_too_large_array_size() {
|
||||||
|
let _ = std::iter::repeat(()).map_windows(|arr: &[(); usize::MAX]| *arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_laziness() {
|
||||||
|
let counter = AtomicUsize::new(0);
|
||||||
|
let mut iter = (0..5)
|
||||||
|
.inspect(|_| {
|
||||||
|
counter.fetch_add(1, SeqCst);
|
||||||
|
})
|
||||||
|
.map_windows(|arr: &[i32; 2]| *arr);
|
||||||
|
assert_eq!(counter.load(SeqCst), 0);
|
||||||
|
|
||||||
|
assert_eq!(iter.next(), Some([0, 1]));
|
||||||
|
// The first iteration consumes N items (N = 2).
|
||||||
|
assert_eq!(counter.load(SeqCst), 2);
|
||||||
|
|
||||||
|
assert_eq!(iter.next(), Some([1, 2]));
|
||||||
|
assert_eq!(counter.load(SeqCst), 3);
|
||||||
|
|
||||||
|
assert_eq!(iter.next(), Some([2, 3]));
|
||||||
|
assert_eq!(counter.load(SeqCst), 4);
|
||||||
|
|
||||||
|
assert_eq!(iter.next(), Some([3, 4]));
|
||||||
|
assert_eq!(counter.load(SeqCst), 5);
|
||||||
|
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
assert_eq!(counter.load(SeqCst), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_size_hint() {
|
||||||
|
struct SizeHintCheckHelper((usize, Option<usize>));
|
||||||
|
|
||||||
|
impl Iterator for SizeHintCheckHelper {
|
||||||
|
type Item = i32;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<i32> {
|
||||||
|
let (ref mut lo, ref mut hi) = self.0;
|
||||||
|
let next = (*hi != Some(0)).then_some(0);
|
||||||
|
*lo = lo.saturating_sub(1);
|
||||||
|
if let Some(hi) = hi {
|
||||||
|
*hi = hi.saturating_sub(1);
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_size_hint<const N: usize>(
|
||||||
|
size_hint: (usize, Option<usize>),
|
||||||
|
mut mapped_size_hint: (usize, Option<usize>),
|
||||||
|
) {
|
||||||
|
let mut iter = SizeHintCheckHelper(size_hint);
|
||||||
|
let mut mapped_iter = iter.by_ref().map_windows(|_: &[_; N]| ());
|
||||||
|
while mapped_iter.size_hint().0 > 0 {
|
||||||
|
assert_eq!(mapped_iter.size_hint(), mapped_size_hint);
|
||||||
|
assert!(mapped_iter.next().is_some());
|
||||||
|
mapped_size_hint.0 -= 1;
|
||||||
|
mapped_size_hint.1 = mapped_size_hint.1.map(|hi| hi.saturating_sub(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_size_hint::<1>((0, None), (0, None));
|
||||||
|
check_size_hint::<1>((0, Some(0)), (0, Some(0)));
|
||||||
|
check_size_hint::<1>((0, Some(2)), (0, Some(2)));
|
||||||
|
check_size_hint::<1>((1, None), (1, None));
|
||||||
|
check_size_hint::<1>((1, Some(1)), (1, Some(1)));
|
||||||
|
check_size_hint::<1>((1, Some(4)), (1, Some(4)));
|
||||||
|
check_size_hint::<1>((5, None), (5, None));
|
||||||
|
check_size_hint::<1>((5, Some(5)), (5, Some(5)));
|
||||||
|
check_size_hint::<1>((5, Some(10)), (5, Some(10)));
|
||||||
|
|
||||||
|
check_size_hint::<2>((0, None), (0, None));
|
||||||
|
check_size_hint::<2>((0, Some(0)), (0, Some(0)));
|
||||||
|
check_size_hint::<2>((0, Some(2)), (0, Some(1)));
|
||||||
|
check_size_hint::<2>((1, None), (0, None));
|
||||||
|
check_size_hint::<2>((1, Some(1)), (0, Some(0)));
|
||||||
|
check_size_hint::<2>((1, Some(4)), (0, Some(3)));
|
||||||
|
check_size_hint::<2>((5, None), (4, None));
|
||||||
|
check_size_hint::<2>((5, Some(5)), (4, Some(4)));
|
||||||
|
check_size_hint::<2>((5, Some(10)), (4, Some(9)));
|
||||||
|
|
||||||
|
check_size_hint::<5>((0, None), (0, None));
|
||||||
|
check_size_hint::<5>((0, Some(0)), (0, Some(0)));
|
||||||
|
check_size_hint::<5>((0, Some(2)), (0, Some(0)));
|
||||||
|
check_size_hint::<5>((1, None), (0, None));
|
||||||
|
check_size_hint::<5>((1, Some(1)), (0, Some(0)));
|
||||||
|
check_size_hint::<5>((1, Some(4)), (0, Some(0)));
|
||||||
|
check_size_hint::<5>((5, None), (1, None));
|
||||||
|
check_size_hint::<5>((5, Some(5)), (1, Some(1)));
|
||||||
|
check_size_hint::<5>((5, Some(10)), (1, Some(6)));
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
mod inspect;
|
mod inspect;
|
||||||
mod intersperse;
|
mod intersperse;
|
||||||
mod map;
|
mod map;
|
||||||
|
mod map_windows;
|
||||||
mod peekable;
|
mod peekable;
|
||||||
mod scan;
|
mod scan;
|
||||||
mod skip;
|
mod skip;
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
#![feature(is_ascii_octdigit)]
|
#![feature(is_ascii_octdigit)]
|
||||||
#![feature(get_many_mut)]
|
#![feature(get_many_mut)]
|
||||||
#![feature(offset_of)]
|
#![feature(offset_of)]
|
||||||
|
#![feature(iter_map_windows)]
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
#![deny(fuzzy_provenance_casts)]
|
#![deny(fuzzy_provenance_casts)]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue