diff --git a/Cargo.toml b/Cargo.toml index 714e9be..665efc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orx-linked-list" -version = "2.1.0" +version = "2.2.0" edition = "2021" authors = ["orxfun "] description = "An efficient and recursive singly and doubly linked list implementation." @@ -10,8 +10,8 @@ keywords = ["linked", "list", "vec", "array", "pinned"] categories = ["data-structures", "rust-patterns"] [dependencies] -orx-selfref-col = "1.2" -orx-split-vec = "2.1" +orx-selfref-col = "1.3" +orx-split-vec = "2.3" [dev-dependencies] rand = "0.8" diff --git a/README.md b/README.md index 4cb0624..d0cf528 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,22 @@ An efficient and recursive singly and doubly linked list implementation. +* *efficient*: Please see benchmarks section for performance reports of common linked list operations. Furthermore, `List` implementation emphasizes safe constant time access and mutations through usage of `NodeIndex`. +* *singly or doubly*: This is a generic parameter of the `List`. As expected, `Doubly` allows for more operations than `Singly`; however, it keeps two references per node rather than one. +* *recursive*: `List` allows creating a list by combining two lists in constant time. + + ## Variants -* `type SinglyLinkedList<'a, T> = List<'a, Singly, T>;` -* `type DoublyLinkedList<'a, T> = List<'a, Doubly, T>;` +* **`List`** where `T` is the type of elements: + * **`List`**: is the singly linked list, where each node holds a reference to the next node. + * This is equivalent to `List>, T>`. + * The alternative memory policy is `List, T>`. + * **`List`**: is the doubly linked list, where each node holds references to the previous and next nodes. + * This is equivalent to `List>, T>`. + * The alternative memory policy is `List, T>`. + +*For possible memory management policies, please see advanced usage section.* ## Time Complexity of Methods @@ -18,24 +30,24 @@ The following is the list of methods with constant time **O(1)** time complexity | ***O(1)*** Methods | | -------- | -| `front`, `back`: access to front and back of the list | -| `get`: access to to any node with a given index | -| `push_front`, `push_back`: push to front or back (*d*) of the list | -| `pop_front`, `pop_back`: pop from front and back (*d*) of the list | -| `insert_prev_to`, `insert_next_to`: insert a value previous or next to an existing node with a given index (*d*) | -| `append_front`, `append_back`: append another list to front or back of the list | -| `iter`, `iter_from_back`: create an iterator from the front or back (*d*) of the list; iterating has O(n) time complexity | -| `iter_forward_from`, `iter_backward_from`: create a forward or backward (*d*) iterator from any intermediate node with a given index; iterating has O(n) time complexity | +| **`front`, `back`**: access to front and back of the list | +| **`get`**: access to to any node with a given index | +| **`push_front`, `push_back`**: push to front or back (*d*) of the list | +| **`pop_front`, `pop_back`**: pop from front and back (*d*) of the list | +| **`insert_prev_to`, `insert_next_to`**: insert a value previous or next to an existing node with a given index (*d*) | +| **`append_front`, `append_back`**: append another list to front or back of the list | +| **`iter`, `iter_from_back`**: create an iterator from the front or back (*d*) of the list; iterating has O(n) time complexity | +| **`iter_forward_from`, `iter_backward_from`**: create a forward or backward (*d*) iterator from any intermediate node with a given index; iterating has O(n) time complexity | | ***O(n)*** Methods | | -------- | -| `index_of`: get the index of an element, which can later be used for ***O(1)*** methods | -| `contains`, `position_of`: check the existence or position of a value | -| `insert_at`: insert an element to an arbitrary position of the list | -| `remove_at`: remove an element from an arbitrary position of the list | -| `iter`, `iter_from_back`: iterate from the front or back (*d*) of the list | -| `iter_forward_from`, `iter_backward_from`: iterate in forward or backward (*d*) direction from any intermediate node with a given index | -| `retain`, `retain_collect`: retain keeping elements satisfying a predicate and optionally collect removed elements | +| **`index_of`**: get the index of an element, which can later be used for ***O(1)*** methods | +| **`contains`, `position_of`**: check the existence or position of a value | +| **`insert_at`**: insert an element to an arbitrary position of the list | +| **`remove_at`**: remove an element from an arbitrary position of the list | +| **`iter`, `iter_from_back`**: iterate from the front or back (*d*) of the list | +| **`iter_forward_from`, `iter_backward_from`**: iterate in forward or backward (*d*) direction from any intermediate node with a given index | +| **`retain`, `retain_collect`**: retain keeping elements satisfying a predicate and optionally collect removed elements | ## Examples @@ -52,11 +64,9 @@ fn eq<'a, I: Iterator + Clone>(iter: I, slice: &[u32]) -> bool { } let _list: List = List::new(); -let _list = SinglyLinkedList::::new(); let _list: List = List::new(); -let _list = DoublyLinkedList::::new(); -let mut list = DoublyLinkedList::from_iter([3, 4, 5]); +let mut list = List::::from_iter([3, 4, 5]); assert_eq!(list.front(), Some(&3)); assert_eq!(list.back(), Some(&5)); assert!(eq(list.iter(), &[3, 4, 5])); @@ -69,11 +79,11 @@ list.push_back(5); list.push_front(3); assert!(eq(list.iter(), &[3, 4, 5])); -let other = DoublyLinkedList::from_iter([6, 7, 8, 9]); +let other = List::::from_iter([6, 7, 8, 9]); list.append_back(other); assert!(eq(list.iter(), &[3, 4, 5, 6, 7, 8, 9])); -let other = DoublyLinkedList::from_iter([0, 1, 2]); +let other = List::::from_iter([0, 1, 2]); list.append_front(other); assert!(eq(list.iter(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); @@ -90,17 +100,16 @@ assert!(eq(odds.iter(), &[1, 3])); ### `NodeIndex` Usage -`NodeIndex` allows indexing into the collection in constant time with safety guarantees. The indices returned by growth methods, such as `push_back` or `append_next_to`, can be stored externally or an index for a value can be obtained in linear time with `index_of` method. +`NodeIndex` allows indexing into the collection in constant time with safety guarantees. The indices returned by growth methods, such as `push_back` or `append_next_to`, can be stored externally. Otherwise, an index for a value can be searched and obtained in linear time with `index_of` method. You may see below that these indices enable constant time access and mutation methods. ```rust use orx_linked_list::*; -use orx_selfref_col::NodeIndexError; fn eq<'a, I: Iterator + Clone>(iter: I, slice: &[char]) -> bool { iter.clone().count() == slice.len() && iter.zip(slice.iter()).all(|(a, b)| a == b) } -let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); +let mut list = List::::from_iter(['a', 'b', 'c', 'd']); let x = list.index_of(&'x'); assert!(x.is_none()); @@ -131,6 +140,162 @@ assert_eq!( list.get_or_error(b).err(), Some(NodeIndexError::RemovedNode) ); + +// indices can also be stored on insertion +let mut list = List::::from_iter(['a', 'b', 'c', 'd']); + +let x = list.push_back('x'); // grab index of x in O(1) on insertion + +_ = list.push_back('e'); +_ = list.push_back('f'); +assert!(eq(list.iter(), &['a', 'b', 'c', 'd', 'x', 'e', 'f'])); + +let data_x = list.get(x); // O(1) +assert_eq!(data_x, Some(&'x')); + +list.insert_prev_to(x, 'w').unwrap(); // O(1) +list.insert_next_to(x, 'y').unwrap(); // O(1) +assert!(eq(list.iter(), &['a', 'b', 'c', 'd', 'w', 'x', 'y', 'e', 'f'])); +``` + +
+ +### Advanced Usage + +`NodeIndex` is useful in overcoming the major drawback of linked lists that it requires O(n) time to reach the location to apply the O(1) mutation. With holding the required `NodeIndex`, these mutations can be achieved in O(1). However, in order to use these constant time methods, the node index must be valid. + +There are three possible reasons why a node index can be invalid and using it in relevant methods returns `NodeIndexError`: +- **a.** We are using the `NodeIndex` on a different `List`. +- **b.** We are using the `NodeIndex` while the corresponding element is removed from the `List`. +- **c.** `List` executed a memory reclaim under the hood in order to improve memory utilization. + +Notice that **a** and **b** are obviously mistakes, and hence, receiving an error is straightforward. Actually, we can see that using a `NodeIndex` on a `List` is much safer than using a `usize` on a `Vec`, as we are not protected against these mistakes in standard vector. + +However, **c** is completely related with underlying memory management of the `List`. There are two available policies, which are set as the generic argument of both `Singly` and `Doubly`: +* `MemoryReclaimOnThreshold` +* `MemoryReclaimNever` + +#### Default Policy: `MemoryReclaimOnThreshold<2>` + +Adding elements to the `List` leads the underlying storage to grow, as expected. On the other hand, removing elements from the list leaves holes in the corresponding positions. In other words, the values are taken out but the memory location where the value is taken out is not immediately used for new elements. + +The `MemoryReclaimOnThreshold` policy automatically reclaims these holes whenever the utilization falls below a threshold. The threshold is a function of the constant generic parameter `D`. Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`. +* when `D = 0`: memory will be reclaimed when utilization is below 0.00% (equivalent to never). +* when `D = 1`: memory will be reclaimed when utilization is below 50.00%. +* when `D = 2`: memory will be reclaimed when utilization is below 75.00%. +* when `D = 3`: memory will be reclaimed when utilization is below 87.50%. +* ... + +Underlying `PinnedVec` does not reallocate on memory reclaim operations. Instead, it efficiently moves around the elements within already claimed memory to fill the gaps and repairs the links among the nodes. However, since the positions of the elements will be moved, already obtained `NodeIndex`es might potentially be pointing to wrong positions. +* Fortunately, `NodeIndex` is aware of this operation. Therefore, it is **not** possible to wrongly use the index. If we obtain a node index, then the list reclaims memory, and then we try to use this node index on this list, we receive `NodeIndexError::ReorganizedCollection`. +* Unfortunately, the `NodeIndex` now is not useful. All we can do is re-obtain the index by a linear search with methods such as `index_of`. + +```rust +use orx_linked_list::*; + +fn float_eq(x: f32, y: f32) -> bool { + (x - y).abs() < f32::EPSILON +} + +// MemoryReclaimOnThreshold<2> -> memory will be reclaimed when utilization is below 75% +let mut list = List::::new(); +let a = list.push_back('a'); +list.push_back('b'); +list.push_back('c'); +list.push_back('d'); +list.push_back('e'); + +assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + +// no reorganization; 'a' is still valid +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); + +_ = list.pop_back(); // leaves a hole + +assert!(float_eq(list.node_utilization(), 0.80)); // utilization = 4/5 = 80% + +// no reorganization; 'a' is still valid +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); + +_ = list.pop_back(); // leaves the second hole; we have utilization = 3/5 = 60% + // this is below the threshold 75%, and triggers reclaim + // we claim the two unused nodes / holes + +assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 3/3 = 100% + +// nodes reorganized; 'a' is no more valid +assert_eq!( + list.get_or_error(a), + Err(NodeIndexError::ReorganizedCollection) +); +assert_eq!(list.get(a), None); + +// re-obtain the index +let a = list.index_of(&'a').unwrap(); +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); +``` + +#### Alternative Policy: `MemoryReclaimNever` + +However, it is possible to make sure that the node indices will always be valid, unless we manually invalidate them, by simply eliminating the case **c**. Setting the memory reclaim policy to `MemoryReclaimNever` guarantees that there will be no automatic or implicit memory reorganizations: +* use `List, T>` instead of `List`, or +* use `List, T>` instead of `List`. + +The drawback of this approach is that memory utilization can be low if there is a large number of pop or remove operations. However, `List` gives caller the control to manage memory by the following two methods: +* `List::node_utilization(&self) -> f32` method can be used to see the ratio of number of active/utilized nodes to the number of used nodes. The caller can decide when to take action by the following. +* `List::reclaim_closed_nodes(&mut self)` method can be used to manually run memory reclaim operation which will bring `node_utilization` to 100% while invalidating already created node indices. + +```rust +use orx_linked_list::*; + +fn float_eq(x: f32, y: f32) -> bool { + (x - y).abs() < f32::EPSILON +} + +// MemoryReclaimNever -> memory will never be reclaimed automatically +let mut list = List::, _>::new(); +let a = list.push_back('a'); +list.push_back('b'); +list.push_back('c'); +list.push_back('d'); +list.push_back('e'); + +assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + +// no reorganization; 'a' is still valid +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); + +_ = list.pop_back(); // leaves a hole +_ = list.pop_back(); // leaves the second hole +_ = list.pop_back(); // leaves the third hole + +assert!(float_eq(list.node_utilization(), 0.40)); // utilization = 2/5 = 40% + +// still no reorganization; 'a' is and will always be valid unless we manually reclaim +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); + +list.reclaim_closed_nodes(); + +// we can manually reclaim memory any time we want to maximize utilization +assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 2/2 = 100% + +// we are still protected by list & index validation +// nodes reorganized; 'a' is no more valid, we cannot wrongly use the index +assert_eq!( + list.get_or_error(a), + Err(NodeIndexError::ReorganizedCollection) +); +assert_eq!(list.get(a), None); + +// re-obtain the index +let a = list.index_of(&'a').unwrap(); +assert_eq!(list.get_or_error(a), Ok(&'a')); +assert_eq!(list.get(a), Some(&'a')); ``` ## Internal Features @@ -140,6 +305,8 @@ assert_eq!( * With careful encapsulation, `SelfRefCol` prevents passing in external references to the list and leaking within list node references to outside. Once this is established, it provides methods to easily mutate inter list node references. These features allowed a very convenient implementation of the linked list in this crate with almost no use of the `unsafe` keyword, no read or writes through pointers and no access by indices. Compared to the `std::collections::LinkedList` implementation, it can be observed that `orx_linked_list::List` is a much **higher level implementation**. * Furthermore, `orx_linked_list::List` is **significantly faster** than the standard linked list. One of the main reasons for this is the feature of `SelfRefCol` keeping all close to each other rather than at arbitrary locations in memory which leads to a better cache locality. +
+ ## Benchmarks ### Mutation Ends diff --git a/src/common_traits/debug.rs b/src/common_traits/debug.rs index 6698401..0eea1aa 100644 --- a/src/common_traits/debug.rs +++ b/src/common_traits/debug.rs @@ -2,11 +2,13 @@ use crate::{ list::List, variants::{doubly::Doubly, singly::Singly}, }; +use orx_selfref_col::MemoryReclaimPolicy; use std::fmt::Debug; -impl<'a, T> Debug for List<'a, Singly, T> +impl<'a, T, M> Debug for List<'a, Singly, T> where T: Debug, + M: MemoryReclaimPolicy, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SinglyLinkedList") @@ -17,9 +19,10 @@ where } } -impl<'a, T> Debug for List<'a, Doubly, T> +impl<'a, T, M> Debug for List<'a, Doubly, T> where T: Debug, + M: 'a + MemoryReclaimPolicy, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DoublyLinkedList") diff --git a/src/common_traits/from_iter.rs b/src/common_traits/from_iter.rs index dd5aa56..f6b18b2 100644 --- a/src/common_traits/from_iter.rs +++ b/src/common_traits/from_iter.rs @@ -1,7 +1,11 @@ use crate::{Doubly, List, Singly}; +use orx_selfref_col::MemoryReclaimPolicy; use orx_selfref_col::SelfRefCol; -impl<'a, T> FromIterator for List<'a, Singly, T> { +impl<'a, T, M> FromIterator for List<'a, Singly, T> +where + M: MemoryReclaimPolicy, +{ fn from_iter>(iter: I) -> Self { let mut col = SelfRefCol::from_iter(iter); @@ -25,7 +29,10 @@ impl<'a, T> FromIterator for List<'a, Singly, T> { } } -impl<'a, T> FromIterator for List<'a, Doubly, T> { +impl<'a, T, M> FromIterator for List<'a, Doubly, T> +where + M: MemoryReclaimPolicy, +{ fn from_iter>(iter: I) -> Self { let mut col = SelfRefCol::from_iter(iter); diff --git a/src/common_traits/validators.rs b/src/common_traits/validators.rs index 2d99fe9..3a42113 100644 --- a/src/common_traits/validators.rs +++ b/src/common_traits/validators.rs @@ -2,6 +2,7 @@ use crate::{ list::List, variants::{doubly::Doubly, ends::ListEnds, list_variant::ListVariant, singly::Singly}, }; +use orx_selfref_col::MemoryReclaimPolicy; impl<'a, V, T> List<'a, V, T> where @@ -23,14 +24,20 @@ where } } -impl<'a, T> List<'a, Singly, T> { +impl<'a, T, M> List<'a, Singly, T> +where + M: MemoryReclaimPolicy, +{ #[cfg(test)] pub(crate) fn validate_list(&self) { self.validate_next(); } } -impl<'a, T> List<'a, Doubly, T> { +impl<'a, T, M> List<'a, Doubly, T> +where + M: MemoryReclaimPolicy, +{ #[cfg(test)] pub(crate) fn validate_list(&self) { self.validate_next(); diff --git a/src/iterators/forward.rs b/src/iterators/forward.rs index 7f691d1..12e5b63 100644 --- a/src/iterators/forward.rs +++ b/src/iterators/forward.rs @@ -1,7 +1,6 @@ -use std::iter::FusedIterator; - use crate::variants::list_variant::ListVariant; use orx_selfref_col::{Node, NodeRefSingle, NodeRefs}; +use std::iter::FusedIterator; pub struct IterForward<'iter, 'a, V, T> where diff --git a/src/lib.rs b/src/lib.rs index 70f083e..83ef2b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,22 @@ //! //! An efficient and recursive singly and doubly linked list implementation. //! +//! * *efficient*: Please see benchmarks section for performance reports of common linked list operations. Furthermore, `List` implementation emphasizes safe constant time access and mutations through usage of `NodeIndex`. +//! * *singly or doubly*: This is a generic parameter of the `List`. As expected, `Doubly` allows for more operations than `Singly`; however, it keeps two references per node rather than one. +//! * *recursive*: `List` allows creating a list by combining two lists in constant time. +//! +//! //! ## Variants //! -//! * `type SinglyLinkedList<'a, T> = List<'a, Singly, T>;` -//! * `type DoublyLinkedList<'a, T> = List<'a, Doubly, T>;` +//! * **`List`** where `T` is the type of elements: +//! * **`List`**: is the singly linked list, where each node holds a reference to the next node. +//! * This is equivalent to `List>, T>`. +//! * The alternative memory policy is `List, T>`. +//! * **`List`**: is the doubly linked list, where each node holds references to the previous and next nodes. +//! * This is equivalent to `List>, T>`. +//! * The alternative memory policy is `List, T>`. +//! +//! *For possible memory management policies, please see advanced usage section.* //! //! ## Time Complexity of Methods //! @@ -18,24 +30,24 @@ //! //! | ***O(1)*** Methods | //! | -------- | -//! | `front`, `back`: access to front and back of the list | -//! | `get`: access to to any node with a given index | -//! | `push_front`, `push_back`: push to front or back (*d*) of the list | -//! | `pop_front`, `pop_back`: pop from front and back (*d*) of the list | -//! | `insert_prev_to`, `insert_next_to`: insert a value previous or next to an existing node with a given index (*d*) | -//! | `append_front`, `append_back`: append another list to front or back of the list | -//! | `iter`, `iter_from_back`: create an iterator from the front or back (*d*) of the list; iterating has O(n) time complexity | -//! | `iter_forward_from`, `iter_backward_from`: create a forward or backward (*d*) iterator from any intermediate node with a given index; iterating has O(n) time complexity | +//! | **`front`, `back`**: access to front and back of the list | +//! | **`get`**: access to to any node with a given index | +//! | **`push_front`, `push_back`**: push to front or back (*d*) of the list | +//! | **`pop_front`, `pop_back`**: pop from front and back (*d*) of the list | +//! | **`insert_prev_to`, `insert_next_to`**: insert a value previous or next to an existing node with a given index (*d*) | +//! | **`append_front`, `append_back`**: append another list to front or back of the list | +//! | **`iter`, `iter_from_back`**: create an iterator from the front or back (*d*) of the list; iterating has O(n) time complexity | +//! | **`iter_forward_from`, `iter_backward_from`**: create a forward or backward (*d*) iterator from any intermediate node with a given index; iterating has O(n) time complexity | //! //! | ***O(n)*** Methods | //! | -------- | -//! | `index_of`: get the index of an element, which can later be used for ***O(1)*** methods | -//! | `contains`, `position_of`: check the existence or position of a value | -//! | `insert_at`: insert an element to an arbitrary position of the list | -//! | `remove_at`: remove an element from an arbitrary position of the list | -//! | `iter`, `iter_from_back`: iterate from the front or back (*d*) of the list | -//! | `iter_forward_from`, `iter_backward_from`: iterate in forward or backward (*d*) direction from any intermediate node with a given index | -//! | `retain`, `retain_collect`: retain keeping elements satisfying a predicate and optionally collect removed elements | +//! | **`index_of`**: get the index of an element, which can later be used for ***O(1)*** methods | +//! | **`contains`, `position_of`**: check the existence or position of a value | +//! | **`insert_at`**: insert an element to an arbitrary position of the list | +//! | **`remove_at`**: remove an element from an arbitrary position of the list | +//! | **`iter`, `iter_from_back`**: iterate from the front or back (*d*) of the list | +//! | **`iter_forward_from`, `iter_backward_from`**: iterate in forward or backward (*d*) direction from any intermediate node with a given index | +//! | **`retain`, `retain_collect`**: retain keeping elements satisfying a predicate and optionally collect removed elements | //! //! //! ## Examples @@ -52,11 +64,9 @@ //! } //! //! let _list: List = List::new(); -//! let _list = SinglyLinkedList::::new(); //! let _list: List = List::new(); -//! let _list = DoublyLinkedList::::new(); //! -//! let mut list = DoublyLinkedList::from_iter([3, 4, 5]); +//! let mut list = List::::from_iter([3, 4, 5]); //! assert_eq!(list.front(), Some(&3)); //! assert_eq!(list.back(), Some(&5)); //! assert!(eq(list.iter(), &[3, 4, 5])); @@ -69,11 +79,11 @@ //! list.push_front(3); //! assert!(eq(list.iter(), &[3, 4, 5])); //! -//! let other = DoublyLinkedList::from_iter([6, 7, 8, 9]); +//! let other = List::::from_iter([6, 7, 8, 9]); //! list.append_back(other); //! assert!(eq(list.iter(), &[3, 4, 5, 6, 7, 8, 9])); //! -//! let other = DoublyLinkedList::from_iter([0, 1, 2]); +//! let other = List::::from_iter([0, 1, 2]); //! list.append_front(other); //! assert!(eq(list.iter(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); //! @@ -90,17 +100,16 @@ //! //! ### `NodeIndex` Usage //! -//! `NodeIndex` allows indexing into the collection in constant time with safety guarantees. The indices returned by growth methods, such as `push_back` or `append_next_to`, can be stored externally or an index for a value can be obtained in linear time with `index_of` method. +//! `NodeIndex` allows indexing into the collection in constant time with safety guarantees. The indices returned by growth methods, such as `push_back` or `append_next_to`, can be stored externally. Otherwise, an index for a value can be searched and obtained in linear time with `index_of` method. You may see below that these indices enable constant time access and mutation methods. //! //! ```rust //! use orx_linked_list::*; -//! use orx_selfref_col::NodeIndexError; //! //! fn eq<'a, I: Iterator + Clone>(iter: I, slice: &[char]) -> bool { //! iter.clone().count() == slice.len() && iter.zip(slice.iter()).all(|(a, b)| a == b) //! } //! -//! let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); +//! let mut list = List::::from_iter(['a', 'b', 'c', 'd']); //! //! let x = list.index_of(&'x'); //! assert!(x.is_none()); @@ -131,6 +140,162 @@ //! list.get_or_error(b).err(), //! Some(NodeIndexError::RemovedNode) //! ); +//! +//! // indices can also be stored on insertion +//! let mut list = List::::from_iter(['a', 'b', 'c', 'd']); +//! +//! let x = list.push_back('x'); // grab index of x in O(1) on insertion +//! +//! _ = list.push_back('e'); +//! _ = list.push_back('f'); +//! assert!(eq(list.iter(), &['a', 'b', 'c', 'd', 'x', 'e', 'f'])); +//! +//! let data_x = list.get(x); // O(1) +//! assert_eq!(data_x, Some(&'x')); +//! +//! list.insert_prev_to(x, 'w').unwrap(); // O(1) +//! list.insert_next_to(x, 'y').unwrap(); // O(1) +//! assert!(eq(list.iter(), &['a', 'b', 'c', 'd', 'w', 'x', 'y', 'e', 'f'])); +//! ``` +//! +//!
+//! +//! ### Advanced Usage +//! +//! `NodeIndex` is useful in overcoming the major drawback of linked lists that it requires O(n) time to reach the location to apply the O(1) mutation. With holding the required `NodeIndex`, these mutations can be achieved in O(1). However, in order to use these constant time methods, the node index must be valid. +//! +//! There are three possible reasons why a node index can be invalid and using it in relevant methods returns `NodeIndexError`: +//! - **a.** We are using the `NodeIndex` on a different `List`. +//! - **b.** We are using the `NodeIndex` while the corresponding element is removed from the `List`. +//! - **c.** `List` executed a memory reclaim under the hood in order to improve memory utilization. +//! +//! Notice that **a** and **b** are obviously mistakes, and hence, receiving an error is straightforward. Actually, we can see that using a `NodeIndex` on a `List` is much safer than using a `usize` on a `Vec`, as we are not protected against these mistakes in standard vector. +//! +//! However, **c** is completely related with underlying memory management of the `List`. There are two available policies, which are set as the generic argument of both `Singly` and `Doubly`: +//! * `MemoryReclaimOnThreshold` +//! * `MemoryReclaimNever` +//! +//! #### Default Policy: `MemoryReclaimOnThreshold<2>` +//! +//! Adding elements to the `List` leads the underlying storage to grow, as expected. On the other hand, removing elements from the list leaves holes in the corresponding positions. In other words, the values are taken out but the memory location where the value is taken out is not immediately used for new elements. +//! +//! The `MemoryReclaimOnThreshold` policy automatically reclaims these holes whenever the utilization falls below a threshold. The threshold is a function of the constant generic parameter `D`. Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`. +//! * when `D = 0`: memory will be reclaimed when utilization is below 0.00% (equivalent to never). +//! * when `D = 1`: memory will be reclaimed when utilization is below 50.00%. +//! * when `D = 2`: memory will be reclaimed when utilization is below 75.00%. +//! * when `D = 3`: memory will be reclaimed when utilization is below 87.50%. +//! * ... +//! +//! Underlying `PinnedVec` does not reallocate on memory reclaim operations. Instead, it efficiently moves around the elements within already claimed memory to fill the gaps and repairs the links among the nodes. However, since the positions of the elements will be moved, already obtained `NodeIndex`es might potentially be pointing to wrong positions. +//! * Fortunately, `NodeIndex` is aware of this operation. Therefore, it is **not** possible to wrongly use the index. If we obtain a node index, then the list reclaims memory, and then we try to use this node index on this list, we receive `NodeIndexError::ReorganizedCollection`. +//! * Unfortunately, the `NodeIndex` now is not useful. All we can do is re-obtain the index by a linear search with methods such as `index_of`. +//! +//! ```rust +//! use orx_linked_list::*; +//! +//! fn float_eq(x: f32, y: f32) -> bool { +//! (x - y).abs() < f32::EPSILON +//! } +//! +//! // MemoryReclaimOnThreshold<2> -> memory will be reclaimed when utilization is below 75% +//! let mut list = List::::new(); +//! let a = list.push_back('a'); +//! list.push_back('b'); +//! list.push_back('c'); +//! list.push_back('d'); +//! list.push_back('e'); +//! +//! assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% +//! +//! // no reorganization; 'a' is still valid +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); +//! +//! _ = list.pop_back(); // leaves a hole +//! +//! assert!(float_eq(list.node_utilization(), 0.80)); // utilization = 4/5 = 80% +//! +//! // no reorganization; 'a' is still valid +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); +//! +//! _ = list.pop_back(); // leaves the second hole; we have utilization = 3/5 = 60% +//! // this is below the threshold 75%, and triggers reclaim +//! // we claim the two unused nodes / holes +//! +//! assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 3/3 = 100% +//! +//! // nodes reorganized; 'a' is no more valid +//! assert_eq!( +//! list.get_or_error(a), +//! Err(NodeIndexError::ReorganizedCollection) +//! ); +//! assert_eq!(list.get(a), None); +//! +//! // re-obtain the index +//! let a = list.index_of(&'a').unwrap(); +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); +//! ``` +//! +//! #### Alternative Policy: `MemoryReclaimNever` +//! +//! However, it is possible to make sure that the node indices will always be valid, unless we manually invalidate them, by simply eliminating the case **c**. Setting the memory reclaim policy to `MemoryReclaimNever` guarantees that there will be no automatic or implicit memory reorganizations: +//! * use `List, T>` instead of `List`, or +//! * use `List, T>` instead of `List`. +//! +//! The drawback of this approach is that memory utilization can be low if there is a large number of pop or remove operations. However, `List` gives caller the control to manage memory by the following two methods: +//! * `List::node_utilization(&self) -> f32` method can be used to see the ratio of number of active/utilized nodes to the number of used nodes. The caller can decide when to take action by the following. +//! * `List::reclaim_closed_nodes(&mut self)` method can be used to manually run memory reclaim operation which will bring `node_utilization` to 100% while invalidating already created node indices. +//! +//! ```rust +//! use orx_linked_list::*; +//! +//! fn float_eq(x: f32, y: f32) -> bool { +//! (x - y).abs() < f32::EPSILON +//! } +//! +//! // MemoryReclaimNever -> memory will never be reclaimed automatically +//! let mut list = List::, _>::new(); +//! let a = list.push_back('a'); +//! list.push_back('b'); +//! list.push_back('c'); +//! list.push_back('d'); +//! list.push_back('e'); +//! +//! assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% +//! +//! // no reorganization; 'a' is still valid +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); +//! +//! _ = list.pop_back(); // leaves a hole +//! _ = list.pop_back(); // leaves the second hole +//! _ = list.pop_back(); // leaves the third hole +//! +//! assert!(float_eq(list.node_utilization(), 0.40)); // utilization = 2/5 = 40% +//! +//! // still no reorganization; 'a' is and will always be valid unless we manually reclaim +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); +//! +//! list.reclaim_closed_nodes(); +//! +//! // we can manually reclaim memory any time we want to maximize utilization +//! assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 2/2 = 100% +//! +//! // we are still protected by list & index validation +//! // nodes reorganized; 'a' is no more valid, we cannot wrongly use the index +//! assert_eq!( +//! list.get_or_error(a), +//! Err(NodeIndexError::ReorganizedCollection) +//! ); +//! assert_eq!(list.get(a), None); +//! +//! // re-obtain the index +//! let a = list.index_of(&'a').unwrap(); +//! assert_eq!(list.get_or_error(a), Ok(&'a')); +//! assert_eq!(list.get(a), Some(&'a')); //! ``` //! //! ## Internal Features @@ -140,6 +305,8 @@ //! * With careful encapsulation, `SelfRefCol` prevents passing in external references to the list and leaking within list node references to outside. Once this is established, it provides methods to easily mutate inter list node references. These features allowed a very convenient implementation of the linked list in this crate with almost no use of the `unsafe` keyword, no read or writes through pointers and no access by indices. Compared to the `std::collections::LinkedList` implementation, it can be observed that `orx_linked_list::List` is a much **higher level implementation**. //! * Furthermore, `orx_linked_list::List` is **significantly faster** than the standard linked list. One of the main reasons for this is the feature of `SelfRefCol` keeping all close to each other rather than at arbitrary locations in memory which leads to a better cache locality. //! +//!
+//! //! ## Benchmarks //! //! ### Mutation Ends @@ -181,4 +348,7 @@ mod option_utils; mod variants; pub use list::{DoublyLinkedList, List, SinglyLinkedList}; +pub use orx_selfref_col::{ + MemoryReclaimNever, MemoryReclaimOnThreshold, MemoryReclaimPolicy, NodeIndexError, +}; pub use variants::{doubly::Doubly, singly::Singly}; diff --git a/src/list.rs b/src/list.rs index 521edca..b891ceb 100644 --- a/src/list.rs +++ b/src/list.rs @@ -3,19 +3,24 @@ use crate::{ option_utils::some_only_if, variants::{doubly::Doubly, ends::ListEnds, list_variant::ListVariant, singly::Singly}, }; -use orx_selfref_col::{Node, NodeIndex, NodeIndexError, NodeRefs, SelfRefCol}; +use orx_selfref_col::{ + MemoryReclaimOnThreshold, MemoryReclaimPolicy, Node, NodeDataLazyClose, NodeIndex, + NodeIndexError, NodeRefs, Reclaim, SelfRefCol, SelfRefColMut, Variant, +}; use orx_split_vec::{Recursive, SplitVec}; +pub(crate) type DefaultMemoryPolicy = MemoryReclaimOnThreshold<2>; + /// A singly linked [`List`] allowing pushing and popping elements at the front in constant time. -pub type SinglyLinkedList<'a, T> = List<'a, Singly, T>; +pub type SinglyLinkedList<'a, T, M = DefaultMemoryPolicy> = List<'a, Singly, T>; /// A doubly linked [`List`] allowing pushing and popping elements both at the front and the back in constant time. -pub type DoublyLinkedList<'a, T> = List<'a, Doubly, T>; +pub type DoublyLinkedList<'a, T, M = DefaultMemoryPolicy> = List<'a, Doubly, T>; type MutKey<'rf, 'a, V, T> = orx_selfref_col::SelfRefColMut<'rf, 'a, V, T, SplitVec, Recursive>>; -type DoublyMutKey<'rf, 'a, T> = MutKey<'rf, 'a, Doubly, T>; -type SinglyMutKey<'rf, 'a, T> = MutKey<'rf, 'a, Singly, T>; +type DoublyMutKey<'rf, 'a, T, M> = MutKey<'rf, 'a, Doubly, T>; +type SinglyMutKey<'rf, 'a, T, M> = MutKey<'rf, 'a, Singly, T>; /// Core structure for singly and doubly linked lists: /// * `type SinglyLinkedList<'a, T> = List<'a, Singly, T>;` @@ -157,7 +162,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// assert!(list.is_empty()); /// @@ -179,7 +184,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = SinglyLinkedList::new(); + /// let mut list = List::::new(); /// /// assert!(list.front().is_none()); /// @@ -204,7 +209,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// assert!(list.back().is_none()); /// @@ -236,7 +241,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// list.push_front('b'); /// list.push_back('c'); @@ -266,7 +271,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// let b = list.push_front('b'); /// list.push_back('c'); @@ -304,7 +309,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// list.push_front('b'); /// list.push_back('c'); @@ -399,7 +404,11 @@ where /// assert_eq!(Some('a'), popped); /// assert!(list.is_empty()); /// ``` - pub fn pop_front(&mut self) -> Option { + pub fn pop_front(&mut self) -> Option + where + for<'rf> SelfRefColMut<'rf, 'a, V, T, SplitVec, Recursive>>: + Reclaim, + { self.col.mutate_take((), |x, _| Self::pop_front_node(&x)) } @@ -424,7 +433,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// let a = list.push_back('a'); /// let b = list.push_back('b'); @@ -445,6 +454,7 @@ where /// assert_eq!(None, list.get(a)); /// assert_eq!(None, list.get(b)); /// ``` + #[inline] pub fn get(&self, node_index: NodeIndex<'a, V, T>) -> Option<&T> { node_index.data(&self.col) } @@ -468,9 +478,8 @@ where /// /// ```rust /// use orx_linked_list::*; - /// use orx_selfref_col::NodeIndexError; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// let a = list.push_back('a'); /// let b = list.push_back('b'); @@ -478,24 +487,28 @@ where /// assert_eq!(Ok(&'a'), list.get_or_error(a)); /// assert_eq!(Ok(&'b'), list.get_or_error(b)); /// - /// list.push_front('c'); + /// list.push_back('c'); /// list.push_back('d'); - /// list.push_front('e'); + /// list.push_back('e'); /// list.push_back('f'); /// + /// assert_eq!( + /// vec!['a', 'b', 'c', 'd', 'e', 'f'], + /// list.iter().copied().collect::>() + /// ); + /// /// assert_eq!(Ok(&'a'), list.get_or_error(a)); /// assert_eq!(Ok(&'b'), list.get_or_error(b)); /// - /// list.clear(); + /// let removed = list.remove(a); + /// assert_eq!(removed, Ok('a')); /// - /// assert_eq!(Some(NodeIndexError::RemovedNode), list.get_or_error(a).err()); - /// assert_eq!(Some(NodeIndexError::RemovedNode), list.get_or_error(b).err()); + /// assert_eq!(Err(NodeIndexError::RemovedNode), list.get_or_error(a)); + /// assert_eq!(Ok(&'b'), list.get_or_error(b)); /// ``` + #[inline] pub fn get_or_error(&self, node_index: NodeIndex<'a, V, T>) -> Result<&T, NodeIndexError> { - match node_index.data(&self.col) { - Some(data) => Ok(data), - None => Err(NodeIndexError::RemovedNode), - } + node_index.data_or_error(&self.col) } /// ***O(n)*** Performs a forward search from the front and returns the index of the first node with value equal to the given `value`. @@ -510,7 +523,7 @@ where /// use orx_linked_list::*; /// use orx_selfref_col::NodeIndexError; /// - /// let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); + /// let mut list = List::::from_iter(['a', 'b', 'c', 'd']); /// /// let x = list.index_of(&'x'); /// assert!(x.is_none()); @@ -563,7 +576,7 @@ where /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); + /// let mut list = List::::from_iter(['a', 'b', 'c', 'd']); /// /// assert!(list.contains(&'a')); /// assert!(!list.contains(&'x')); @@ -589,7 +602,7 @@ where /// use orx_linked_list::*; /// use orx_selfref_col::NodeIndexError; /// - /// let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); + /// let mut list = List::::from_iter(['a', 'b', 'c', 'd']); /// /// let x = list.position_of(&'x'); /// assert_eq!(x, None); @@ -607,6 +620,126 @@ where .map(|(i, _)| i) } + // memory + /// Returns the node utilization as a fraction of active nodes to the used nodes: + /// * 1.0 when there is no closed node; + /// * 0.0 when all used memory is used by closed nodes. + /// + /// Node utilization can be brought to 100%: + /// * automatically by the underlying memory policy if `MemoryReclaimOnThreshold` is used; or + /// * manually by calling the `reclaim_closed_nodes` method. + /// + /// It is important to note that, memory reclaim operation leads to reorganization of the nodes, which invalidates the node indices obtained before the process. + /// + /// # Examples + /// + /// ```rust + /// use orx_linked_list::*; + /// + /// fn float_eq(x: f32, y: f32) -> bool { + /// (x - y).abs() < f32::EPSILON + /// } + /// + /// // MemoryReclaimOnThreshold<2> -> memory will be reclaimed when utilization is below 75% + /// let mut list = List::::new(); + /// let a = list.push_back('a'); + /// list.push_back('b'); + /// list.push_back('c'); + /// list.push_back('d'); + /// list.push_back('e'); + /// + /// assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + /// + /// // no reorganization; 'a' is still valid + /// assert_eq!(list.get_or_error(a), Ok(&'a')); + /// assert_eq!(list.get(a), Some(&'a')); + /// + /// _ = list.pop_back(); // leaves a hole + /// + /// assert!(float_eq(list.node_utilization(), 0.80)); // utilization = 4/5 = 80% + /// + /// // no reorganization; 'a' is still valid + /// assert_eq!(list.get_or_error(a), Ok(&'a')); + /// assert_eq!(list.get(a), Some(&'a')); + /// + /// _ = list.pop_back(); // leaves the second hole; we have utilization = 3/5 = 60% + /// // this is below the threshold 75%, and triggers reclaim + /// // we claim the two unused nodes / holes + /// + /// assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 3/3 = 100% + /// + /// // nodes reorganized; 'a' is no more valid + /// assert_eq!( + /// list.get_or_error(a), + /// Err(NodeIndexError::ReorganizedCollection) + /// ); + /// assert_eq!(list.get(a), None); + /// ``` + #[inline] + pub fn node_utilization(&self) -> f32 { + self.col.node_utilization() + } + + /// Manually attempts to reclaim closed nodes. + /// + /// # Safety + /// + /// It is important to note that, memory reclaim operation leads to reorganization of the nodes, which invalidates the node indices obtained before the process. + /// + /// # Examples + /// + /// ```rust + /// use orx_linked_list::*; + /// + /// fn float_eq(x: f32, y: f32) -> bool { + /// (x - y).abs() < f32::EPSILON + /// } + /// + /// // MemoryReclaimNever -> memory will never be reclaimed automatically + /// let mut list = List::, _>::new(); + /// let a = list.push_back('a'); + /// list.push_back('b'); + /// list.push_back('c'); + /// list.push_back('d'); + /// list.push_back('e'); + /// + /// assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + /// + /// // no reorganization; 'a' is still valid + /// assert_eq!(list.get_or_error(a), Ok(&'a')); + /// assert_eq!(list.get(a), Some(&'a')); + /// + /// _ = list.pop_back(); // leaves a hole + /// _ = list.pop_back(); // leaves the second hole + /// _ = list.pop_back(); // leaves the third hole + /// + /// assert!(float_eq(list.node_utilization(), 0.40)); // utilization = 2/5 = 40% + /// + /// // still no reorganization; 'a' is and will always be valid unless we manually reclaim + /// assert_eq!(list.get_or_error(a), Ok(&'a')); + /// assert_eq!(list.get(a), Some(&'a')); + /// + /// list.reclaim_closed_nodes(); + /// + /// // we can manually reclaim memory any time we want to maximize utilization + /// assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 2/2 = 100% + /// + /// // we are still protected by list & index validation + /// // nodes reorganized; 'a' is no more valid, we cannot wrongly use the index + /// assert_eq!( + /// list.get_or_error(a), + /// Err(NodeIndexError::ReorganizedCollection) + /// ); + /// assert_eq!(list.get(a), None); + /// ``` + pub fn reclaim_closed_nodes(&mut self) + where + for<'rf> SelfRefColMut<'rf, 'a, V, T, SplitVec, Recursive>>: + Reclaim, + { + self.col.reclaim_closed_nodes() + } + // helpers /// Pushes the `value` as the first node of the list and sets both ends to this first node. #[inline(always)] @@ -646,7 +779,12 @@ where } /// Pops the front node and returns its `value`; returns None if the list is empty. - fn pop_front_node<'rf>(x: &MutKey<'rf, 'a, V, T>) -> Option { + fn pop_front_node<'rf>(x: &MutKey<'rf, 'a, V, T>) -> Option + where + V: Variant<'a, T, Storage = NodeDataLazyClose>, + SelfRefColMut<'rf, 'a, V, T, SplitVec, Recursive>>: + Reclaim, + { x.ends().front().map(|prior_front| { let new_front = *prior_front.next().get(); let new_back = some_only_if(new_front.is_some(), x.ends().back()); @@ -661,7 +799,10 @@ where } } -impl<'a, T: 'a> List<'a, Singly, T> { +impl<'a, T: 'a, M> List<'a, Singly, T> +where + M: MemoryReclaimPolicy, +{ // mut /// ***O(1)*** Pushes the `value` to the `front` of the list. @@ -683,7 +824,7 @@ impl<'a, T: 'a> List<'a, Singly, T> { /// assert_eq!(Some('b'), popped); /// assert_eq!(Some(&'a'), list.front()); /// ``` - pub fn push_front(&mut self, value: T) -> NodeIndex<'a, Singly, T> { + pub fn push_front(&mut self, value: T) -> NodeIndex<'a, Singly, T> { self.col .mutate_take(value, |x, value| match x.ends().front() { Some(prior_front) => { @@ -842,7 +983,7 @@ impl<'a, T: 'a> List<'a, Singly, T> { /// list.insert_at(1, 'x'); /// assert_eq!(&['a', 'x', 'b', 'c'], list.iter().copied().collect::>().as_slice()); ///``` - pub fn insert_at(&mut self, at: usize, value: T) -> NodeIndex<'a, Singly, T> { + pub fn insert_at(&mut self, at: usize, value: T) -> NodeIndex<'a, Singly, T> { assert!(at <= self.len(), "out of bounds"); match at { 0 => self.push_front(value), @@ -877,7 +1018,7 @@ impl<'a, T: 'a> List<'a, Singly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = SinglyLinkedList::from_iter([0, 1, 2, 3, 4]); + /// let mut list = List::::from_iter([0, 1, 2, 3, 4]); /// /// list.retain(&|x| *x % 2 == 0); /// @@ -904,7 +1045,7 @@ impl<'a, T: 'a> List<'a, Singly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = SinglyLinkedList::from_iter([0, 1, 2, 3, 4]); + /// let mut list = List::::from_iter([0, 1, 2, 3, 4]); /// /// let mut odds = vec![]; /// let mut collect = |x| odds.push(x); @@ -922,11 +1063,14 @@ impl<'a, T: 'a> List<'a, Singly, T> { Predicate: Fn(&T) -> bool, Collect: FnMut(T), { - fn remove<'a, T>( - mut_key: &SinglyMutKey<'_, 'a, T>, - prev: Option<&'a Node<'a, Singly, T>>, - node_to_remove: &'a Node<'a, Singly, T>, - ) -> T { + fn remove<'a, T, M>( + mut_key: &SinglyMutKey<'_, 'a, T, M>, + prev: Option<&'a Node<'a, Singly, T>>, + node_to_remove: &'a Node<'a, Singly, T>, + ) -> T + where + M: MemoryReclaimPolicy, + { if let Some(prev) = prev { prev.set_next(mut_key, *node_to_remove.next().get()); } @@ -981,10 +1125,11 @@ impl<'a, T: 'a> List<'a, Singly, T> { /// # Panics /// /// Panics if `self.len() < 2` and/or `at == 0`. + #[allow(clippy::type_complexity)] fn get_prev_and_current_at<'rf>( - mut_key: &SinglyMutKey<'rf, 'a, T>, + mut_key: &SinglyMutKey<'rf, 'a, T, M>, at: usize, - ) -> (&'a Node<'a, Singly, T>, &'a Node<'a, Singly, T>) { + ) -> (&'a Node<'a, Singly, T>, &'a Node<'a, Singly, T>) { let mut prev = unsafe { mut_key.ends().front().unwrap_unchecked() }; let mut current = unsafe { prev.next().get().unwrap_unchecked() }; for _ in 1..at { @@ -996,7 +1141,10 @@ impl<'a, T: 'a> List<'a, Singly, T> { } } -impl<'a, T: 'a> List<'a, Doubly, T> { +impl<'a, T: 'a, M> List<'a, Doubly, T> +where + M: MemoryReclaimPolicy, +{ // get /// ***O(n)*** Performs a backward search from the back and returns the index of the first node with value equal to the given `value`. /// @@ -1010,7 +1158,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// use orx_linked_list::*; /// use orx_selfref_col::NodeIndexError; /// - /// let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); + /// let mut list = List::::from_iter(['a', 'b', 'c', 'd']); /// /// let x = list.index_of_from_back(&'x'); /// assert!(x.is_none()); @@ -1038,7 +1186,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// assert_eq!(list.get(b), None); /// assert_eq!(list.get_or_error(b).err(), Some(NodeIndexError::RemovedNode)); /// ``` - pub fn index_of_from_back(&self, value: &T) -> Option> + pub fn index_of_from_back(&self, value: &T) -> Option, T>> where T: PartialEq, { @@ -1063,7 +1211,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::from_iter(['a', 'b', 'c', 'd']); + /// let mut list = List::::from_iter(['a', 'b', 'c', 'd']); /// /// assert!(list.contains_from_back(&'a')); /// assert!(!list.contains_from_back(&'x')); @@ -1095,7 +1243,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// assert_eq!(Some(&'a'), iter.next()); /// assert!(iter.next().is_none()); /// ``` - pub fn iter_from_back(&self) -> IterBackward<'_, 'a, Doubly, T> { + pub fn iter_from_back(&self) -> IterBackward<'_, 'a, Doubly, T> { IterBackward::new(self.col.ends().back()) } @@ -1112,7 +1260,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// let b = list.push_front('b'); /// list.push_back('c'); @@ -1131,8 +1279,8 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ``` pub fn iter_backward_from( &self, - node_index: NodeIndex<'a, Doubly, T>, - ) -> Result, NodeIndexError> { + node_index: NodeIndex<'a, Doubly, T>, + ) -> Result, T>, NodeIndexError> { match node_index.invalidity_reason_for_collection(&self.col) { None => Ok(IterBackward::new(Some(unsafe { node_index.as_ref_unchecked() @@ -1160,7 +1308,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// assert_eq!(Some('b'), popped); /// assert_eq!(Some(&'a'), list.front()); /// ``` - pub fn push_front(&mut self, value: T) -> NodeIndex<'a, Doubly, T> { + pub fn push_front(&mut self, value: T) -> NodeIndex<'a, Doubly, T> { self.col .mutate_take(value, |x, value| Self::push_front_node(&x, value)) } @@ -1184,7 +1332,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// assert_eq!(Some('b'), popped); /// assert_eq!(Some(&'a'), list.back()); /// ``` - pub fn push_back(&mut self, value: T) -> NodeIndex<'a, Doubly, T> { + pub fn push_back(&mut self, value: T) -> NodeIndex<'a, Doubly, T> { self.col .mutate_take(value, |x, value| Self::push_back_node(&x, value)) } @@ -1375,7 +1523,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// use orx_linked_list::*; /// use orx_selfref_col::NodeIndexError; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// list.push_back('a'); /// let b = list.push_back('b'); @@ -1394,7 +1542,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// assert_eq!(vec!['a', 'c', 'd'], list.iter().copied().collect::>()); ///``` #[allow(clippy::missing_panics_doc)] - pub fn remove(&mut self, node_index: NodeIndex<'a, Doubly, T>) -> Result { + pub fn remove(&mut self, node_index: NodeIndex<'a, Doubly, T>) -> Result { self.col.mutate_take(node_index, |x, idx| { x.get_node_ref_or_error(idx).map(|node| { if node.ref_eq(Self::get_existing_front(&x)) { @@ -1434,7 +1582,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// list.insert_at(1, 'x'); /// assert_eq!(&['a', 'x', 'b', 'c'], list.iter().copied().collect::>().as_slice()); ///``` - pub fn insert_at(&mut self, at: usize, value: T) -> NodeIndex<'a, Doubly, T> { + pub fn insert_at(&mut self, at: usize, value: T) -> NodeIndex<'a, Doubly, T> { assert!(at <= self.len(), "out of bounds"); match at { 0 => self.push_front(value), @@ -1478,7 +1626,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// list.push_back('a'); /// let b = list.push_back('b'); @@ -1494,9 +1642,9 @@ impl<'a, T: 'a> List<'a, Doubly, T> { ///``` pub fn insert_prev_to( &mut self, - node_index: NodeIndex<'a, Doubly, T>, + node_index: NodeIndex<'a, Doubly, T>, value: T, - ) -> Result, NodeIndexError> { + ) -> Result, T>, NodeIndexError> { self.col .mutate_take((node_index, value), |x, (idx, value)| { x.get_node_ref_or_error(idx).map(|node| { @@ -1531,7 +1679,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::new(); + /// let mut list = List::::new(); /// /// list.push_back('a'); /// let b = list.push_back('b'); @@ -1547,9 +1695,9 @@ impl<'a, T: 'a> List<'a, Doubly, T> { ///``` pub fn insert_next_to( &mut self, - node_index: NodeIndex<'a, Doubly, T>, + node_index: NodeIndex<'a, Doubly, T>, value: T, - ) -> Result, NodeIndexError> { + ) -> Result, T>, NodeIndexError> { self.col .mutate_take((node_index, value), |x, (idx, value)| { x.get_node_ref_or_error(idx).map(|node| { @@ -1577,7 +1725,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::from_iter([0, 1, 2, 3, 4]); + /// let mut list = List::::from_iter([0, 1, 2, 3, 4]); /// /// list.retain(&|x| *x % 2 == 0); /// @@ -1604,7 +1752,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// ```rust /// use orx_linked_list::*; /// - /// let mut list = DoublyLinkedList::from_iter([0, 1, 2, 3, 4]); + /// let mut list = List::::from_iter([0, 1, 2, 3, 4]); /// /// let mut odds = vec![]; /// let mut collect = |x| odds.push(x); @@ -1622,7 +1770,13 @@ impl<'a, T: 'a> List<'a, Doubly, T> { Predicate: Fn(&T) -> bool, Collect: FnMut(T), { - fn remove<'a, T>(mut_key: &DoublyMutKey<'_, 'a, T>, node: &'a Node<'a, Doubly, T>) -> T { + fn remove<'a, T, M>( + mut_key: &DoublyMutKey<'_, 'a, T, M>, + node: &'a Node<'a, Doubly, T>, + ) -> T + where + M: MemoryReclaimPolicy, + { if let Some(next) = node.next().get() { next.set_prev(mut_key, *node.prev().get()); } @@ -1678,7 +1832,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { // helpers /// Removes the `node` from the list, repairs the links and returns the removed value. - fn remove_node<'rf>(mut_key: &DoublyMutKey<'rf, 'a, T>, node: &'a Node<'a, Doubly, T>) -> T { + fn remove_node<'rf>( + mut_key: &DoublyMutKey<'rf, 'a, T, M>, + node: &'a Node<'a, Doubly, T>, + ) -> T { if let Some(next) = node.next().get() { next.set_prev(mut_key, *node.prev().get()); } @@ -1694,10 +1851,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// /// Returns the index of the new node created for the `new_value`. fn insert_node_prev_to<'rf>( - x: &DoublyMutKey<'rf, 'a, T>, - node: &'a Node<'a, Doubly, T>, + x: &DoublyMutKey<'rf, 'a, T, M>, + node: &'a Node<'a, Doubly, T>, new_value: T, - ) -> NodeIndex<'a, Doubly, T> { + ) -> NodeIndex<'a, Doubly, T> { let new_node = x.push_get_ref(new_value); if let Some(prev) = node.prev().get() { @@ -1715,10 +1872,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// /// Returns the index of the new node created for the `new_value`. fn insert_node_next_to<'rf>( - x: &DoublyMutKey<'rf, 'a, T>, - node: &'a Node<'a, Doubly, T>, + x: &DoublyMutKey<'rf, 'a, T, M>, + node: &'a Node<'a, Doubly, T>, new_value: T, - ) -> NodeIndex<'a, Doubly, T> { + ) -> NodeIndex<'a, Doubly, T> { let new_node = x.push_get_ref(new_value); if let Some(next) = node.next().get() { @@ -1737,7 +1894,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// # Panics /// /// Panics if `self.is_empty()`. - fn get_node_at<'rf>(mut_key: &DoublyMutKey<'rf, 'a, T>, at: usize) -> &'a Node<'a, Doubly, T> { + fn get_node_at<'rf>( + mut_key: &DoublyMutKey<'rf, 'a, T, M>, + at: usize, + ) -> &'a Node<'a, Doubly, T> { let mut current = unsafe { mut_key.ends().front().unwrap_unchecked() }; for _ in 0..at { current = unsafe { current.next().get().unwrap_unchecked() }; @@ -1751,9 +1911,9 @@ impl<'a, T: 'a> List<'a, Doubly, T> { /// /// Panics if `self.is_empty()`. fn get_node_at_from_back<'rf>( - mut_key: &DoublyMutKey<'rf, 'a, T>, + mut_key: &DoublyMutKey<'rf, 'a, T, M>, at_from_back: usize, - ) -> &'a Node<'a, Doubly, T> { + ) -> &'a Node<'a, Doubly, T> { let mut current = unsafe { mut_key.ends().back().unwrap_unchecked() }; for _ in 0..at_from_back { current = unsafe { current.prev().get().unwrap_unchecked() }; @@ -1762,7 +1922,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { } /// Pushes the `value` as the front node and returns the index to the created node. - fn push_front_node<'rf>(x: &MutKey<'rf, 'a, Doubly, T>, value: T) -> NodeIndex<'a, Doubly, T> { + fn push_front_node<'rf>( + x: &MutKey<'rf, 'a, Doubly, T>, + value: T, + ) -> NodeIndex<'a, Doubly, T> { match x.ends().front() { Some(prior_front) => { let new_front = x.push_get_ref(value); @@ -1776,7 +1939,10 @@ impl<'a, T: 'a> List<'a, Doubly, T> { } /// Pushes the `value` as the back node and returns the index to the created node. - fn push_back_node<'rf>(x: &MutKey<'rf, 'a, Doubly, T>, value: T) -> NodeIndex<'a, Doubly, T> { + fn push_back_node<'rf>( + x: &MutKey<'rf, 'a, Doubly, T>, + value: T, + ) -> NodeIndex<'a, Doubly, T> { match x.ends().back() { Some(prior_back) => { let new_back = x.push_get_ref(value); @@ -1790,7 +1956,7 @@ impl<'a, T: 'a> List<'a, Doubly, T> { } /// Pops the back node and returns its `value`; returns None if the list is empty. - fn pop_back_node<'rf>(x: &MutKey<'rf, 'a, Doubly, T>) -> Option { + fn pop_back_node<'rf>(x: &MutKey<'rf, 'a, Doubly, T>) -> Option { x.ends().back().map(|prior_back| { let new_back = *prior_back.prev().get(); let new_front = some_only_if(new_back.is_some(), x.ends().front()); @@ -1918,7 +2084,7 @@ pub(crate) mod tests { #[test] fn singly_back() { - let mut singly = SinglyLinkedList::new(); + let mut singly = SinglyLinkedList::<_, MemoryReclaimOnThreshold<2>>::new(); assert_empty_list(&singly); singly.push_front('x'); @@ -2186,7 +2352,7 @@ pub(crate) mod tests { #[test] fn remove() { - let mut wrong_collection = DoublyLinkedList::new(); + let mut wrong_collection = DoublyLinkedList::<_, MemoryReclaimOnThreshold<2>>::new(); let n = 1000; for i in 0..n { @@ -2222,7 +2388,7 @@ pub(crate) mod tests { #[test] fn insert_prev_to() { - let mut wrong_collection = DoublyLinkedList::new(); + let mut wrong_collection = DoublyLinkedList::<_, MemoryReclaimOnThreshold<2>>::new(); let n = 1000; for i in 0..n { @@ -2257,7 +2423,7 @@ pub(crate) mod tests { #[test] fn insert_next_to() { - let mut wrong_collection = DoublyLinkedList::new(); + let mut wrong_collection = DoublyLinkedList::<_, MemoryReclaimOnThreshold<2>>::new(); let n = 1000; for i in 0..n { @@ -2610,4 +2776,162 @@ pub(crate) mod tests { assert_eq!(back, singly.back()); } } + + #[test] + fn eee() { + use crate::*; + + fn float_eq(x: f32, y: f32) -> bool { + (x - y).abs() < f32::EPSILON + } + + // MemoryReclaimOnThreshold<2> -> memory will be reclaimed when utilization is below 75% + let mut list = List::::new(); + let a = list.push_back('a'); + list.push_back('b'); + list.push_back('c'); + list.push_back('d'); + list.push_back('e'); + + assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + + // no reorganization; 'a' is still valid + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + + _ = list.pop_back(); // leaves a hole + + assert!(float_eq(list.node_utilization(), 0.80)); // utilization = 4/5 = 80% + + // no reorganization; 'a' is still valid + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + + _ = list.pop_back(); // leaves the second hole; we have utilization = 3/5 = 60% + // this is below the threshold 75%, and triggers reclaim + // we claim the two unused nodes / holes + + assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 3/3 = 100% + + // nodes reorganized; 'a' is no more valid + assert_eq!( + list.get_or_error(a), + Err(NodeIndexError::ReorganizedCollection) + ); + assert_eq!(list.get(a), None); + + // re-obtain the index + let a = list.index_of(&'a').unwrap(); + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + } + + #[test] + fn fff() { + use crate::*; + + fn float_eq(x: f32, y: f32) -> bool { + (x - y).abs() < f32::EPSILON + } + + // MemoryReclaimNever -> memory will never be reclaimed automatically + let mut list = List::, _>::new(); + let a = list.push_back('a'); + list.push_back('b'); + list.push_back('c'); + list.push_back('d'); + list.push_back('e'); + + assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 5/5 = 100% + + // no reorganization; 'a' is still valid + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + + _ = list.pop_back(); // leaves a hole + _ = list.pop_back(); // leaves the second hole + _ = list.pop_back(); // leaves the third hole + + assert!(float_eq(list.node_utilization(), 0.40)); // utilization = 2/5 = 40% + + // still no reorganization; 'a' is and will always be valid unless we manually reclaim + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + + list.reclaim_closed_nodes(); + + // we can manually reclaim memory any time we want to maximize utilization + assert!(float_eq(list.node_utilization(), 1.00)); // utilization = 2/2 = 100% + + // we are still protected by list & index validation + // nodes reorganized; 'a' is no more valid, we cannot wrongly use the index + assert_eq!( + list.get_or_error(a), + Err(NodeIndexError::ReorganizedCollection) + ); + assert_eq!(list.get(a), None); + + // re-obtain the index + let a = list.index_of(&'a').unwrap(); + assert_eq!(list.get_or_error(a), Ok(&'a')); + assert_eq!(list.get(a), Some(&'a')); + } + + #[test] + fn zzz() { + fn eq<'a, I: Iterator + Clone>(iter: I, slice: &[char]) -> bool { + iter.clone().count() == slice.len() && iter.zip(slice.iter()).all(|(a, b)| a == b) + } + + let mut list = List::::from_iter(['a', 'b', 'c', 'd']); + + let x = list.index_of(&'x'); + assert!(x.is_none()); + + let maybe_b = list.index_of(&'b'); // O(n) + assert!(maybe_b.is_some()); + + let b = maybe_b.unwrap(); + + let data_b = list.get(b); // O(1) + assert_eq!(data_b, Some(&'b')); + + // O(1) to create the iterators from the index + assert!(eq(list.iter_forward_from(b).unwrap(), &['b', 'c', 'd'])); + assert!(eq(list.iter_backward_from(b).unwrap(), &['b', 'a'])); + + list.insert_prev_to(b, 'X').unwrap(); // O(1) + list.insert_next_to(b, 'Y').unwrap(); // O(1) + assert!(eq(list.iter(), &['a', 'X', 'b', 'Y', 'c', 'd'])); + + let removed = list.remove(b); // O(1) + assert_eq!(removed, Ok('b')); + assert!(eq(list.iter(), &['a', 'X', 'Y', 'c', 'd'])); + + // not possible to wrongly use the index + assert_eq!(list.get(b), None); + assert_eq!( + list.get_or_error(b).err(), + Some(NodeIndexError::RemovedNode) + ); + + // indices can also be stored on insertion + let mut list = List::::from_iter(['a', 'b', 'c', 'd']); + + let x = list.push_back('x'); // grab index of x in O(1) on insertion + + _ = list.push_back('e'); + _ = list.push_back('f'); + assert!(eq(list.iter(), &['a', 'b', 'c', 'd', 'x', 'e', 'f'])); + + let data_x = list.get(x); // O(1) + assert_eq!(data_x, Some(&'x')); + + list.insert_prev_to(x, 'w').unwrap(); // O(1) + list.insert_next_to(x, 'y').unwrap(); // O(1) + assert!(eq( + list.iter(), + &['a', 'b', 'c', 'd', 'w', 'x', 'y', 'e', 'f'] + )); + } } diff --git a/src/variants/doubly.rs b/src/variants/doubly.rs index f8353f2..bb3e4b6 100644 --- a/src/variants/doubly.rs +++ b/src/variants/doubly.rs @@ -1,17 +1,21 @@ use super::{ends::ListEnds, list_variant::ListVariant}; +use crate::list::DefaultMemoryPolicy; use orx_selfref_col::{ - MemoryReclaimOnThreshold, Node, NodeDataLazyClose, NodeRefSingle, NodeRefs, NodeRefsArray, - Variant, + MemoryReclaimPolicy, Node, NodeDataLazyClose, NodeRefSingle, NodeRefs, NodeRefsArray, Variant, }; +use std::marker::PhantomData; -pub type EndsDoubly<'a, T> = NodeRefsArray<'a, 2, Doubly, T>; +pub type EndsDoubly<'a, T, M> = NodeRefsArray<'a, 2, Doubly, T>; -impl<'a, T> ListEnds<'a, Doubly, T> for EndsDoubly<'a, T> { - fn front(&self) -> Option<&'a Node<'a, Doubly, T>> { +impl<'a, T, M> ListEnds<'a, Doubly, T> for EndsDoubly<'a, T, M> +where + M: 'a + MemoryReclaimPolicy, +{ + fn front(&self) -> Option<&'a Node<'a, Doubly, T>> { self.get()[0] } - fn back(&self) -> Option<&'a Node<'a, Doubly, T>> { + fn back(&self) -> Option<&'a Node<'a, Doubly, T>> { self.get()[1] } } @@ -22,11 +26,14 @@ impl<'a, T> ListEnds<'a, Doubly, T> for EndsDoubly<'a, T> { /// * It is possible to iterate from the `front` to the `back` of the list with `iter` method; /// and from the `back` to the `front` with `iter_from_back` method. #[derive(Clone, Copy, Debug)] -pub struct Doubly; +pub struct Doubly { + phantom: PhantomData, +} -impl<'a, T> Variant<'a, T> for Doubly +impl<'a, T, M> Variant<'a, T> for Doubly where T: 'a, + M: 'a + MemoryReclaimPolicy, { type Storage = NodeDataLazyClose; @@ -34,15 +41,20 @@ where type Next = NodeRefSingle<'a, Self, T>; - type Ends = EndsDoubly<'a, T>; + type Ends = EndsDoubly<'a, T, M>; - type MemoryReclaim = MemoryReclaimOnThreshold<2>; + type MemoryReclaim = M; } -impl<'a, T> ListVariant<'a, T> for Doubly +impl<'a, T, M> ListVariant<'a, T> for Doubly where T: 'a, + M: 'a + MemoryReclaimPolicy, { + type PrevNode = NodeRefSingle<'a, Self, T>; + + type NextNode = NodeRefSingle<'a, Self, T>; + #[cfg(test)] fn validate(list: &crate::list::List<'a, Self, T>) where diff --git a/src/variants/list_variant.rs b/src/variants/list_variant.rs index 6a2aa4f..63d7ea3 100644 --- a/src/variants/list_variant.rs +++ b/src/variants/list_variant.rs @@ -1,4 +1,4 @@ -use orx_selfref_col::{NodeDataLazyClose, NodeRefSingle, NodeRefsArray, Variant}; +use orx_selfref_col::{NodeDataLazyClose, NodeRefSingle, NodeRefs, NodeRefsArray, Variant}; pub trait ListVariant<'a, T>: Variant< @@ -12,6 +12,9 @@ where Self: 'a, T: 'a, { + type PrevNode: NodeRefs<'a, Self, T>; + type NextNode: NodeRefs<'a, Self, T>; + #[cfg(test)] fn validate(list: &crate::list::List<'a, Self, T>) where diff --git a/src/variants/singly.rs b/src/variants/singly.rs index 824028e..636ab61 100644 --- a/src/variants/singly.rs +++ b/src/variants/singly.rs @@ -1,17 +1,22 @@ use super::{ends::ListEnds, list_variant::ListVariant}; +use crate::list::DefaultMemoryPolicy; use orx_selfref_col::{ - MemoryReclaimOnThreshold, Node, NodeDataLazyClose, NodeRefNone, NodeRefSingle, NodeRefs, + MemoryReclaimPolicy, Node, NodeDataLazyClose, NodeRefNone, NodeRefSingle, NodeRefs, NodeRefsArray, Variant, }; +use std::marker::PhantomData; -pub type EndsSingly<'a, T> = NodeRefsArray<'a, 2, Singly, T>; +pub type EndsSingly<'a, T, M> = NodeRefsArray<'a, 2, Singly, T>; -impl<'a, T> ListEnds<'a, Singly, T> for EndsSingly<'a, T> { - fn front(&self) -> Option<&'a Node<'a, Singly, T>> { +impl<'a, T, M> ListEnds<'a, Singly, T> for EndsSingly<'a, T, M> +where + M: MemoryReclaimPolicy, +{ + fn front(&self) -> Option<&'a Node<'a, Singly, T>> { self.get()[0] } - fn back(&self) -> Option<&'a Node<'a, Singly, T>> { + fn back(&self) -> Option<&'a Node<'a, Singly, T>> { self.get()[1] } } @@ -21,11 +26,17 @@ impl<'a, T> ListEnds<'a, Singly, T> for EndsSingly<'a, T> { /// * The list keeps track of its `front`. /// * It is possible to iterate from the `front` to the back of the list. #[derive(Clone, Copy, Debug)] -pub struct Singly; +pub struct Singly +where + M: MemoryReclaimPolicy, +{ + phantom: PhantomData, +} -impl<'a, T> Variant<'a, T> for Singly +impl<'a, T, M> Variant<'a, T> for Singly where T: 'a, + M: 'a + MemoryReclaimPolicy, { type Storage = NodeDataLazyClose; @@ -33,15 +44,20 @@ where type Next = NodeRefSingle<'a, Self, T>; - type Ends = EndsSingly<'a, T>; + type Ends = EndsSingly<'a, T, M>; - type MemoryReclaim = MemoryReclaimOnThreshold<2>; + type MemoryReclaim = M; } -impl<'a, T> ListVariant<'a, T> for Singly +impl<'a, T, M> ListVariant<'a, T> for Singly where T: 'a, + M: 'a + MemoryReclaimPolicy, { + type PrevNode = NodeRefNone; + + type NextNode = NodeRefSingle<'a, Self, T>; + #[cfg(test)] fn validate(list: &crate::list::List<'a, Self, T>) where