Skip to content

Commit 7a655bf

Browse files
committed
feat(Itertools): add strip_prefix and strip_prefix_by methods
1 parent 5707384 commit 7a655bf

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5120,6 +5120,58 @@ pub trait Itertools: Iterator {
51205120
_ => Err(sh),
51215121
}
51225122
}
5123+
5124+
/// Removes a prefix from the iterator, returning the rest.
5125+
///
5126+
/// If `self` begins with all the items yielded by `prefix` (in order), this
5127+
/// returns `Some` of the iterator advanced past that prefix. Otherwise it
5128+
/// returns `None`. This is the iterator analogue of [`str::strip_prefix`].
5129+
///
5130+
/// See [`strip_prefix_by`](Itertools::strip_prefix_by) for a variant
5131+
/// taking an explicit equality predicate.
5132+
///
5133+
/// ```
5134+
/// use itertools::Itertools;
5135+
///
5136+
/// assert_eq!((1..6).strip_prefix([1, 2]).map(Itertools::collect_vec), Some(vec![3, 4, 5]));
5137+
/// assert_eq!((1..6).strip_prefix([1, 9]).map(Itertools::collect_vec), None);
5138+
/// assert_eq!((1..6).strip_prefix(std::iter::empty()).map(Itertools::collect_vec), Some(vec![1, 2, 3, 4, 5]));
5139+
/// ```
5140+
fn strip_prefix<J>(self, prefix: J) -> Option<Self>
5141+
where
5142+
Self: Sized,
5143+
J: IntoIterator,
5144+
Self::Item: PartialEq<J::Item>,
5145+
{
5146+
self.strip_prefix_by(prefix, |a, b| a == b)
5147+
}
5148+
5149+
/// Removes a prefix from the iterator using `eq` to compare items.
5150+
///
5151+
/// If `self` begins with all the items yielded by `prefix` (in order, as
5152+
/// judged by `eq`), this returns `Some` of the iterator advanced past that
5153+
/// prefix. Otherwise it returns `None`. This allows the prefix items to
5154+
/// have a different type than `Self::Item`.
5155+
///
5156+
/// ```
5157+
/// use itertools::Itertools;
5158+
///
5159+
/// let path = ["home", "user", "file"];
5160+
/// let stripped = path.iter().strip_prefix_by(["home", "user"], |a, b| **a == *b);
5161+
/// assert_eq!(stripped.map(Itertools::collect_vec), Some(vec![&"file"]));
5162+
/// ```
5163+
fn strip_prefix_by<J, F>(mut self, prefix: J, mut eq: F) -> Option<Self>
5164+
where
5165+
Self: Sized,
5166+
J: IntoIterator,
5167+
F: FnMut(&Self::Item, &J::Item) -> bool,
5168+
{
5169+
let matched = prefix.into_iter().try_for_each(|wanted| match self.next() {
5170+
Some(got) if eq(&got, &wanted) => Ok(()),
5171+
_ => Err(()),
5172+
});
5173+
matched.ok().map(|()| self)
5174+
}
51235175
}
51245176

51255177
impl<T> Itertools for T where T: Iterator + ?Sized {}

tests/quick.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,4 +2098,24 @@ quickcheck! {
20982098
itertools::equal(v.iter().tail(n), result)
20992099
&& itertools::equal(v.iter().filter(|_| true).tail(n), result)
21002100
}
2101+
2102+
fn strip_prefix_matches_str(haystack: String, needle: String) -> bool {
2103+
let expected = haystack.strip_prefix(&needle);
2104+
let got: Option<String> = haystack
2105+
.chars()
2106+
.strip_prefix(needle.chars())
2107+
.map(Iterator::collect);
2108+
got.as_deref() == expected
2109+
}
2110+
2111+
fn strip_prefix_by_matches_strip_prefix(v: Vec<i32>, n: u8) -> bool {
2112+
let prefix: Vec<i32> = v.iter().take(n as usize).copied().collect();
2113+
let by_eq = v.iter().strip_prefix_by(&prefix, |a, b| **a == **b);
2114+
let plain = v.iter().strip_prefix(prefix.iter());
2115+
match (by_eq, plain) {
2116+
(Some(a), Some(b)) => itertools::equal(a, b),
2117+
(None, None) => true,
2118+
_ => false,
2119+
}
2120+
}
21012121
}

0 commit comments

Comments
 (0)