Skip to content

Commit 4fb1c5e

Browse files
committed
Change Position to a struct to avoid Position::Only gotcha
1 parent e770271 commit 4fb1c5e

File tree

2 files changed

+72
-48
lines changed

2 files changed

+72
-48
lines changed

src/lib.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,8 +1945,8 @@ pub trait Itertools: Iterator {
19451945
pad_tail::pad_using(self, min, f)
19461946
}
19471947

1948-
/// Return an iterator adaptor that combines each element with a `Position` to
1949-
/// ease special-case handling of the first, last, or [singular](Position::Only) elements.
1948+
/// Return an iterator adaptor that combines each element with a `Position`
1949+
/// to ease special-case handling of the first or last elements.
19501950
///
19511951
/// Iterator element type is
19521952
/// [`(Position, Self::Item)`](Position)
@@ -1955,14 +1955,21 @@ pub trait Itertools: Iterator {
19551955
/// use itertools::{Itertools, Position};
19561956
///
19571957
/// let it = (0..4).with_position();
1958-
/// itertools::assert_equal(it,
1959-
/// vec![(Position::First, 0),
1960-
/// (Position::Middle, 1),
1961-
/// (Position::Middle, 2),
1962-
/// (Position::Last, 3)]);
1963-
///
1958+
/// itertools::assert_equal(
1959+
/// it,
1960+
/// vec![
1961+
/// (Position { is_first: true, is_last: false }, 0),
1962+
/// (Position { is_first: false, is_last: false }, 1),
1963+
/// (Position { is_first: false, is_last: false }, 2),
1964+
/// (Position { is_first: false, is_last: true }, 3),
1965+
/// ],
1966+
/// );
1967+
19641968
/// let it = (0..1).with_position();
1965-
/// itertools::assert_equal(it, vec![(Position::Only, 0)]);
1969+
/// itertools::assert_equal(
1970+
/// it,
1971+
/// vec![(Position { is_first: true, is_last: true }, 0)],
1972+
/// );
19661973
/// ```
19671974
fn with_position(self) -> WithPosition<Self>
19681975
where

src/with_position.rs

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -45,49 +45,48 @@ where
4545
/// The first component of the value yielded by `WithPosition`.
4646
/// Indicates the position of this element in the iterator results.
4747
///
48-
/// When handling the first or last position,
49-
/// remember to consider the special case of [`Position::Only`].
50-
///
5148
/// See [`.with_position()`](crate::Itertools::with_position) for more information.
5249
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53-
pub enum Position {
54-
/// This is the first element, and there is more than one element.
55-
First,
56-
/// This is neither the first nor the last element.
57-
Middle,
58-
/// This is the last element, and there was more than one element.
59-
Last,
60-
/// This is the only element.
61-
Only,
50+
pub struct Position {
51+
/// This is the initial element (also true if there's exactly one element)
52+
pub is_first: bool,
53+
/// This is the final element (also true if there's exactly one element)
54+
pub is_last: bool,
55+
}
56+
57+
impl Position {
58+
/// This is the first and the last element at the same time, and there are no more elements
59+
pub fn is_exactly_one(self) -> bool {
60+
self.is_first && self.is_last
61+
}
62+
63+
/// This is neither first nor last element, and there will be more elements
64+
pub fn is_middle(self) -> bool {
65+
!self.is_first && !self.is_last
66+
}
67+
68+
/// This is the initial element (also true if there's exactly one element)
69+
pub fn is_first(self) -> bool {
70+
self.is_first
71+
}
72+
73+
/// This is the final element (also true if there's exactly one element)
74+
pub fn is_last(self) -> bool {
75+
self.is_last
76+
}
6277
}
6378

6479
impl<I: Iterator> Iterator for WithPosition<I> {
6580
type Item = (Position, I::Item);
6681

6782
fn next(&mut self) -> Option<Self::Item> {
68-
match self.peekable.next() {
69-
Some(item) => {
70-
if !self.handled_first {
71-
// Haven't seen the first item yet, and there is one to give.
72-
self.handled_first = true;
73-
// Peek to see if this is also the last item,
74-
// in which case tag it as `Only`.
75-
match self.peekable.peek() {
76-
Some(_) => Some((Position::First, item)),
77-
None => Some((Position::Only, item)),
78-
}
79-
} else {
80-
// Have seen the first item, and there's something left.
81-
// Peek to see if this is the last item.
82-
match self.peekable.peek() {
83-
Some(_) => Some((Position::Middle, item)),
84-
None => Some((Position::Last, item)),
85-
}
86-
}
87-
}
88-
// Iterator is finished.
89-
None => None,
90-
}
83+
let item = self.peekable.next()?;
84+
85+
let is_last = self.peekable.peek().is_none();
86+
let is_first = !self.handled_first;
87+
self.handled_first = true;
88+
89+
Some((Position { is_first, is_last }, item))
9190
}
9291

9392
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -105,18 +104,36 @@ impl<I: Iterator> Iterator for WithPosition<I> {
105104
match self.peekable.next() {
106105
Some(second) => {
107106
let first = std::mem::replace(&mut head, second);
108-
init = f(init, (Position::First, first));
107+
let position = Position {
108+
is_first: true,
109+
is_last: false,
110+
};
111+
init = f(init, (position, first));
112+
}
113+
None => {
114+
let position = Position {
115+
is_first: true,
116+
is_last: true,
117+
};
118+
return f(init, (position, head));
109119
}
110-
None => return f(init, (Position::Only, head)),
111120
}
112121
}
113122
// Have seen the first item, and there's something left.
114123
init = self.peekable.fold(init, |acc, mut item| {
115124
std::mem::swap(&mut head, &mut item);
116-
f(acc, (Position::Middle, item))
125+
let position = Position {
126+
is_first: false,
127+
is_last: false,
128+
};
129+
f(acc, (position, item))
117130
});
131+
let position = Position {
132+
is_first: false,
133+
is_last: true,
134+
};
118135
// The "head" is now the last item.
119-
init = f(init, (Position::Last, head));
136+
init = f(init, (position, head));
120137
}
121138
init
122139
}

0 commit comments

Comments
 (0)