wyz/
bidi.rs

1//! A bidirectional iterator that only checks its direction once.
2
3use core::iter::FusedIterator;
4
5/** An iterator that conditionally reverses itself upon creation.
6
7This acts as a conditional `.rev()` adapter: it reverses the direction of
8iteration, swapping `.next()` and `.next_back()`, but only if the provided
9condition is true. If the condition is false, then iteration proceeds normally.
10
11The condition is only evaluated when the adapter is constructed, and all calls
12to drive the iterator are branchless.
13
14## Usage
15
16This can be constructed directly with `Bidi::new(some_iterator)`, but it is more
17conveniently accessed as an extension method on double-ended iterators. Import
18`wyz::BidiIterator` or `wyz::bidi::*;` and then call `.bidi()` in your iterator
19adapter sequence.
20
21## Examples
22
23This can be used to hand-roll a `memmove` implementation that correctly handles
24the case where the destination begins in the source region:
25
26```rust
27use wyz::bidi::*;
28
29unsafe fn memmove<T>(from: *const T, to: *mut T, count: usize) {
30 let src = from .. from.add(count);
31 let rev = src.contains(&(to as *const T));
32 for idx in (0 .. count).bidi(rev) {
33  to.add(idx).write(from.add(idx).read());
34 }
35}
36```
37
38This example detects if `to` is between `from` and `from.add(count)` and uses
39that to determine whether to iterate forward from `0` to `count - 1` or backward
40from `count - 1` to `0`.
41**/
42pub struct Bidi<I>
43where I: DoubleEndedIterator
44{
45	/// The iterator being governed.
46	inner: I,
47	/// A pointer to either `I::next` or `I::next_back`.
48	next: fn(&mut I) -> Option<<I as Iterator>::Item>,
49	/// A pointer to either `I::next_back` or `I::next`.
50	next_back: fn(&mut I) -> Option<<I as Iterator>::Item>,
51	/// A pointer to either `I::nth` or `I::nth_back`.
52	nth: fn(&mut I, usize) -> Option<<I as Iterator>::Item>,
53	/// A pointer to either `I::nth_back` or `I::nth`.
54	nth_back: fn(&mut I, usize) -> Option<<I as Iterator>::Item>,
55}
56
57impl<I> Bidi<I>
58where I: DoubleEndedIterator
59{
60	/// Applies the `Bidi` adapter to a double-ended iterator and selects the
61	/// direction of traversal.
62	///
63	/// ## Parameters
64	///
65	/// - `iter`: anything that can be made into a double-ended iterator
66	/// - `cond`: determines whether iteration proceeds ordinarily or reversed
67	pub fn new<II>(iter: II, cond: bool) -> Self
68	where II: IntoIterator<IntoIter = I> {
69		let inner = iter.into_iter();
70		if cond {
71			Self {
72				inner,
73				next: <I as DoubleEndedIterator>::next_back,
74				next_back: <I as Iterator>::next,
75				nth: <I as DoubleEndedIterator>::nth_back,
76				nth_back: <I as Iterator>::nth,
77			}
78		}
79		else {
80			Self {
81				inner,
82				next: <I as Iterator>::next,
83				next_back: <I as DoubleEndedIterator>::next_back,
84				nth: <I as Iterator>::nth,
85				nth_back: <I as DoubleEndedIterator>::nth_back,
86			}
87		}
88	}
89}
90
91impl<I> Iterator for Bidi<I>
92where I: DoubleEndedIterator
93{
94	type Item = <I as Iterator>::Item;
95
96	#[inline]
97	fn next(&mut self) -> Option<Self::Item> {
98		(&mut self.next)(&mut self.inner)
99	}
100
101	#[inline]
102	fn nth(&mut self, n: usize) -> Option<Self::Item> {
103		(&mut self.nth)(&mut self.inner, n)
104	}
105
106	#[inline]
107	#[cfg(not(tarpaulin_include))]
108	fn size_hint(&self) -> (usize, Option<usize>) {
109		self.inner.size_hint()
110	}
111
112	#[inline]
113	#[cfg(not(tarpaulin_include))]
114	fn count(self) -> usize {
115		self.inner.count()
116	}
117
118	#[inline]
119	#[cfg(not(tarpaulin_include))]
120	fn last(mut self) -> Option<Self::Item> {
121		self.next_back()
122	}
123}
124
125impl<I> DoubleEndedIterator for Bidi<I>
126where I: DoubleEndedIterator
127{
128	#[inline]
129	fn next_back(&mut self) -> Option<Self::Item> {
130		(&mut self.next_back)(&mut self.inner)
131	}
132
133	#[inline]
134	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
135		(&mut self.nth_back)(&mut self.inner, n)
136	}
137}
138
139impl<I> ExactSizeIterator for Bidi<I>
140where I: DoubleEndedIterator + ExactSizeIterator
141{
142	#[inline]
143	#[cfg(not(tarpaulin_include))]
144	fn len(&self) -> usize {
145		self.inner.len()
146	}
147}
148
149impl<I> FusedIterator for Bidi<I> where I: DoubleEndedIterator + FusedIterator
150{
151}
152
153/// Extension trait that provides `.bidi()` for all double-ended iterators.
154pub trait BidiIterator
155where
156	Self: Sized + IntoIterator,
157	<Self as IntoIterator>::IntoIter: DoubleEndedIterator,
158{
159	/// Conditionally reverses the direction of iteration.
160	///
161	/// When `cond` is true, this adapter swaps the `next` and `nth` methods
162	/// with `next_back` and `nth_back`. The resulting iterator is equivalent to
163	/// `if cond { self.rev() } else { self }`.
164	///
165	/// ## Examples
166	///
167	/// ```rust
168	/// use wyz::BidiIterator;
169	///
170	/// let data = [1, 2, 3];
171	/// let mut iter = data.iter().copied().bidi(false);
172	/// assert_eq!(iter.next(), Some(1));
173	/// assert_eq!(iter.next_back(), Some(3));
174	///
175	/// let mut iter = data.iter().copied().bidi(true);
176	/// assert_eq!(iter.next(), Some(3));
177	/// assert_eq!(iter.next_back(), Some(1));
178	/// ```
179	fn bidi(self, cond: bool) -> Bidi<Self::IntoIter> {
180		Bidi::new(self, cond)
181	}
182}
183
184impl<I> BidiIterator for I
185where
186	I: Sized + IntoIterator,
187	<I as IntoIterator>::IntoIter: DoubleEndedIterator,
188{
189}
190
191#[cfg(test)]
192mod tests {
193	use super::*;
194
195	#[test]
196	fn forward() {
197		let mut iter = (0 .. 6).bidi(false);
198
199		assert_eq!(iter.next(), Some(0));
200		assert_eq!(iter.next_back(), Some(5));
201		assert_eq!(iter.nth(1), Some(2));
202		assert_eq!(iter.nth_back(1), Some(3));
203		assert!(iter.next().is_none());
204	}
205
206	#[test]
207	fn reverse() {
208		let mut iter = (0 .. 6).bidi(true);
209
210		assert_eq!(iter.next(), Some(5));
211		assert_eq!(iter.next_back(), Some(0));
212		assert_eq!(iter.nth(1), Some(3));
213		assert_eq!(iter.nth_back(1), Some(2));
214		assert!(iter.next().is_none());
215	}
216}