diff --git a/README.md b/README.md index 10fd936..3685d68 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ data.group_by(|x| x % 2); // HashMap::from([(0, vec![2]), (1, vec![1, 3] | *minmax_by* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | *minmax_by_key* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | *minmax_item* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| *move_item* | :heavy_check_mark: | | | | +| *move_at* | :heavy_check_mark: | | | | | *multicombinations* | :heavy_check_mark: | | | | | *pad_left* | :heavy_check_mark: | | | | | *pad_left_with* | :heavy_check_mark: | | | | @@ -122,8 +122,9 @@ data.group_by(|x| x % 2); // HashMap::from([(0, vec![2]), (1, vec![1, 3] | *permutations* | :heavy_check_mark: | | | | | *position* | :heavy_check_mark: | :heavy_check_mark: | | | | *positions* | :heavy_check_mark: | :heavy_check_mark: | | | -| *position_of* | :heavy_check_mark: | :heavy_check_mark: | | | | *positions_of* | :heavy_check_mark: | :heavy_check_mark: | | | +| *position_of* | :heavy_check_mark: | :heavy_check_mark: | | | +| *position_sequence* | :heavy_check_mark: | :heavy_check_mark: | | | | *powerset* | :heavy_check_mark: | | :heavy_check_mark: | | | *product* | :heavy_check_mark: | | :heavy_check_mark: | | | *product_keys* | | | | :heavy_check_mark: | @@ -155,9 +156,7 @@ data.group_by(|x| x % 2); // HashMap::from([(0, vec![2]), (1, vec![1, 3] | *splice* | :heavy_check_mark: | | | | | *step_by* | :heavy_check_mark: | | | | | *subset* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| *subsequence* | :heavy_check_mark: | :heavy_check_mark: | | | | *superset* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| *supersequence* | :heavy_check_mark: | :heavy_check_mark: | | | | *sum* | :heavy_check_mark: | | :heavy_check_mark: | | | *sum_keys* | | | | :heavy_check_mark: | | *sum_values* | | | | :heavy_check_mark: | diff --git a/src/extensions/collections/linked_list.rs b/src/extensions/collections/linked_list.rs index f1aa3b4..ae15138 100644 --- a/src/extensions/collections/linked_list.rs +++ b/src/extensions/collections/linked_list.rs @@ -122,6 +122,14 @@ impl Ordered for LinkedList { fn positions(&self, predicate: impl FnMut(&Item) -> bool) -> Vec { positions(self.iter(), predicate) } + + #[inline] + fn position_sequence<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> Option + where + Item: PartialEq + 'a, + { + position_sequence(self.iter(), elements) + } } impl Reversible for LinkedList { diff --git a/src/extensions/collections/slice.rs b/src/extensions/collections/slice.rs index ff50ae5..4064a40 100644 --- a/src/extensions/collections/slice.rs +++ b/src/extensions/collections/slice.rs @@ -122,6 +122,14 @@ impl Ordered for [Item] { fn positions(&self, predicate: impl FnMut(&Item) -> bool) -> Vec { positions(self.iter(), predicate) } + + #[inline] + fn position_sequence<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> Option + where + Item: PartialEq + 'a, + { + position_sequence(self.iter(), elements) + } } impl Reversible for [Item] { diff --git a/src/extensions/collections/vec.rs b/src/extensions/collections/vec.rs index 522e1b4..bc7c66e 100644 --- a/src/extensions/collections/vec.rs +++ b/src/extensions/collections/vec.rs @@ -121,6 +121,14 @@ impl Ordered for Vec { fn positions(&self, predicate: impl FnMut(&Item) -> bool) -> Vec { positions(self.iter(), predicate) } + + #[inline] + fn position_sequence<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> Option + where + Item: PartialEq + 'a, + { + position_sequence(self.iter(), elements) + } } impl Reversible for Vec { @@ -128,7 +136,7 @@ impl Reversible for Vec { fn common_suffix_length<'a, I>(&'a self, elements: &'a impl Iterable = &'a Item, Iterator<'a> = I>) -> usize where I: DoubleEndedIterator, - Item: PartialEq + 'a + Item: PartialEq + 'a, { common_suffix_length(self.iter().rev(), elements) } diff --git a/src/extensions/collections/vec_deque.rs b/src/extensions/collections/vec_deque.rs index 506df94..a01f228 100644 --- a/src/extensions/collections/vec_deque.rs +++ b/src/extensions/collections/vec_deque.rs @@ -122,6 +122,14 @@ impl Ordered for VecDeque { fn positions(&self, predicate: impl FnMut(&Item) -> bool) -> Vec { positions(self.iter(), predicate) } + + #[inline] + fn position_sequence<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> Option + where + Item: PartialEq + 'a, + { + position_sequence(self.iter(), elements) + } } impl Reversible for VecDeque { diff --git a/src/extensions/ordered.rs b/src/extensions/ordered.rs index b7e1255..6be4fd7 100644 --- a/src/extensions/ordered.rs +++ b/src/extensions/ordered.rs @@ -220,6 +220,48 @@ pub trait Ordered { { self.positions(|x| x == element) } + + /// Searches for a sub-sequence in a collection, returning its index. + /// + /// After finding a starting element of specified sequence in a collection, + /// `position_seq()` compares each element of the collection with the specified value, + /// and if all of them matche, then `position_seq()` returns [`Some(start_index)`]. + /// If any of the elements do not match, it returns [`None`]. + /// + /// `position_seq()` is short-circuiting; in other words, it will stop + /// processing as soon as it finds a matching sequence. + /// + /// Returns `Some(0)` if specified sequence is empty. + /// + /// # Overflow Behavior + /// + /// The method does no guarding against overflows, so if there are more + /// than [`usize::MAX`] non-matching elements, it either produces the wrong + /// result or panics. If debug assertions are enabled, a panic is guaranteed. + /// + /// # Panics + /// + /// This function might panic if the collection has more than `usize::MAX` + /// non-matching elements. + /// + /// [`Some(start_index)`]: Some + /// [`Some(0)`]: Some + /// + /// # Example + /// + /// ``` + /// use crate::cantrip::*; + /// + /// let a = vec![1, 2, 3, 4, 5]; + /// + /// assert_eq!(a.position_sequence(&vec![2, 3, 4]), Some(1)); + /// assert_eq!(a.position_sequence(&vec![]), Some(0)); + /// + /// assert_eq!(a.position_sequence(&vec![1, 3]), None); + /// ``` + fn position_sequence<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> Option + where + Item: PartialEq + 'a; } pub(crate) fn equivalent<'a, Item>( @@ -302,3 +344,26 @@ where { iterator.enumerate().filter(|(_, item)| predicate(item)).map(|(index, _)| index).collect() } + +pub(crate) fn position_sequence<'a, Item>( + mut iterator: impl Iterator, elements: &'a impl Iterable = &'a Item>, +) -> Option +where + Item: PartialEq + 'a, +{ + let mut elements_iterator = elements.iterator(); + if let Some(first_element) = elements_iterator.next() { + if let Some(start_index) = iterator.position(|item| item == first_element) { + for (item, element) in iterator.zip(elements_iterator) { + if item != element { + return None; + } + } + Some(start_index) + } else { + None + } + } else { + Some(0) + } +} diff --git a/src/extensions/sequence.rs b/src/extensions/sequence.rs index 710ce2c..24b6192 100644 --- a/src/extensions/sequence.rs +++ b/src/extensions/sequence.rs @@ -24,8 +24,6 @@ pub trait Sequence { // coalesce // chunked_by // permutations - // subsequence - // supersequence // variations // variations_repetitive @@ -75,16 +73,16 @@ pub trait Sequence { /// let a = vec![1, 2]; /// let e: Vec = Vec::new(); /// - /// assert_eq!(a.add_all_at(0, vec![3, 4]), [3, 4, 1, 2]); + /// assert_eq!(a.add_all_at(0, vec![3, 4]), vec![3, 4, 1, 2]); /// # let a = source.clone(); - /// assert_eq!(a.add_all_at(1, vec![3, 4]), [1, 3, 4, 2]); + /// assert_eq!(a.add_all_at(1, vec![3, 4]), vec![1, 3, 4, 2]); /// # let a = source.clone(); - /// assert_eq!(a.add_all_at(2, vec![3, 4]), [1, 2, 3, 4]); + /// assert_eq!(a.add_all_at(2, vec![3, 4]), vec![1, 2, 3, 4]); /// # let a = source.clone(); - /// assert_eq!(e.add_all_at(0, vec![1, 2]), [1, 2]); + /// assert_eq!(e.add_all_at(0, vec![1, 2]), vec![1, 2]); /// /// # let a = source.clone(); - /// assert_eq!(a.add_all_at(3, vec![3, 4]), [1, 2]); + /// assert_eq!(a.add_all_at(3, vec![3, 4]), vec![1, 2]); /// ``` fn add_all_at(self, index: usize, elements: impl IntoIterator) -> Self where @@ -675,22 +673,22 @@ pub trait Sequence { /// # let source = vec![1, 2, 3, 4, 5]; /// let a = vec![1, 2, 3, 4, 5]; /// - /// assert_eq!(a.move_item(1, 3), vec![1, 3, 4, 2, 5]); + /// assert_eq!(a.move_at(1, 3), vec![1, 3, 4, 2, 5]); /// # let a = source.clone(); - /// assert_eq!(a.move_item(2, 4), vec![1, 2, 4, 5, 3]); + /// assert_eq!(a.move_at(2, 4), vec![1, 2, 4, 5, 3]); /// # let a = source.clone(); - /// assert_eq!(a.move_item(0, 5), vec![2, 3, 4, 5]); + /// assert_eq!(a.move_at(0, 5), vec![2, 3, 4, 5]); /// # let a = source.clone(); - /// assert_eq!(a.move_item(3, 1), vec![1, 4, 2, 3, 5]); + /// assert_eq!(a.move_at(3, 1), vec![1, 4, 2, 3, 5]); /// # let a = source.clone(); - /// assert_eq!(a.move_item(4, 0), vec![5, 1, 2, 3, 4]); + /// assert_eq!(a.move_at(4, 0), vec![5, 1, 2, 3, 4]); /// /// # let a = source.clone(); - /// assert_eq!(a.move_item(3, 3), vec![1, 2, 3, 4, 5]); + /// assert_eq!(a.move_at(3, 3), vec![1, 2, 3, 4, 5]); /// # let a = source.clone(); - /// assert_eq!(a.move_item(5, 1), vec![1, 2, 3, 4, 5]); + /// assert_eq!(a.move_at(5, 1), vec![1, 2, 3, 4, 5]); /// ``` - fn move_item(self, source_index: usize, target_index: usize) -> Self + fn move_at(self, source_index: usize, target_index: usize) -> Self where Self: IntoIterator + FromIterator, { @@ -894,6 +892,81 @@ pub trait Sequence { values.into_iter().cycle().take(size).collect() } + /// Creates a collection by replacing an element at specified index + /// in the original collection. + /// + /// if the specified index exceeds the collection size, no elements are replaced. + /// + /// # Example + /// + /// ``` + /// use cantrip::*; + /// + /// # let source = vec![1, 2, 3]; + /// let a = vec![1, 2, 3]; + /// let e: Vec = Vec::new(); + /// + /// assert_eq!(a.replace_at(1, 4), vec![1, 4, 3]); + /// + /// # let a = source.clone(); + /// assert_eq!(a.replace_at(3, 5), vec![1, 2, 3]); + /// assert_eq!(e.replace_at(0, 1), vec![]); + /// ``` + #[inline] + fn replace_at(self, index: usize, element: Item) -> Self + where + Self: IntoIterator + FromIterator, + { + self.replace_all_at(index..(index + 1), iter::once(element)) + } + + /// Creates a collection by replacing all elements at specified indices in a collection + /// by elements from another collection. + /// + /// if the specified index exceeds the collection size, no elements are replaced. + /// + /// # Example + /// + /// ``` + /// use cantrip::*; + /// + /// # let source = vec![1, 2, 3]; + /// let a = vec![1, 2, 3]; + /// let e: Vec = Vec::new(); + /// + /// assert_eq!(a.replace_all_at(vec![0, 2], vec![4, 5]), vec![4, 2, 5]); + /// # let a = source.clone(); + /// assert_eq!(a.replace_all_at(vec![1, 3], vec![4, 5]), vec![1, 4, 3]); + /// # let a = source.clone(); + /// assert_eq!(a.replace_all_at(vec![0, 2], vec![4]), vec![4, 2, 3]); + /// # let a = source.clone(); + /// assert_eq!(a.replace_all_at(vec![0, 2], vec![4, 5, 6]), vec![4, 2, 5]); + /// + /// # let a = source.clone(); + /// assert_eq!(a.replace_all_at(vec![3, 4], vec![4, 5]), vec![1, 2, 3]); + /// assert_eq!(e.replace_all_at(vec![0], vec![1]), vec![]); + /// ``` + fn replace_all_at(self, indices: impl IntoIterator, elements: impl IntoIterator) -> Self + where + Self: IntoIterator + FromIterator, + { + let mut iterator = self.into_iter(); + let positions: HashSet = HashSet::from_iter(indices); + let mut elements_iterator = elements.into_iter(); + unfold(0_usize, |position| { + iterator.next().map(|item| { + let result = if positions.contains(position) { + elements_iterator.next().unwrap_or(item) + } else { + item + }; + *position += 1; + result + }) + }) + .collect() + } + /// Creates a collection by reversing the original collection's direction. /// /// # Example @@ -1139,30 +1212,6 @@ pub trait Sequence { result.into_iter().collect() } - #[inline] - fn replace_at(self, index: usize, element: Item) -> Self - where - Self: IntoIterator + FromIterator, - { - self.replace_all_at(index..(index + 1), iter::once(element)) - } - - fn replace_all_at(self, indices: impl IntoIterator, replacement: impl IntoIterator) -> Self - where - Self: IntoIterator + FromIterator, - { - let mut iterator = self.into_iter(); - let positions: HashSet = HashSet::from_iter(indices); - let mut values = replacement.into_iter(); - unfold(0_usize, |position| { - let item = iterator.next(); - let result = if positions.contains(position) { values.next() } else { item }; - *position += 1; - result - }) - .collect() - } - /// Creates a collection by only including elements in the specified range. /// /// if the specified index exceeds the collection size, no elements are inserted. @@ -1447,6 +1496,30 @@ where .collect() } +pub(crate) fn chunked(collection: Collection, size: usize, exact: bool) -> Result +where + Collection: IntoIterator + Default + Extend, + Result: Default + Extend, +{ + assert_ne!(size, 0, "chunk size must be non-zero"); + let mut result = Result::default(); + let mut chunk = Collection::default(); + let mut index: usize = 0; + for item in collection.into_iter() { + if index > 0 && index == size { + result.extend(iter::once(chunk)); + chunk = Collection::default(); + index = 0; + } + chunk.extend(iter::once(item)); + index += 1; + } + if index > 0 && !exact { + result.extend(iter::once(chunk)); + } + result +} + pub(crate) fn common_prefix_length<'a, Item>( iterator: impl Iterator, elements: &'a impl Iterable = &'a Item>, ) -> usize @@ -1500,30 +1573,6 @@ where .collect() } -pub(crate) fn chunked(collection: Collection, size: usize, exact: bool) -> Result -where - Collection: IntoIterator + Default + Extend, - Result: Default + Extend, -{ - assert_ne!(size, 0, "chunk size must be non-zero"); - let mut result = Result::default(); - let mut chunk = Collection::default(); - let mut index: usize = 0; - for item in collection.into_iter() { - if index > 0 && index == size { - result.extend(iter::once(chunk)); - chunk = Collection::default(); - index = 0; - } - chunk.extend(iter::once(item)); - index += 1; - } - if index > 0 && !exact { - result.extend(iter::once(chunk)); - } - result -} - #[allow(unused_results)] pub(crate) fn windowed<'a, Item, Collection, Result>( mut iterator: impl Iterator, size: usize, diff --git a/src/lib.rs b/src/lib.rs index 9c3d860..726c4ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ /// | *minmax_by* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | /// | *minmax_by_key* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | /// | *minmax_item* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -/// | *move_item* | :heavy_check_mark: | | | | +/// | *move_at* | :heavy_check_mark: | | | | /// | *multicombinations* | :heavy_check_mark: | | | | /// | *pad_left* | :heavy_check_mark: | | | | /// | *pad_left_with* | :heavy_check_mark: | | | | @@ -126,8 +126,9 @@ /// | *permutations* | :heavy_check_mark: | | | | /// | *position* | :heavy_check_mark: | :heavy_check_mark: | | | /// | *positions* | :heavy_check_mark: | :heavy_check_mark: | | | -/// | *position_of* | :heavy_check_mark: | :heavy_check_mark: | | | /// | *positions_of* | :heavy_check_mark: | :heavy_check_mark: | | | +/// | *position_of* | :heavy_check_mark: | :heavy_check_mark: | | | +/// | *position_sequence* | :heavy_check_mark: | :heavy_check_mark: | | | /// | *powerset* | :heavy_check_mark: | | :heavy_check_mark: | | /// | *product* | :heavy_check_mark: | | :heavy_check_mark: | | /// | *product_keys* | | | | :heavy_check_mark: | @@ -159,9 +160,7 @@ /// | *splice* | :heavy_check_mark: | | | | /// | *step_by* | :heavy_check_mark: | | | | /// | *subset* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -/// | *subsequence* | :heavy_check_mark: | :heavy_check_mark: | | | /// | *superset* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -/// | *supersequence* | :heavy_check_mark: | :heavy_check_mark: | | | /// | *sum* | :heavy_check_mark: | | :heavy_check_mark: | | /// | *sum_keys* | | | | :heavy_check_mark: | /// | *sum_values* | | | | :heavy_check_mark: |