orx-linked-list

Implements a doubly-linked list.

As opposed to the note of std::collections::LinkedList, orx_linked_list::LinkedList provides the main theoretical benefits of a linked list; i.e.,

while aiming to avoid the practical drawbacks related with allocations and CPU cache due to the following:

Usage

Basic usage of the linked list is demonstrated below.

```rust use orxlinkedlist::prelude::*;

let mut list = LinkedList::withdoublinggrowth(4);

list.pushback('y'); list.pushfront('x'); list.pushback('z'); asserteq!(vec!['x', 'y', 'z'], list.collect_vec());

asserteq!(list.popback(), Some('z')); asserteq!(list.popfront(), Some('x')); asserteq!(vec!['y'], list.collectvec());

list.pushfront('x'); list.pushback('z'); asserteq!(vec!['x', 'y', 'z'], list.collectvec());

list.insertat(1, '?'); asserteq!(vec!['x', '?', 'y', 'z'], list.collect_vec());

asserteq!(Some(&'?'), list.getat(1)); *list.getmutat(1).unwrap() = '!';

asserteq!('!', list.removeat(1)); asserteq!(vec!['x', 'y', 'z'], list.collectvec()); ```

Memory

LinkedList provides two ways to configure the memory strategy:

Underlying Storage Variants

The complete signature of a LinkedList holding elements of type T is as follows:

rust ignore LinkedList<'a, T, P> where P: PinnedVec<LinkedListNode<'a, T>>

The choice of the underlying PinnedVec defines the dynamic allocations. See FixedVec or SplitVec for possible strategies.

The following type aliases are defined for convenience to simplify the type signatures:

```rust ignore pub type LinkedListFixed<'a, T> = LinkedList<'a, T, FixedVec>>;

pub type LinkedListLinear<'a, T> = LinkedList<'a, T, SplitVec, Linear>>;

pub type LinkedListDoubling<'a, T> = LinkedList<'a, T, SplitVec, Doubling>>;

pub type LinkedListExponential<'a, T> = LinkedList<'a, T, SplitVec, Exponential>>; ```

These variants can be constructed with corresponding associated functions:

rust ignore let list: LinkedListFixed<char> = LinkedList::with_fixed_capacity(100); let list: LinkedListLinear<char> = LinkedList::with_linear_growth(32); let list: LinkedListDoubling<char> = LinkedList::with_doubling_growth(4); let list: LinkedListExponential<char> = LinkedList::with_exponential_growth(4, 1.5);

Time Complexity vs Memory Utilization

LinkedList holds all elements close to each other in a PinnedVec aiming for better cache locality while using thin references rather than wide pointers and to reduce heap allocations. In order to achieve O(1) time complexity while avoiding smart pointers, remove and pop operations are designed to be semi-lazy.

In the lazy case; i.e., when the strategy is set to MemoryReclaimStrategy::Lazy, every pop_back, pop_front or remove_at operation leaves a gap in the underlying vector. Status of utilization of the underlying vector can be queried using the memory_status method and the gaps can completely be reclaimed by manually calling the memory_reclaim method which has a time complexity of O(n) where n is the length of the underlying vector.

Being able to be lazy and to reclaim the gaps, it is possible to define and use different automated strategies which would fit better in different situations:

Memory utilization stategy is defined by a field which can be modified any time.

rust ignore let list = list .with_memory_utilization(MemoryUtilization::Eager) .with_memory_utilization(MemoryUtilization::Lazy) .with_memory_utilization(MemoryUtilization::WithThreshold(0.5));