From 7249de2bd4974917a703c117184cab9467f97f23 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Thu, 12 Aug 2021 19:49:25 +0200 Subject: [PATCH] packed linked list --- Cargo.toml | 2 +- benches/packed_linked_list.rs | 63 ++++++++++ src/packed_linked_list/mod.rs | 214 ++++++++++++++++++++++++++++++++- src/packed_linked_list/test.rs | 65 +++++++++- 4 files changed, 336 insertions(+), 8 deletions(-) create mode 100644 benches/packed_linked_list.rs diff --git a/Cargo.toml b/Cargo.toml index b125e55..5ba7365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,5 @@ edition = "2018" criterion = "0.3.5" [[bench]] -name = "linked_list" +name = "packed_linked_list" harness = false \ No newline at end of file diff --git a/benches/packed_linked_list.rs b/benches/packed_linked_list.rs new file mode 100644 index 0000000..be89b23 --- /dev/null +++ b/benches/packed_linked_list.rs @@ -0,0 +1,63 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use datastructures::linked_list::LinkedList; +use datastructures::packed_linked_list::PackedLinkedList; + +fn create_random_list(size: usize) -> LinkedList { + let mut number = 837582573; + let mut list = LinkedList::new(); + for _ in 0..size { + // just random stuff I cam up with, does not need to be actually random + number = (number ^ (number << 5)) >> 3; + list.push_back(number); + } + list +} + +fn create_random_packed_list_16(size: usize) -> PackedLinkedList { + let mut number = 837582573; + let mut list = PackedLinkedList::new(); + for _ in 0..size { + // just random stuff I cam up with, does not need to be actually random + number = (number ^ (number << 5)) >> 3; + list.push_back(number); + } + list +} + +fn create_random_packed_list_128(size: usize) -> PackedLinkedList { + let mut number = 837582573; + let mut list = PackedLinkedList::new(); + for _ in 0..size { + // just random stuff I cam up with, does not need to be actually random + number = (number ^ (number << 5)) >> 3; + list.push_back(number); + } + list +} + +fn push_back(c: &mut Criterion) { + let mut group = c.benchmark_group("push_back"); + for i in [100, 1_0000_00].iter() { + group.bench_with_input(BenchmarkId::new("create_random_list", i), i, |b, i| { + b.iter(|| create_random_list(*i)) + }); + group.bench_with_input( + BenchmarkId::new("create_random_packed_list_16", i), + i, + |b, i| b.iter(|| create_random_packed_list_16(*i)), + ); + group.bench_with_input( + BenchmarkId::new("create_random_packed_list_128", i), + i, + |b, i| b.iter(|| create_random_packed_list_128(*i)), + ); + } + group.finish(); +} + +criterion_group!( + name = benches; + config = Criterion::default(); + targets = push_back +); +criterion_main!(benches); diff --git a/src/packed_linked_list/mod.rs b/src/packed_linked_list/mod.rs index 405c189..e18d98b 100644 --- a/src/packed_linked_list/mod.rs +++ b/src/packed_linked_list/mod.rs @@ -1,14 +1,15 @@ #[cfg(test)] mod test; +use std::iter::FromIterator; use std::marker::PhantomData; +use std::mem; use std::mem::MaybeUninit; use std::ptr::NonNull; fn allocate_nonnull(element: T) -> NonNull { - let boxed = Box::new(element); // SAFETY: box is always non-null - unsafe { NonNull::new_unchecked(Box::leak(boxed)) } + unsafe { NonNull::new_unchecked(Box::leak(Box::new(element))) } } /// @@ -27,6 +28,16 @@ pub struct PackedLinkedList { _maker: PhantomData, } +impl Drop for PackedLinkedList { + fn drop(&mut self) { + let mut item = self.first; + while let Some(node) = item { + let boxed = unsafe { Box::from_raw(node.as_ptr()) }; + item = boxed.next; + } + } +} + impl PackedLinkedList { pub fn new() -> Self { Self { @@ -36,6 +47,7 @@ impl PackedLinkedList { } } + /// Pushes a new value to the front of the list pub fn push_front(&mut self, element: T) { // SAFETY: All pointers should always point to valid memory, unsafe { @@ -53,19 +65,143 @@ impl PackedLinkedList { } } + /// Pushes a new value to the back of the list + pub fn push_back(&mut self, element: T) { + // SAFETY: All pointers should always point to valid memory, + unsafe { + match self.last { + None => { + self.insert_node_end(); + self.last.unwrap().as_mut().push_back(element) + } + Some(node) if node.as_ref().is_full() => { + self.insert_node_end(); + self.last.unwrap().as_mut().push_front(element) + } + Some(mut node) => node.as_mut().push_back(element), + } + } + } + + /// Pops the front element and returns it + pub fn pop_front(&mut self) -> Option { + let first = &mut self.first?; + unsafe { + let node = first.as_mut(); + debug_assert_ne!(node.size, 0); + + let item = mem::replace(&mut node.values[0], MaybeUninit::uninit()).assume_init(); + + if node.size == 1 { + // the last item, deallocate it + let mut boxed = Box::from_raw(first.as_ptr()); + boxed.next.as_mut().map(|next| next.as_mut().prev = None); + self.first = boxed.next; + if let None = self.first { + // if this node was the last one, also remove it from the tail pointer + self.last = None; + } + } else { + // more items, move them down + std::ptr::copy( + &node.values[1] as *const _, + &mut node.values[0] as *mut _, + node.size, + ); + node.size -= 1; + } + + Some(item) + } + } + + /// Pops the back value and returns it + pub fn pop_back(&mut self) -> Option { + let last = &mut self.last?; + unsafe { + let node = last.as_mut(); + debug_assert_ne!(node.size, 0); + + let item = + mem::replace(&mut node.values[node.size - 1], MaybeUninit::uninit()).assume_init(); + + if node.size == 1 { + // the last item, deallocate it + let mut boxed = Box::from_raw(last.as_ptr()); + boxed + .prev + .as_mut() + .map(|previous| previous.as_mut().next = None); + self.last = boxed.prev; + if let None = self.last { + // if this node was the last one, also remove it from the tail pointer + self.first = None; + } + } else { + // more items + node.size -= 1; + } + + Some(item) + } + } + pub fn iter(&self) -> Iter { Iter::new(self) } + pub fn into_iter(self) -> IntoIter { + IntoIter::new(self) + } + fn insert_node_start(&mut self) { let node = Some(allocate_nonnull(Node::new(None, self.first))); self.first .as_mut() .map(|first| unsafe { first.as_mut().prev = node }); self.first = node; + if let None = self.last { + self.last = node; + } + } + + fn insert_node_end(&mut self) { + let node = Some(allocate_nonnull(Node::new(self.last, None))); + self.last + .as_mut() + .map(|last| unsafe { last.as_mut().next = node }); + self.last = node; + if let None = self.first { + self.first = node; + } } } +impl FromIterator for PackedLinkedList { + fn from_iter>(iter: I) -> Self { + let mut list = PackedLinkedList::new(); + let mut iter = iter.into_iter(); + while let Some(item) = iter.next() { + list.push_back(item); + } + list + } +} + +impl Extend for PackedLinkedList { + fn extend>(&mut self, iter: I) { + let mut iter = iter.into_iter(); + while let Some(item) = iter.next() { + self.push_back(item); + } + } +} + +/// A single node in the packed linked list +/// +/// The node can have 1 to `COUNT` items. +/// A node is never guaranteed to be full, even if it has a next node +/// A node is always guaranteed to be non-empty #[derive(Debug)] struct Node { prev: Option>>, @@ -86,6 +222,7 @@ impl Node { } } + /// Checks whether the node is full fn is_full(&self) -> bool { self.size == COUNT } @@ -105,18 +242,20 @@ impl Node { unsafe fn push_front(&mut self, element: T) { debug_assert!(self.size < COUNT); // copy all values up - unsafe { + if COUNT > 1 { std::ptr::copy( &self.values[0] as *const _, &mut self.values[1] as *mut _, self.size, - ) + ); } + self.values[0] = MaybeUninit::new(element); self.size += 1; } } +#[derive(Debug)] pub struct Iter<'a, T, const COUNT: usize> { node: Option<&'a Node>, index: usize, @@ -156,3 +295,70 @@ impl<'a, T, const COUNT: usize> Iterator for Iter<'a, T, COUNT> { } } } + +#[derive(Debug)] +pub struct IntoIter { + node: Option>>, + index: usize, +} + +impl Drop for IntoIter { + fn drop(&mut self) { + while let Some(_) = self.next() {} + } +} + +impl IntoIter { + fn new(list: PackedLinkedList) -> Self { + let iter = Self { + node: list.first.map(|nn| unsafe { Box::from_raw(nn.as_ptr()) }), + index: 0, + }; + // do not drop the list + mem::forget(list); + iter + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + // take the node. the node has to either be returned or replaced by a new one. the None left + // behind here is *not* a valid state + let mut node = self.node.take()?; + + // SAFETY: see more detailed comments + unsafe { + if node.size > self.index { + // take more items from the node + // take out the item and replace it with uninitialized memory + // the index pointer is increased, so no one will access this again + let item = + mem::replace(&mut node.values[self.index], MaybeUninit::uninit()).assume_init(); + self.index += 1; + // re-insert the node + self.node = Some(node); + Some(item) + } else { + // go to the next node + // if next is empty, return None and stop the iteration + // take ownership over the node. the last node will be dropped here + let mut next_node = Box::from_raw(node.next?.as_ptr()); + next_node.prev = None; + self.index = 1; + // a node should never be empty + debug_assert_ne!(next_node.size, 0); + self.node = Some(next_node); + // see comment above + Some( + mem::replace( + &mut self.node.as_mut().unwrap().values[0], + MaybeUninit::uninit(), + ) + .assume_init(), + ) + } + } + } +} diff --git a/src/packed_linked_list/test.rs b/src/packed_linked_list/test.rs index 2a4982a..5bd1d06 100644 --- a/src/packed_linked_list/test.rs +++ b/src/packed_linked_list/test.rs @@ -10,6 +10,32 @@ fn push_front_single_node() { let mut list = PackedLinkedList::<_, 16>::new(); list.push_front("hallo"); } +#[test] +fn push_front_multiple_nodes_count_1() { + let mut list = PackedLinkedList::<_, 1>::new(); + list.push_front("3"); + list.push_front("2"); + list.push_front("1"); +} + +#[test] +fn push_front_multiple_nodes_count_2() { + let mut list = PackedLinkedList::<_, 2>::new(); + list.push_front("3"); + list.push_front("2"); + list.push_front("1"); +} + +#[test] +fn pop_front() { + let mut list = create_sized_list::<_, 2>(&[1, 2, 3, 4]); + assert_eq!(list.pop_front(), Some(1)); + assert_eq!(list.pop_front(), Some(2)); + assert_eq!(list.pop_front(), Some(3)); + assert_eq!(list.pop_front(), Some(4)); + assert_eq!(list.pop_front(), None); + assert_eq!(list.pop_front(), None); +} #[test] fn iter_single_node() { @@ -22,7 +48,40 @@ fn iter_single_node() { assert_eq!(iter.next(), None); } -fn create_list(iter: &[T]) -> PackedLinkedList { - //iter.into_iter().cloned().collect() - todo!() +#[test] +fn into_iter() { + let mut iter = create_list(&[1, 2, 3]).into_iter(); + assert_eq!(iter.next(), Some(1)); + assert_eq!(iter.next(), Some(2)); + assert_eq!(iter.next(), Some(3)); + assert_eq!(iter.next(), None); +} + +// ignore this test for now +#[test] +#[cfg(all(test, not(test)))] +fn iter_mut() { + let mut list = create_list(&[1, 2, 3, 4]); + let mut iter_mut = list.iter_mut(); + *iter_mut.next().unwrap() = 10; + assert!([10, 2, 3, 4].iter().zip(list.iter()).all(|(a, b)| a == b)); +} + +#[test] +fn from_iter() { + let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + let list = vec + .clone() + .into_iter() + .collect::>(); + let list_iter = list.iter(); + assert!(list_iter.zip(vec.iter()).all(|(a, b)| a == b)); +} + +fn create_list(iter: &[T]) -> PackedLinkedList { + iter.into_iter().cloned().collect() +} + +fn create_sized_list(iter: &[T]) -> PackedLinkedList { + iter.into_iter().cloned().collect() }