From 69e297d9e8d1dad6ef88f0e21df1635824bfe69d Mon Sep 17 00:00:00 2001 From: Yosi Hezi Date: Sat, 20 Aug 2022 22:57:58 +0300 Subject: [PATCH] Add entry_refresh --- src/lib.rs | 44 ++++++++++++++++++++++ src/tests/mod.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d34d5ac..d9f328b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,6 +262,50 @@ impl LinkedHashMap { Entry::Vacant(VacantEntry { key: k, map: self }) } + /// Gets the given key's corresponding entry in the map for in-place manipulation. + /// If entry is found, it is moved to the end of the list. + /// This operation can be used in implementation of LRU cache. + /// + /// # Examples + /// + /// ``` + /// use linked_hash_map::LinkedHashMap; + /// + /// let mut letters = LinkedHashMap::new(); + /// + /// for ch in "a short treatise on fungi".chars() { + /// let counter = letters.entry_refresh(ch).or_insert(0); + /// *counter += 1; + /// } + /// + /// assert_eq!(letters[&'s'], 2); + /// assert_eq!(letters[&'t'], 3); + /// assert_eq!(letters[&'u'], 1); + /// assert_eq!(letters.get(&'y'), None); + /// + /// letters.entry_refresh('y').or_insert(0); + /// letters.entry_refresh('t').or_insert(0); + /// assert_eq!(letters.back(), Some((&'t', &3))); + /// ``` + pub fn entry_refresh(&mut self, k: K) -> Entry { + let self_ptr: *mut Self = self; + + let (_value, node_ptr_opt) = match self.map.get(Qey::from_ref(&k)) { + None => (None, None), + Some(node) => (Some(unsafe { &mut (**node).value }), Some(*node)), + }; + if let Some(node_ptr) = node_ptr_opt { + self.detach(node_ptr); + self.attach(node_ptr); + return Entry::Occupied(OccupiedEntry { + entry: node_ptr, + map: self_ptr, + marker: marker::PhantomData, + }); + } + Entry::Vacant(VacantEntry { key: k, map: self }) + } + /// Returns an iterator visiting all entries in insertion order. /// Iterator element type is `OccupiedEntry`. Allows for removal /// as well as replacing the entry. diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 5e0e73a..420c195 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -111,6 +111,100 @@ fn test_entry_and_modify() { assert_eq!(map.get("there"), None); } +#[test] +fn test_entry_refresh_insert_vacant() { + let mut map = LinkedHashMap::new(); + match map.entry_refresh("1".to_string()) { + Entry::Vacant(e) => { + assert_eq!(*e.insert(vec![10, 10]), vec![10, 10]); + } + _ => panic!("fail"), + } + assert!(map.contains_key("1")); + assert_eq!(map["1"], vec![10, 10]); + + match map.entry_refresh("1".to_string()) { + Entry::Occupied(mut e) => { + assert_eq!(*e.get(), vec![10, 10]); + assert_eq!(e.insert(vec![10, 16]), vec![10, 10]); + } + _ => panic!("fail"), + } + + assert!(map.contains_key("1")); + assert_eq!(map["1"], vec![10, 16]); + + match map.entry_refresh("1".to_string()) { + Entry::Occupied(e) => { + assert_eq!(e.remove(), vec![10, 16]); + } + _ => panic!("fail"), + } +} + +#[test] +fn test_entry_refresh_or_default() { + let mut map = LinkedHashMap::>::new(); + map.entry_refresh("hello".to_string()).or_default().push(4); + map.entry_refresh("hello".to_string()).or_default().push(5); + map.entry_refresh("hello".to_string()).or_default().push(8); + + map.entry_refresh("bye".to_string()).or_default().push(9); + + map.entry_refresh("there".to_string()).or_default(); + + assert_eq!(map["hello"], &[4, 5, 8]); + assert_eq!(map["bye"], &[9]); + assert_eq!(map["there"], &[]); +} + +#[test] +fn test_entry_refresh_and_modify() { + let mut map = LinkedHashMap::>::new(); + map.entry_refresh("hello".to_string()) + .and_modify(|v| v.push(3)) + .or_default(); + map.entry_refresh("hello".to_string()) + .and_modify(|v| v.push(4)) + .or_default(); + map.entry_refresh("hello".to_string()) + .and_modify(|v| v.push(5)) + .or_default(); + + map.entry_refresh("bye".to_string()).or_default(); + + map.entry_refresh("bye".to_string()) + .and_modify(|v| v.push(3)) + .and_modify(|v| v.push(4)) + .and_modify(|v| v.push(5)); + + map.entry_refresh("there".to_string()) + .and_modify(|v| v.push(3)); + + assert_eq!(map["hello"], &[4, 5]); + assert_eq!(map["bye"], &[3, 4, 5]); + assert_eq!(map.get("there"), None); +} + +#[test] +fn test_entry_refresh_refreshing() { + let mut map = LinkedHashMap::new(); + + for ch in "this is refreshing".chars() { + let counter = map.entry_refresh(ch).or_insert(0); + *counter += 1; + } + assert_eq!(map.back(), Some((&'g', &1))); + map.entry_refresh('r').and_modify(|v| *v *= 2); + assert_eq!(map.back(), Some((&'r', &4))); + map.entry_refresh('y').and_modify(|v| *v *= 2).or_insert(0); + assert_eq!(map.back(), Some((&'y', &0))); + + assert_eq!(map.front(), Some((&'t', &1))); + map.entry_refresh('t'); + assert_eq!(map.back(), Some((&'t', &1))); +} + #[test] fn test_entries_replacing() { let mut map = LinkedHashMap::new(); @@ -184,6 +278,7 @@ fn test_entries_remove() { assert!(map.is_empty()); } + #[test] fn entries_insert() { let mut map = LinkedHashMap::new();