From 1bb1fe4da59545c424f5da303f263e07f192fc13 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Mon, 19 Feb 2024 20:54:58 +1100 Subject: [PATCH 01/13] feat: add `CursorMut` --- .circleci/config.yml | 2 +- src/linked_hash_map.rs | 247 +++++++++++++++++++++++++++++++++++++-- tests/linked_hash_map.rs | 205 ++++++++++++++++++++++++++++++++ 3 files changed, 442 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 00e8e9f..61ebbcb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: cimg/rust:1.65.0 + - image: cimg/rust:1.75.0 steps: - checkout - run: diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 9b5dd71..2419535 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -680,7 +680,7 @@ where } pub enum Entry<'a, K, V, S> { - Occupied(OccupiedEntry<'a, K, V>), + Occupied(OccupiedEntry<'a, K, V, S>), Vacant(VacantEntry<'a, K, V, S>), } @@ -755,12 +755,12 @@ impl<'a, K, V, S> Entry<'a, K, V, S> { } } -pub struct OccupiedEntry<'a, K, V> { +pub struct OccupiedEntry<'a, K, V, S> { key: K, - raw_entry: RawOccupiedEntryMut<'a, K, V>, + raw_entry: RawOccupiedEntryMut<'a, K, V, S>, } -impl fmt::Debug for OccupiedEntry<'_, K, V> { +impl fmt::Debug for OccupiedEntry<'_, K, V, S> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OccupiedEntry") @@ -770,7 +770,7 @@ impl fmt::Debug for OccupiedEntry<'_, K, V> { } } -impl<'a, K, V> OccupiedEntry<'a, K, V> { +impl<'a, K, V, S> OccupiedEntry<'a, K, V, S> { #[inline] pub fn key(&self) -> &K { self.raw_entry.key() @@ -829,6 +829,16 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { self.replace_entry(value) } + /// Returns a `CursorMut` over the current entry. + #[inline] + pub fn cursor_mut(self) -> CursorMut<'a, K, V, S> + where + K: Eq + Hash, + S: BuildHasher, + { + self.raw_entry.cursor_mut() + } + /// Replaces the entry's key with the key provided to `LinkedHashMap::entry`, and replaces the /// entry's value with the given `value` parameter. /// @@ -984,6 +994,7 @@ where match entry { Ok(occupied) => RawEntryMut::Occupied(RawOccupiedEntryMut { + hash_builder: &self.map.hash_builder, free: &mut self.map.free, values: &mut self.map.values, entry: occupied, @@ -1015,7 +1026,7 @@ where } pub enum RawEntryMut<'a, K, V, S> { - Occupied(RawOccupiedEntryMut<'a, K, V>), + Occupied(RawOccupiedEntryMut<'a, K, V, S>), Vacant(RawVacantEntryMut<'a, K, V, S>), } @@ -1076,13 +1087,14 @@ impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { } } -pub struct RawOccupiedEntryMut<'a, K, V> { +pub struct RawOccupiedEntryMut<'a, K, V, S> { + hash_builder: &'a S, free: &'a mut Option>>, values: &'a mut Option>>, entry: hash_table::OccupiedEntry<'a, NonNull>>, } -impl<'a, K, V> RawOccupiedEntryMut<'a, K, V> { +impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { #[inline] pub fn key(&self) -> &K { self.get_key_value().0 @@ -1184,6 +1196,22 @@ impl<'a, K, V> RawOccupiedEntryMut<'a, K, V> { let node = self.entry.remove().0; unsafe { remove_node(self.free, node) } } + + /// Returns a `CursorMut` over the current entry. + #[inline] + pub fn cursor_mut(self) -> CursorMut<'a, K, V, S> + where + K: Eq + Hash, + S: BuildHasher, + { + CursorMut { + cur: self.entry.get().as_ptr(), + hash_builder: self.hash_builder, + free: self.free, + values: self.values, + table: self.entry.into_table(), + } + } } pub struct RawVacantEntryMut<'a, K, V, S> { @@ -1260,7 +1288,7 @@ impl fmt::Debug for RawEntryMut<'_, K, V, S> { } } -impl fmt::Debug for RawOccupiedEntryMut<'_, K, V> { +impl fmt::Debug for RawOccupiedEntryMut<'_, K, V, S> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RawOccupiedEntryMut") @@ -1284,17 +1312,19 @@ impl fmt::Debug for RawEntryBuilder<'_, K, V, S> { } } -unsafe impl<'a, K, V> Send for RawOccupiedEntryMut<'a, K, V> +unsafe impl<'a, K, V, S> Send for RawOccupiedEntryMut<'a, K, V, S> where K: Send, V: Send, + S: Send, { } -unsafe impl<'a, K, V> Sync for RawOccupiedEntryMut<'a, K, V> +unsafe impl<'a, K, V, S> Sync for RawOccupiedEntryMut<'a, K, V, S> where K: Sync, V: Sync, + S: Sync, { } @@ -1344,6 +1374,33 @@ pub struct Drain<'a, K, V> { marker: PhantomData<(K, V, &'a LinkedHashMap)>, } +/// The `CursorMut` struct and its implementation provide the basic mutable Cursor API for Linked +/// lists as proposed in +/// [here](https://github.com/rust-lang/rfcs/blob/master/text/2570-linked-list-cursors.md), with +/// several exceptions: +/// +/// - It behaves similarly to Rust's Iterators, returning `None` when the end of the list is +/// reached. A _ghost_ node is positioned between the head and tail of the linked list to +/// facilitate this. If the cursor is over this ghost node, `None` is returned, signaling the end +/// or start of the list. From this position, the cursor can move in either direction as the +/// linked list is circular, with the ghost node connecting the two ends. +/// - The current implementation does not include an `index` method, as it does not track the index +/// of its elements. It operates by providing items as key-value tuples, allowing the value to be +/// modified via a mutable reference while the key could not be changed. +/// - The current implementation does not include `splice_*`, `split_*`, `as_cursor`, or +/// `remove_current` methods, as there hasn't been a strong demand for these features in +/// real-world scenarios. However, they can be readily incorporated into the existing codebase if +/// needed. +/// - For added convenience, it includes the `move_at` method. +/// +pub struct CursorMut<'a, K, V, S> { + cur: *mut Node, + hash_builder: &'a S, + free: &'a mut Option>>, + values: &'a mut Option>>, + table: &'a mut hashbrown::HashTable>>, +} + impl IterMut<'_, K, V> { #[inline] pub(crate) fn iter(&self) -> Iter<'_, K, V> { @@ -1677,6 +1734,174 @@ impl<'a, K, V> Drop for Drain<'a, K, V> { } } +impl<'a, K, V, S> CursorMut<'a, K, V, S> { + /// Fetches the current element in the list, provided it is not the _ghost_ node, + /// which acts as a devider in the linked list structure indicating the end/front + /// of the list. TODO: rephrase. + #[inline] + pub fn current(&mut self) -> Option<(&K, &mut V)> { + unsafe { + let at = NonNull::new_unchecked(self.cur); + self.peek(|| at) + } + } + + /// Retrieves the next element in the list (moving towards the end). + #[inline] + pub fn peek_next(&mut self) -> Option<(&K, &mut V)> { + unsafe { + let at = (*self.cur).links.value.next; + self.peek(|| at) + } + } + + /// Retrieves the previous element in the list (moving towards the front). + #[inline] + pub fn peek_prev(&mut self) -> Option<(&K, &mut V)> { + unsafe { + let at = (*self.cur).links.value.prev; + self.peek(|| at) + } + } + + // Retrieves the element specified by the at closure function without advancing current + // position to it. + #[inline] + fn peek(&mut self, at: impl FnOnce() -> NonNull>) -> Option<(&K, &mut V)> { + if let Some(values) = self.values { + unsafe { + let node = at().as_ptr(); + if node == values.as_ptr() { + None + } else { + let entry = (*node).entry_mut(); + Some((&entry.0, &mut entry.1)) + } + } + } else { + unreachable!("underlying doubly-linked list is not initialized") + } + } + + /// Updates the pointer to the current element to the next element in the + /// list (that is, moving towards the end). + #[inline] + pub fn move_next(&mut self) { + let at = unsafe { (*self.cur).links.value.next }; + self.muv(|| at); + } + + /// Updates the pointer to the current element to the previous element in the + /// list (that is, moving towards the front). + #[inline] + pub fn move_prev(&mut self) { + let at = unsafe { (*self.cur).links.value.prev }; + self.muv(|| at); + } + + /// Positions the cursor at the element associated with the specified key. Returns `true` + /// if the element exists and the operation succeeds, or `false` if the element does not + /// exist. + #[inline] + pub fn move_at(&mut self, key: K) -> bool + where + K: Eq + Hash, + S: BuildHasher, + { + unsafe { + let hash = hash_key(self.hash_builder, &key); + let i_entry = self + .table + .find_entry(hash, |o| (*o).as_ref().key_ref().eq(&key)); + + match i_entry { + Ok(occupied) => { + let at = *occupied.get(); + self.muv(|| at); + true + } + Err(_) => false, + } + } + } + + // Updates the pointer to the current element to the one returned by the at closure function. + #[inline] + fn muv(&mut self, at: impl FnOnce() -> NonNull>) { + self.cur = at().as_ptr(); + } + + /// Inserts the provided key and value before the current item. It checks if an entry + /// with the given key exists and, if so, replaces its value with the provided `1` + /// parameter from the given tuple. The key is not updated; this matters for types that + /// can be `==` without being identical. + /// + /// If the entry doesn't exist, it creates a new one. If a value has been updated, the + /// function returns the *old* value wrapped with `Some` and `None` otherwise. + #[inline] + pub fn insert_before(&mut self, tuple: (K, V)) -> Option + where + K: Eq + Hash, + S: BuildHasher, + { + let before = unsafe { NonNull::new_unchecked(self.cur) }; + self.insert(tuple, || before) + } + + /// Inserts the provided key and value after the current item. It checks if an entry + /// with the given key exists and, if so, replaces its value with the provided `1` + /// parameter from the given tuple. The key is not updated; this matters for types that + /// can be `==` without being identical. + /// + /// If the entry doesn't exist, it creates a new one. If a value has been updated, the + /// function returns the *old* value wrapped with `Some` and `None` otherwise. + #[inline] + pub fn insert_after(&mut self, tuple: (K, V)) -> Option + where + K: Eq + Hash, + S: BuildHasher, + { + let before = unsafe { (*self.cur).links.value.next }; + self.insert(tuple, || before) + } + + // Inserts `item` immediately before the element returned by the `before` closure function. + #[inline] + fn insert(&mut self, tuple: (K, V), before: impl FnOnce() -> NonNull>) -> Option + where + K: Eq + Hash, + S: BuildHasher, + { + let (key, value) = tuple; + unsafe { + let hash = hash_key(self.hash_builder, &key); + let i_entry = self + .table + .find_entry(hash, |o| (*o).as_ref().key_ref().eq(&key)); + + match i_entry { + Ok(occupied) => { + let mut node = *occupied.into_mut(); + let pv = mem::replace(&mut node.as_mut().entry_mut().1, value); + detach_node(node); + attach_before(node, before()); + Some(pv) + } + Err(_) => { + let mut new_node = allocate_node(self.free); + new_node.as_mut().put_entry((key, value)); + let hash_builder = self.hash_builder; + self.table.insert_unique(hash, new_node, move |k| { + hash_key(hash_builder, (*k).as_ref().key_ref()) + }); + attach_before(new_node, before()); + None + } + } + } + } +} + pub struct Keys<'a, K, V> { inner: Iter<'a, K, V>, } diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 936a8a7..1b487b9 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -575,3 +575,208 @@ fn test_shrink_to_fit_resize() { assert_eq!(map.get(&i).unwrap(), &i); } } + +#[test] +fn test_cursor_mut_current() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + let value = cursor.current().unwrap(); + assert_eq!(value, (&3, &mut 3)); + *value.1 = 5; + let value = cursor.current().unwrap(); + assert_eq!(value, (&3, &mut 5)); + } +} + +#[test] +fn test_cursor_mut_move_next() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + map.insert(5, 5); + map.insert(6, 6); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 3); + cursor.move_next(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 4); + cursor.move_next(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 5); + cursor.move_next(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 6); + cursor.move_next(); + let value = cursor.current(); + assert!(value.is_none()); + cursor.move_next(); + let value = cursor.current(); + assert!(value.is_some()); + assert_eq!(value.unwrap().1, &mut 3); + cursor.move_next(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 4); + } +} + +#[test] +fn test_cursor_mut_move_prev() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + cursor.move_prev(); + let value = cursor.current(); + assert!(value.is_none()); + cursor.move_prev(); + let value = cursor.current(); + assert!(&value.is_some()); + assert_eq!(value.unwrap().1, &mut 3); + } +} + +#[test] +fn test_cursor_mut_move_at() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + map.insert(5, 5); + map.insert(6, 6); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + let moved = cursor.move_at(6); + assert!(moved); + let value = cursor.current(); + assert!(value.is_some()); + assert_eq!(value.unwrap().1, &mut 6); + let moved = cursor.move_at(7); + assert!(!moved); + } +} + +#[test] +fn test_cursor_mut_pick_next() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + let next = cursor.peek_next(); + assert!(&next.is_some()); + assert_eq!(next.unwrap().1, &mut 4); + cursor.move_next(); + let next = cursor.peek_next(); + assert!(&next.is_none()); + cursor.move_next(); + let next = cursor.peek_next(); + assert!(&next.is_some()); + let value = next.as_ref().unwrap().to_owned(); + assert_eq!(*value.1, 3); + *next.unwrap().1 = 5; + let next = cursor.peek_next(); + assert!(&next.is_some()); + assert_eq!(next.unwrap().1, &mut 5); + } +} + +#[test] +fn test_cursor_mut_pick_prev() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + let next = cursor.peek_prev(); + assert!(&next.is_none()); + cursor.move_prev(); + let next = cursor.peek_prev(); + assert!(&next.is_some()); + assert_eq!(next.unwrap(), (&4, &mut 4)); + } +} + +#[test] +fn test_cursor_mut_insert_before() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + + // Insert new item in the middle + if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { + entry.cursor_mut().insert_before((5, 5)); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(3, 3), (5, 5), (4, 4)].iter().copied())); + } + + // Insert new item at the very end of the list + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + let mut cursor = entry.cursor_mut(); + cursor.move_prev(); + cursor.insert_before((6, 6)); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(3, 3), (5, 5), (4, 4), (6, 6)].iter().copied())); + } + + // Relocate item and override value + if let linked_hash_map::Entry::Occupied(entry) = map.entry(5) { + entry.cursor_mut().insert_before((4, 42)); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(3, 3), (4, 42), (5, 5), (6, 6)].iter().copied())); + } +} + +#[test] +fn test_cursor_mut_insert_after() { + let mut map = LinkedHashMap::new(); + + map.insert(3, 3); + map.insert(4, 4); + + // Insert new item in the middle. + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + entry.cursor_mut().insert_after((5, 5)); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(3, 3), (5, 5), (4, 4)].iter().copied())); + } + + // Insert new item as the first one. + if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { + let mut cursor = entry.cursor_mut(); + cursor.move_next(); + cursor.insert_after((6, 6)); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(6, 6), (3, 3), (5, 5), (4, 4)].iter().copied())); + } +} From 09b5ecc06d8aec00690dfe948e8d27f857c48f8d Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 25 Feb 2024 17:19:52 +1100 Subject: [PATCH 02/13] add `LinkedHashMap::cursor_mut` method Also, improve documentation, for consistency. --- src/linked_hash_map.rs | 26 ++++++++++++++++++++------ tests/linked_hash_map.rs | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 2419535..d50956c 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -503,6 +503,21 @@ where } } } + + /// Returns the `CursorMut` over the _guard_ node. + /// + /// Note: The `CursorMut` over the _guard_ node in an empty `LinkedHashMap` will always + /// return `None` as its current eliment, regardless of any move in any direction. + pub fn cursor_mut(&mut self) -> CursorMut { + unsafe { ensure_guard_node(&mut self.values) }; + CursorMut { + cur: self.values.as_ptr(), + hash_builder: &self.hash_builder, + free: &mut self.free, + values: &mut self.values, + table: &mut self.table, + } + } } impl LinkedHashMap @@ -1380,10 +1395,10 @@ pub struct Drain<'a, K, V> { /// several exceptions: /// /// - It behaves similarly to Rust's Iterators, returning `None` when the end of the list is -/// reached. A _ghost_ node is positioned between the head and tail of the linked list to -/// facilitate this. If the cursor is over this ghost node, `None` is returned, signaling the end +/// reached. A _guard_ node is positioned between the head and tail of the linked list to +/// facilitate this. If the cursor is over this guard node, `None` is returned, signaling the end /// or start of the list. From this position, the cursor can move in either direction as the -/// linked list is circular, with the ghost node connecting the two ends. +/// linked list is circular, with the guard node connecting the two ends. /// - The current implementation does not include an `index` method, as it does not track the index /// of its elements. It operates by providing items as key-value tuples, allowing the value to be /// modified via a mutable reference while the key could not be changed. @@ -1735,9 +1750,8 @@ impl<'a, K, V> Drop for Drain<'a, K, V> { } impl<'a, K, V, S> CursorMut<'a, K, V, S> { - /// Fetches the current element in the list, provided it is not the _ghost_ node, - /// which acts as a devider in the linked list structure indicating the end/front - /// of the list. TODO: rephrase. + /// Returns an `Option` of the current element in the list, provided it is not the + /// _guard_ node, and `None` overwise. #[inline] pub fn current(&mut self) -> Option<(&K, &mut V)> { unsafe { diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 1b487b9..850cd84 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -780,3 +780,25 @@ fn test_cursor_mut_insert_after() { .eq([(6, 6), (3, 3), (5, 5), (4, 4)].iter().copied())); } } + +#[test] +fn test_cursor_mut() { + let mut map: LinkedHashMap = LinkedHashMap::new(); + // CursorMut over the _guard_ node in an empty LinkedHashMap will always return `None` as its + // current eliment, regardless of any move in any direction. + let mut cursor = map.cursor_mut(); + assert!(cursor.current().is_none()); + cursor.move_next(); + assert!(cursor.current().is_none()); + cursor.insert_after((1, 1)); + cursor.move_next(); + assert!(cursor.current().is_some()); + assert_eq!(cursor.current().unwrap().1, &mut 1); + cursor.move_next(); + assert!(cursor.current().is_none()); + + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(1, 1)].iter().copied())); +} From 41d96a04dd0f15d7a55fc02aa9f130d147409382 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Mon, 26 Feb 2024 08:54:53 +1100 Subject: [PATCH 03/13] remove unreachable! in favor of None --- src/linked_hash_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index d50956c..33a8ee4 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1793,7 +1793,7 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { } } } else { - unreachable!("underlying doubly-linked list is not initialized") + None } } From 2b45428b52b43602279eb9a74e9b714aee2f6218 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Thu, 29 Feb 2024 20:38:34 +1100 Subject: [PATCH 04/13] fn signature: move_at(K) -> move_at(&K) --- src/linked_hash_map.rs | 6 +++--- tests/linked_hash_map.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 33a8ee4..ade309a 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1817,16 +1817,16 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { /// if the element exists and the operation succeeds, or `false` if the element does not /// exist. #[inline] - pub fn move_at(&mut self, key: K) -> bool + pub fn move_at(&mut self, key: &K) -> bool where K: Eq + Hash, S: BuildHasher, { unsafe { - let hash = hash_key(self.hash_builder, &key); + let hash = hash_key(self.hash_builder, key); let i_entry = self .table - .find_entry(hash, |o| (*o).as_ref().key_ref().eq(&key)); + .find_entry(hash, |o| (*o).as_ref().key_ref().eq(key)); match i_entry { Ok(occupied) => { diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 850cd84..3e44019 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -661,12 +661,12 @@ fn test_cursor_mut_move_at() { if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { let mut cursor = entry.cursor_mut(); - let moved = cursor.move_at(6); + let moved = cursor.move_at(&6); assert!(moved); let value = cursor.current(); assert!(value.is_some()); assert_eq!(value.unwrap().1, &mut 6); - let moved = cursor.move_at(7); + let moved = cursor.move_at(&7); assert!(!moved); } } From 6b55ef0bb2a371587900c53888744bc96a0d022c Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:23:34 +1000 Subject: [PATCH 05/13] remove move_at fn --- src/linked_hash_map.rs | 26 -------------------------- tests/linked_hash_map.rs | 21 --------------------- 2 files changed, 47 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index ade309a..13b9b12 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1813,32 +1813,6 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { self.muv(|| at); } - /// Positions the cursor at the element associated with the specified key. Returns `true` - /// if the element exists and the operation succeeds, or `false` if the element does not - /// exist. - #[inline] - pub fn move_at(&mut self, key: &K) -> bool - where - K: Eq + Hash, - S: BuildHasher, - { - unsafe { - let hash = hash_key(self.hash_builder, key); - let i_entry = self - .table - .find_entry(hash, |o| (*o).as_ref().key_ref().eq(key)); - - match i_entry { - Ok(occupied) => { - let at = *occupied.get(); - self.muv(|| at); - true - } - Err(_) => false, - } - } - } - // Updates the pointer to the current element to the one returned by the at closure function. #[inline] fn muv(&mut self, at: impl FnOnce() -> NonNull>) { diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 3e44019..22a1a27 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -650,27 +650,6 @@ fn test_cursor_mut_move_prev() { } } -#[test] -fn test_cursor_mut_move_at() { - let mut map = LinkedHashMap::new(); - - map.insert(3, 3); - map.insert(4, 4); - map.insert(5, 5); - map.insert(6, 6); - - if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { - let mut cursor = entry.cursor_mut(); - let moved = cursor.move_at(&6); - assert!(moved); - let value = cursor.current(); - assert!(value.is_some()); - assert_eq!(value.unwrap().1, &mut 6); - let moved = cursor.move_at(&7); - assert!(!moved); - } -} - #[test] fn test_cursor_mut_pick_next() { let mut map = LinkedHashMap::new(); From 80f55b3d75986d0046a495704d74dba9b85a0817 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:25:23 +1000 Subject: [PATCH 06/13] revert upgrade of rust on ci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61ebbcb..00e8e9f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: cimg/rust:1.75.0 + - image: cimg/rust:1.65.0 steps: - checkout - run: From 57e0876a1e2285f8ab7e9bf96a2ddf0489b2fa6a Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:37:04 +1000 Subject: [PATCH 07/13] cursor_mut -> cursor_{front,back}_mut --- src/linked_hash_map.rs | 33 +++++++++++++++++++++++++++------ tests/linked_hash_map.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 13b9b12..ac77d63 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -504,19 +504,40 @@ where } } - /// Returns the `CursorMut` over the _guard_ node. + /// Returns the `CursorMut` over the front node. /// - /// Note: The `CursorMut` over the _guard_ node in an empty `LinkedHashMap` will always - /// return `None` as its current eliment, regardless of any move in any direction. - pub fn cursor_mut(&mut self) -> CursorMut { + /// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and + /// will always return `None` as its current element, regardless of any move in any + /// direction. + pub fn cursor_front_mut(&mut self) -> CursorMut { unsafe { ensure_guard_node(&mut self.values) }; - CursorMut { + let mut c = CursorMut { cur: self.values.as_ptr(), hash_builder: &self.hash_builder, free: &mut self.free, values: &mut self.values, table: &mut self.table, - } + }; + c.move_next(); + c + } + + /// Returns the `CursorMut` over the back node. + /// + /// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and + /// will always return `None` as its current element, regardless of any move in any + /// direction. + pub fn cursor_back_mut(&mut self) -> CursorMut { + unsafe { ensure_guard_node(&mut self.values) }; + let mut c = CursorMut { + cur: self.values.as_ptr(), + hash_builder: &self.hash_builder, + free: &mut self.free, + values: &mut self.values, + table: &mut self.table, + }; + c.move_prev(); + c } } diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 22a1a27..6b9b8ca 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -761,11 +761,11 @@ fn test_cursor_mut_insert_after() { } #[test] -fn test_cursor_mut() { +fn test_cursor_front_mut() { let mut map: LinkedHashMap = LinkedHashMap::new(); - // CursorMut over the _guard_ node in an empty LinkedHashMap will always return `None` as its - // current eliment, regardless of any move in any direction. - let mut cursor = map.cursor_mut(); + // the CursorMut in an empty LinkedHashMap will always return `None` as its + // current element, regardless of any move in any direction. + let mut cursor = map.cursor_front_mut(); assert!(cursor.current().is_none()); cursor.move_next(); assert!(cursor.current().is_none()); @@ -780,4 +780,24 @@ fn test_cursor_mut() { .iter() .map(|(k, v)| (*k, *v)) .eq([(1, 1)].iter().copied())); + + map.insert(2, 2); + map.insert(3, 3); + + let mut cursor = map.cursor_front_mut(); + assert!(cursor.current().is_some()); + assert_eq!(cursor.current().unwrap().1, &mut 1); +} + +#[test] +fn test_cursor_back_mut() { + let mut map: LinkedHashMap = LinkedHashMap::new(); + + map.insert(1, 1); + map.insert(2, 2); + map.insert(3, 3); + + let mut cursor = map.cursor_back_mut(); + assert!(cursor.current().is_some()); + assert_eq!(cursor.current().unwrap().1, &mut 3); } From b325d8b8a6229be355b0c2221c558f2ccb3ff0c4 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:38:20 +1000 Subject: [PATCH 08/13] remove comment --- src/linked_hash_map.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index ac77d63..f0d9191 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1423,11 +1423,6 @@ pub struct Drain<'a, K, V> { /// - The current implementation does not include an `index` method, as it does not track the index /// of its elements. It operates by providing items as key-value tuples, allowing the value to be /// modified via a mutable reference while the key could not be changed. -/// - The current implementation does not include `splice_*`, `split_*`, `as_cursor`, or -/// `remove_current` methods, as there hasn't been a strong demand for these features in -/// real-world scenarios. However, they can be readily incorporated into the existing codebase if -/// needed. -/// - For added convenience, it includes the `move_at` method. /// pub struct CursorMut<'a, K, V, S> { cur: *mut Node, From 41a3ab1dfed1fd5f0ab8aa4cf469142d7ebba7f1 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:45:55 +1000 Subject: [PATCH 09/13] tuple -> key, value --- src/linked_hash_map.rs | 16 ++++++++++------ tests/linked_hash_map.rs | 12 ++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index f0d9191..28cb013 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1843,13 +1843,13 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { /// If the entry doesn't exist, it creates a new one. If a value has been updated, the /// function returns the *old* value wrapped with `Some` and `None` otherwise. #[inline] - pub fn insert_before(&mut self, tuple: (K, V)) -> Option + pub fn insert_before(&mut self, key: K, value: V) -> Option where K: Eq + Hash, S: BuildHasher, { let before = unsafe { NonNull::new_unchecked(self.cur) }; - self.insert(tuple, || before) + self.insert(key, value, || before) } /// Inserts the provided key and value after the current item. It checks if an entry @@ -1860,23 +1860,27 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { /// If the entry doesn't exist, it creates a new one. If a value has been updated, the /// function returns the *old* value wrapped with `Some` and `None` otherwise. #[inline] - pub fn insert_after(&mut self, tuple: (K, V)) -> Option + pub fn insert_after(&mut self, key: K, value: V) -> Option where K: Eq + Hash, S: BuildHasher, { let before = unsafe { (*self.cur).links.value.next }; - self.insert(tuple, || before) + self.insert(key, value, || before) } // Inserts `item` immediately before the element returned by the `before` closure function. #[inline] - fn insert(&mut self, tuple: (K, V), before: impl FnOnce() -> NonNull>) -> Option + fn insert( + &mut self, + key: K, + value: V, + before: impl FnOnce() -> NonNull>, + ) -> Option where K: Eq + Hash, S: BuildHasher, { - let (key, value) = tuple; unsafe { let hash = hash_key(self.hash_builder, &key); let i_entry = self diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 6b9b8ca..c1d0573 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -704,7 +704,7 @@ fn test_cursor_mut_insert_before() { // Insert new item in the middle if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { - entry.cursor_mut().insert_before((5, 5)); + entry.cursor_mut().insert_before(5, 5); assert!(map .iter() .map(|(k, v)| (*k, *v)) @@ -715,7 +715,7 @@ fn test_cursor_mut_insert_before() { if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { let mut cursor = entry.cursor_mut(); cursor.move_prev(); - cursor.insert_before((6, 6)); + cursor.insert_before(6, 6); assert!(map .iter() .map(|(k, v)| (*k, *v)) @@ -724,7 +724,7 @@ fn test_cursor_mut_insert_before() { // Relocate item and override value if let linked_hash_map::Entry::Occupied(entry) = map.entry(5) { - entry.cursor_mut().insert_before((4, 42)); + entry.cursor_mut().insert_before(4, 42); assert!(map .iter() .map(|(k, v)| (*k, *v)) @@ -741,7 +741,7 @@ fn test_cursor_mut_insert_after() { // Insert new item in the middle. if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { - entry.cursor_mut().insert_after((5, 5)); + entry.cursor_mut().insert_after(5, 5); assert!(map .iter() .map(|(k, v)| (*k, *v)) @@ -752,7 +752,7 @@ fn test_cursor_mut_insert_after() { if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { let mut cursor = entry.cursor_mut(); cursor.move_next(); - cursor.insert_after((6, 6)); + cursor.insert_after(6, 6); assert!(map .iter() .map(|(k, v)| (*k, *v)) @@ -769,7 +769,7 @@ fn test_cursor_front_mut() { assert!(cursor.current().is_none()); cursor.move_next(); assert!(cursor.current().is_none()); - cursor.insert_after((1, 1)); + cursor.insert_after(1, 1); cursor.move_next(); assert!(cursor.current().is_some()); assert_eq!(cursor.current().unwrap().1, &mut 1); From db0b2244701312de89a35fdbb5ea6164f6931d5f Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:46:22 +1000 Subject: [PATCH 10/13] attach node before insert --- src/linked_hash_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 28cb013..f8b8bd1 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1898,11 +1898,11 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { Err(_) => { let mut new_node = allocate_node(self.free); new_node.as_mut().put_entry((key, value)); + attach_before(new_node, before()); let hash_builder = self.hash_builder; self.table.insert_unique(hash, new_node, move |k| { hash_key(hash_builder, (*k).as_ref().key_ref()) }); - attach_before(new_node, before()); None } } From 6bcf27c9778497631d8a4a5f7c1599ba4c6763de Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 16:57:21 +1000 Subject: [PATCH 11/13] remove unnecessary closures --- src/linked_hash_map.rs | 56 ++++++++++++++++++---------------------- tests/linked_hash_map.rs | 12 ++++----- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index f8b8bd1..1b6df36 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1421,7 +1421,7 @@ pub struct Drain<'a, K, V> { /// or start of the list. From this position, the cursor can move in either direction as the /// linked list is circular, with the guard node connecting the two ends. /// - The current implementation does not include an `index` method, as it does not track the index -/// of its elements. It operates by providing items as key-value tuples, allowing the value to be +/// of its elements. It operates by providing elements as key-value tuples, allowing the value to be /// modified via a mutable reference while the key could not be changed. /// pub struct CursorMut<'a, K, V, S> { @@ -1772,7 +1772,7 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { pub fn current(&mut self) -> Option<(&K, &mut V)> { unsafe { let at = NonNull::new_unchecked(self.cur); - self.peek(|| at) + self.peek(at) } } @@ -1781,7 +1781,7 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { pub fn peek_next(&mut self) -> Option<(&K, &mut V)> { unsafe { let at = (*self.cur).links.value.next; - self.peek(|| at) + self.peek(at) } } @@ -1790,17 +1790,16 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { pub fn peek_prev(&mut self) -> Option<(&K, &mut V)> { unsafe { let at = (*self.cur).links.value.prev; - self.peek(|| at) + self.peek(at) } } - // Retrieves the element specified by the at closure function without advancing current - // position to it. + // Retrieves the element without advancing current position to it. #[inline] - fn peek(&mut self, at: impl FnOnce() -> NonNull>) -> Option<(&K, &mut V)> { + fn peek(&mut self, at: NonNull>) -> Option<(&K, &mut V)> { if let Some(values) = self.values { unsafe { - let node = at().as_ptr(); + let node = at.as_ptr(); if node == values.as_ptr() { None } else { @@ -1818,7 +1817,7 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { #[inline] pub fn move_next(&mut self) { let at = unsafe { (*self.cur).links.value.next }; - self.muv(|| at); + self.muv(at); } /// Updates the pointer to the current element to the previous element in the @@ -1826,19 +1825,19 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { #[inline] pub fn move_prev(&mut self) { let at = unsafe { (*self.cur).links.value.prev }; - self.muv(|| at); + self.muv(at); } // Updates the pointer to the current element to the one returned by the at closure function. #[inline] - fn muv(&mut self, at: impl FnOnce() -> NonNull>) { - self.cur = at().as_ptr(); + fn muv(&mut self, at: NonNull>) { + self.cur = at.as_ptr(); } - /// Inserts the provided key and value before the current item. It checks if an entry - /// with the given key exists and, if so, replaces its value with the provided `1` - /// parameter from the given tuple. The key is not updated; this matters for types that - /// can be `==` without being identical. + /// Inserts the provided key and value before the current element. It checks if an entry + /// with the given key exists and, if so, replaces its value with the provided `key` + /// parameter. The key is not updated; this matters for types that can be `==` without + /// being identical. /// /// If the entry doesn't exist, it creates a new one. If a value has been updated, the /// function returns the *old* value wrapped with `Some` and `None` otherwise. @@ -1849,13 +1848,13 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { S: BuildHasher, { let before = unsafe { NonNull::new_unchecked(self.cur) }; - self.insert(key, value, || before) + self.insert(key, value, before) } - /// Inserts the provided key and value after the current item. It checks if an entry - /// with the given key exists and, if so, replaces its value with the provided `1` - /// parameter from the given tuple. The key is not updated; this matters for types that - /// can be `==` without being identical. + /// Inserts the provided key and value after the current element. It checks if an entry + /// with the given key exists and, if so, replaces its value with the provided `key` + /// parameter. The key is not updated; this matters for types that can be `==` without + /// being identical. /// /// If the entry doesn't exist, it creates a new one. If a value has been updated, the /// function returns the *old* value wrapped with `Some` and `None` otherwise. @@ -1866,17 +1865,12 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { S: BuildHasher, { let before = unsafe { (*self.cur).links.value.next }; - self.insert(key, value, || before) + self.insert(key, value, before) } - // Inserts `item` immediately before the element returned by the `before` closure function. + // Inserts an element immediately before the given `before` node. #[inline] - fn insert( - &mut self, - key: K, - value: V, - before: impl FnOnce() -> NonNull>, - ) -> Option + fn insert(&mut self, key: K, value: V, before: NonNull>) -> Option where K: Eq + Hash, S: BuildHasher, @@ -1892,13 +1886,13 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { let mut node = *occupied.into_mut(); let pv = mem::replace(&mut node.as_mut().entry_mut().1, value); detach_node(node); - attach_before(node, before()); + attach_before(node, before); Some(pv) } Err(_) => { let mut new_node = allocate_node(self.free); new_node.as_mut().put_entry((key, value)); - attach_before(new_node, before()); + attach_before(new_node, before); let hash_builder = self.hash_builder; self.table.insert_unique(hash, new_node, move |k| { hash_key(hash_builder, (*k).as_ref().key_ref()) diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index c1d0573..eb85550 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -702,7 +702,7 @@ fn test_cursor_mut_insert_before() { map.insert(3, 3); map.insert(4, 4); - // Insert new item in the middle + // Insert new element in the middle if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { entry.cursor_mut().insert_before(5, 5); assert!(map @@ -711,7 +711,7 @@ fn test_cursor_mut_insert_before() { .eq([(3, 3), (5, 5), (4, 4)].iter().copied())); } - // Insert new item at the very end of the list + // Insert new element at the very end of the list if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { let mut cursor = entry.cursor_mut(); cursor.move_prev(); @@ -722,7 +722,7 @@ fn test_cursor_mut_insert_before() { .eq([(3, 3), (5, 5), (4, 4), (6, 6)].iter().copied())); } - // Relocate item and override value + // Relocate element and override value if let linked_hash_map::Entry::Occupied(entry) = map.entry(5) { entry.cursor_mut().insert_before(4, 42); assert!(map @@ -739,7 +739,7 @@ fn test_cursor_mut_insert_after() { map.insert(3, 3); map.insert(4, 4); - // Insert new item in the middle. + // Insert new element in the middle. if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { entry.cursor_mut().insert_after(5, 5); assert!(map @@ -748,7 +748,7 @@ fn test_cursor_mut_insert_after() { .eq([(3, 3), (5, 5), (4, 4)].iter().copied())); } - // Insert new item as the first one. + // Insert new element as the first one. if let linked_hash_map::Entry::Occupied(entry) = map.entry(4) { let mut cursor = entry.cursor_mut(); cursor.move_next(); @@ -763,7 +763,7 @@ fn test_cursor_mut_insert_after() { #[test] fn test_cursor_front_mut() { let mut map: LinkedHashMap = LinkedHashMap::new(); - // the CursorMut in an empty LinkedHashMap will always return `None` as its + // The `CursorMut`` in an empty LinkedHashMap will always return `None` as its // current element, regardless of any move in any direction. let mut cursor = map.cursor_front_mut(); assert!(cursor.current().is_none()); From a520781ae59ceee958e5272772036a73cd775507 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Sun, 14 Apr 2024 17:13:09 +1000 Subject: [PATCH 12/13] handle the corner-case when inserting before itself --- src/linked_hash_map.rs | 6 ++++-- tests/linked_hash_map.rs | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 1b6df36..991ecf3 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -1885,8 +1885,10 @@ impl<'a, K, V, S> CursorMut<'a, K, V, S> { Ok(occupied) => { let mut node = *occupied.into_mut(); let pv = mem::replace(&mut node.as_mut().entry_mut().1, value); - detach_node(node); - attach_before(node, before); + if node != before { + detach_node(node); + attach_before(node, before); + } Some(pv) } Err(_) => { diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index eb85550..f07e691 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -760,6 +760,27 @@ fn test_cursor_mut_insert_after() { } } +#[test] +fn test_cursor_mut_insert_before_itself() { + let mut map = LinkedHashMap::new(); + + map.insert(2, 2); + map.insert(3, 3); + map.insert(4, 4); + + // Insert a new value before its key. This is a corner case that needs to be + // handled explicitly. + if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { + entry.cursor_mut().insert_before(3, 5); + let r = map.iter().map(|(k, v)| (*k, *v)).collect::>(); + println!("{r:?}"); + assert!(map + .iter() + .map(|(k, v)| (*k, *v)) + .eq([(2, 2), (3, 5), (4, 4)].iter().copied())); + } +} + #[test] fn test_cursor_front_mut() { let mut map: LinkedHashMap = LinkedHashMap::new(); From f4b9a7ffe12bfab58397db9b759d3c71063bdc58 Mon Sep 17 00:00:00 2001 From: Oleg Lebedev Date: Thu, 18 Apr 2024 20:06:04 +1000 Subject: [PATCH 13/13] address nitpicks --- src/linked_hash_map.rs | 33 +++++++++++++++------------------ tests/linked_hash_map.rs | 2 -- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 991ecf3..141d677 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -504,20 +504,25 @@ where } } - /// Returns the `CursorMut` over the front node. - /// - /// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and - /// will always return `None` as its current element, regardless of any move in any - /// direction. - pub fn cursor_front_mut(&mut self) -> CursorMut { + // Returns the `CursorMut` over the _guard_ node. + fn cursor_mut(&mut self) -> CursorMut { unsafe { ensure_guard_node(&mut self.values) }; - let mut c = CursorMut { + CursorMut { cur: self.values.as_ptr(), hash_builder: &self.hash_builder, free: &mut self.free, values: &mut self.values, table: &mut self.table, - }; + } + } + + /// Returns the `CursorMut` over the front node. + /// + /// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and + /// will always return `None` as its current element, regardless of any move in any + /// direction. + pub fn cursor_front_mut(&mut self) -> CursorMut { + let mut c = self.cursor_mut(); c.move_next(); c } @@ -528,14 +533,7 @@ where /// will always return `None` as its current element, regardless of any move in any /// direction. pub fn cursor_back_mut(&mut self) -> CursorMut { - unsafe { ensure_guard_node(&mut self.values) }; - let mut c = CursorMut { - cur: self.values.as_ptr(), - hash_builder: &self.hash_builder, - free: &mut self.free, - values: &mut self.values, - table: &mut self.table, - }; + let mut c = self.cursor_mut(); c.move_prev(); c } @@ -1421,8 +1419,7 @@ pub struct Drain<'a, K, V> { /// or start of the list. From this position, the cursor can move in either direction as the /// linked list is circular, with the guard node connecting the two ends. /// - The current implementation does not include an `index` method, as it does not track the index -/// of its elements. It operates by providing elements as key-value tuples, allowing the value to be -/// modified via a mutable reference while the key could not be changed. +/// of its elements. It provides access to each map entry as a tuple of `(&K, &mut V)`. /// pub struct CursorMut<'a, K, V, S> { cur: *mut Node, diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index f07e691..1ada735 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -772,8 +772,6 @@ fn test_cursor_mut_insert_before_itself() { // handled explicitly. if let linked_hash_map::Entry::Occupied(entry) = map.entry(3) { entry.cursor_mut().insert_before(3, 5); - let r = map.iter().map(|(k, v)| (*k, *v)).collect::>(); - println!("{r:?}"); assert!(map .iter() .map(|(k, v)| (*k, *v))