Skip to content

Commit 36cf4b5

Browse files
committed
More tests, and some minor fixes and improvements
1 parent b7a3e72 commit 36cf4b5

7 files changed

Lines changed: 463 additions & 56 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "index_list"
3-
version = "0.3.2"
3+
version = "0.3.3"
44
description = "A doubly linked list implemented in safe Rust using vector indexes"
55
keywords = ["linked-list", "list", "index", "doubly-linked-list"]
66
categories = ["data-structures", "no-std"]

README.md

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
[![Rust](https://github.com/Fairglow/index-list/actions/workflows/rust.yml/badge.svg)](https://github.com/Fairglow/index-list/actions/workflows/rust.yml)
2-
31
# Index List
42

3+
[![Rust](https://github.com/Fairglow/index-list/actions/workflows/rust.yml/badge.svg)](https://github.com/Fairglow/index-list/actions/workflows/rust.yml)
4+
55
An index list is a hybrid between a vector and a linked-list, with some of the properties of each. Every element has an index in the vector and can be accessed directly there. This index is persistent as long as the element remains in the list and is not affected by other elements of the list. An index does not change if the element is moved in the list, nor when other elements are inserted or removed from the list.
66

77
The user is not meant to know the exact value of the index and should not create any Indexes themselves, but can safely copy an existing one. The index should only be used for the purpose of accessing the element data at that location or to traverse the list. This is why almost all methods on the Indexes are private.
@@ -15,13 +15,15 @@ Old indexes will be reused in FIFO fashion, before new indexes are added.
1515
The list elements are placed in a vector, which is why they can be accessed directly, where each element knows the index of the element before and after it, as well as the data contained at that index. This indirection makes it easy to implement the list safely in Rust, because the traditional next and previous pointers are replaced by their respective indexes, which are just numbers.
1616

1717
You can think of a list node like this:
18+
1819
```rust
1920
struct IndexElem<T> {
20-
next: Option<u32>,
21-
prev: Option<u32>,
22-
data: Option<T>,
21+
next: Option<u32>,
22+
prev: Option<u32>,
23+
data: Option<T>,
2324
}
2425
```
26+
2527
Where an element without data is free and if either `next` or `prev` is `None` then that is the end of the list in that direction.
2628

2729
## The element vector
@@ -34,7 +36,7 @@ To walk the list the user needs a starting index. One can be obtained from eithe
3436

3537
See the included [example code](examples/indexlist.rs) for how this works.
3638

37-
Note that any calls to the `trim_swap` method, may invalidate one or more index. It van be verified because any index greater than the `capacity` has been moved. To prevent this invalidation, you can hold a reference to the list as well as the index, but this will also block any and all modifications to the list while the reference is held.
39+
Note that any calls to the `trim_swap` method, may invalidate one or more index. It can be verified because any index greater than the `capacity` has been moved. To prevent this invalidation, you can hold a reference to the list as well as the index, but this will also block any and all modifications to the list while the reference is held.
3840

3941
## The list capacity
4042

@@ -75,8 +77,8 @@ The core of this crate is implemented in 100% safe Rust, with no `unsafe` code b
7577

7678
However, there are two things to be aware of:
7779

78-
1. The `trim_swap` method, while being 100% safe, is considered "unsafe" from a logical point of view because it can invalidate indexes. If you have cached an index somewhere, it may point to a different element after `trim_swap` is called. Use this method with care.
79-
2. The optional `iter_mut` feature uses a small `unsafe` block to create a mutable iterator. This is a common pattern for this kind of data structure, but it is important to be aware of it.
80+
1. The `trim_swap` method, while being 100% safe, is considered "unsafe" from a logical point of view because it can invalidate indexes. If you have cached an index somewhere, it may point to a different element after `trim_swap` is called. Use this method with care.
81+
2. The optional `iter_mut` feature uses a small `unsafe` block to create a mutable iterator. This is a common pattern for this kind of data structure, but it is important to be aware of it.
8082

8183
## Performance
8284

@@ -88,13 +90,13 @@ This crate comes with a comprehensive benchmark suite that compares `IndexList`
8890

8991
### Available Benchmarks
9092

91-
* **head:** Measures the performance of adding and removing elements from the front of the list.
92-
* **tail:** Measures the performance of adding and removing elements from the back of the list.
93-
* **body:** Measures the performance of inserting and removing elements in the middle of the list.
94-
* **random:** Measures the performance of inserting and removing elements at random positions.
95-
* **walk:** Measures the performance of iterating through the list using `next_index` and `prev_index`.
96-
* **iter:** Measures the performance of iterating through the list using the `iter()` method.
97-
* **iter_mut:** Measures the performance of mutable iteration using the `iter_mut()` method.
93+
- **head:** Measures the performance of adding and removing elements from the front of the list.
94+
- **tail:** Measures the performance of adding and removing elements from the back of the list.
95+
- **body:** Measures the performance of inserting and removing elements in the middle of the list.
96+
- **random:** Measures the performance of inserting and removing elements at random positions.
97+
- **walk:** Measures the performance of iterating through the list using `next_index` and `prev_index`.
98+
- **iter:** Measures the performance of iterating through the list using the `iter()` method.
99+
- **iter_mut:** Measures the performance of mutable iteration using the `iter_mut()` method.
98100

99101
> NOTE: These benchmarks may not reflect any real-life performance difference and you are urged to evaluate this in your own use-case rather than relying on the figures provided by the included benchmarks.
100102
@@ -134,34 +136,34 @@ This project is licensed under the [Mozilla Public License, v. 2.0](LICENSE).
134136

135137
## Reasons to use IndexList
136138

137-
* Data that is frequently inserted or removed from the body of the list (not the ends).
138-
* Use-case where walking the list is required.
139-
* Data that is reordered often, or sorted.
140-
* Need persistent indexes even when data is inserted or removed.
141-
* Want to maintain skip elements for taking larger steps through the list.
142-
* Need to cache certain elements for fast retrieval, without holding a reference to it.
139+
- Data that is frequently inserted or removed from the body of the list (not the ends).
140+
- Use-case where walking the list is required.
141+
- Data that is reordered often, or sorted.
142+
- Need persistent indexes even when data is inserted or removed.
143+
- Want to maintain skip elements for taking larger steps through the list.
144+
- Need to cache certain elements for fast retrieval, without holding a reference to it.
143145

144146
## Reasons to use other alternatives
145147

146-
* Data that is mainly inserted and removed at the ends of the list, then VecDeque is likely a better alternative.
147-
* Merges and splits of the lists are common; these are heavy `O(n)` operations in the IndexList design. The LinkedList is likely much better in this respect.
148-
* When handling lists longer than 4 billion entries, as this list is limited to 32-bit indexes.
149-
* When you need to shrink the list often, because `trim_swap` is expensive and has the side-effect of potentially invalidating indexes. For instance a LinkedList does not require trimming at all.
148+
- Data that is mainly inserted and removed at the ends of the list, then VecDeque is likely a better alternative.
149+
- Merges and splits of the lists are common; these are heavy `O(n)` operations in the IndexList design. The LinkedList is likely much better in this respect.
150+
- When handling lists longer than 4 billion entries, as this list is limited to 32-bit indexes.
151+
- When you need to shrink the list often, because `trim_swap` is expensive and has the side-effect of potentially invalidating indexes. For instance a LinkedList does not require trimming at all.
150152

151153
This is not an exhaustive list of alternatives, and I may have missed important choices, but these were the ones that I was aware of at the time of writing this.
152154

153-
* [`std::collections::LinkedList`](https://doc.rust-lang.org/std/collections/struct.LinkedList.html)
154-
* [`std::collections::VecDeque`](https://doc.rust-lang.org/std/collections/struct.VecDeque.html)
155-
* [`std::vec::Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html)
156-
* [`pie_core::PieList`](https://docs.rs/pie_core/0.2.3/pie_core/struct.PieList.html)
155+
- [`std::collections::LinkedList`](https://doc.rust-lang.org/std/collections/struct.LinkedList.html)
156+
- [`std::collections::VecDeque`](https://doc.rust-lang.org/std/collections/struct.VecDeque.html)
157+
- [`std::vec::Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html)
158+
- [`pie_core::PieList`](https://docs.rs/pie_core/0.2.3/pie_core/struct.PieList.html)
157159

158160
### Reasons to use PieList over IndexList
159161

160162
PieList[^1] shares most of the reasons for picking IndexList, but with these notable improvements:
161163

162-
* Multiple lists of the same kind.
163-
* O(1) split and merge for lists.
164-
* Small data size, where data is typically always accessed when traversing the list.
164+
- Multiple lists of the same kind.
165+
- O(1) split and merge for lists.
166+
- Small data size, where data is typically always accessed when traversing the list.
165167

166168
The PieList is optimized for the foundation of a fibonacci heap, with a priority queue use-case.
167169

src/lib.rs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ mod listnode;
2626
mod listends;
2727

2828
use core::{cmp::Ordering, default::Default, fmt};
29-
use core::iter::{Extend, FromIterator};
29+
use core::iter::{DoubleEndedIterator, Extend, FromIterator, FusedIterator};
3030
use alloc::{vec::Vec, string::String, format};
3131
use crate::{listnode::ListNode, listends::ListEnds};
3232
pub use crate::listindex::ListIndex as ListIndex;
@@ -37,7 +37,7 @@ pub use crate::listdrainiter::ListDrainIter as ListDrainIter;
3737
pub type Index = ListIndex; // for backwards compatibility with 0.2.7
3838

3939
/// Doubly-linked list implemented in safe Rust.
40-
#[derive(Debug)]
40+
#[derive(Debug, Clone)]
4141
pub struct IndexList<T> {
4242
elems: Vec<Option<T>>,
4343
nodes: Vec<ListNode>,
@@ -282,7 +282,7 @@ impl<T> IndexList<T> {
282282
/// are the same index.
283283
///
284284
/// This is similar to calling `let elem = self.remove(this);` followed by `self.insert_before(that, elem)`
285-
/// except that it doesn't invalildate or change the index `this`. That is, the index `this` is guaranteed
285+
/// except that it doesn't invalidate or change the index `this`. That is, the index `this` is guaranteed
286286
/// to still point to the same element `elem` after this operation completes.
287287
///
288288
/// Example:
@@ -309,7 +309,7 @@ impl<T> IndexList<T> {
309309
/// are the same index.
310310
///
311311
/// This is similar to calling `let elem = self.remove(this);` followed by `self.insert_after(that, elem)`
312-
/// except that it doesn't invalildate or change the index `this`. That is, the index `this` is guaranteed
312+
/// except that it doesn't invalidate or change the index `this`. That is, the index `this` is guaranteed
313313
/// to still point to the same element `elem` after this operation completes.
314314
///
315315
/// Example:
@@ -336,7 +336,7 @@ impl<T> IndexList<T> {
336336
/// Returns `true` if the operation was successful. This will fail if `this` is an invalid index.
337337
///
338338
/// This is similar to calling `let elem = self.remove(this);` followed by `self.insert_first(elem)`
339-
/// except that it doesn't invalildate or change the index `this`. That is, the index `this` is guaranteed
339+
/// except that it doesn't invalidate or change the index `this`. That is, the index `this` is guaranteed
340340
/// to still point to the same element `elem` after this operation completes.
341341
///
342342
/// Example:
@@ -362,7 +362,7 @@ impl<T> IndexList<T> {
362362
/// Returns `true` if the operation was successful. This will fail if `this` is an invalid index.
363363
///
364364
/// This is similar to calling `let elem = self.remove(this);` followed by `self.insert_last(elem)`
365-
/// except that it doesn't invalildate or change the index `this`. That is, the index `this` is guaranteed
365+
/// except that it doesn't invalidate or change the index `this`. That is, the index `this` is guaranteed
366366
/// to still point to the same element `elem` after this operation completes.
367367
///
368368
/// Example:
@@ -551,9 +551,7 @@ impl<T> IndexList<T> {
551551
/// ```
552552
#[inline]
553553
pub fn contains(&self, elem: T) -> bool
554-
where
555-
T: PartialEq,
556-
{
554+
where T: PartialEq {
557555
self.elems.contains(&Some(elem))
558556
}
559557
/// Returns the index of the element containg the data.
@@ -570,9 +568,7 @@ impl<T> IndexList<T> {
570568
/// ```
571569
#[inline]
572570
pub fn index_of(&self, elem: T) -> ListIndex
573-
where
574-
T: PartialEq,
575-
{
571+
where T: PartialEq {
576572
ListIndex::from(self.elems.iter().position(|e| {
577573
if let Some(data) = e {
578574
data == &elem
@@ -764,7 +760,7 @@ impl<T> IndexList<T> {
764760
/// # assert_eq!(format!("{:?}", vector), "[1, 2, 3]");
765761
/// ```
766762
pub fn to_vec(&self) -> Vec<&T> {
767-
self.iter().filter_map(Option::Some).collect()
763+
self.iter().collect()
768764
}
769765
/// Insert all the elements from the vector, which will be drained.
770766
///
@@ -1156,6 +1152,51 @@ impl<T> Extend<T> for IndexList<T> {
11561152
}
11571153
}
11581154

1155+
impl<T: PartialEq> PartialEq for IndexList<T> {
1156+
fn eq(&self, other: &Self) -> bool {
1157+
self.len() == other.len() && self.iter().eq(other.iter())
1158+
}
1159+
}
1160+
1161+
impl<T: Eq> Eq for IndexList<T> {}
1162+
1163+
/// An owning iterator over the elements of an `IndexList`.
1164+
pub struct IntoIter<T> {
1165+
list: IndexList<T>,
1166+
}
1167+
1168+
impl<T> Iterator for IntoIter<T> {
1169+
type Item = T;
1170+
#[inline]
1171+
fn next(&mut self) -> Option<Self::Item> {
1172+
self.list.remove_first()
1173+
}
1174+
#[inline]
1175+
fn size_hint(&self) -> (usize, Option<usize>) {
1176+
let len = self.list.len();
1177+
(len, Some(len))
1178+
}
1179+
}
1180+
1181+
impl<T> DoubleEndedIterator for IntoIter<T> {
1182+
#[inline]
1183+
fn next_back(&mut self) -> Option<Self::Item> {
1184+
self.list.remove_last()
1185+
}
1186+
}
1187+
1188+
impl<T> ExactSizeIterator for IntoIter<T> {}
1189+
impl<T> FusedIterator for IntoIter<T> {}
1190+
1191+
impl<T> IntoIterator for IndexList<T> {
1192+
type Item = T;
1193+
type IntoIter = IntoIter<T>;
1194+
1195+
fn into_iter(self) -> Self::IntoIter {
1196+
IntoIter { list: self }
1197+
}
1198+
}
1199+
11591200
#[cfg(test)]
11601201
mod tests {
11611202
use super::*;

src/listdrainiter.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ impl<T> Iterator for ListDrainIter<'_, T> {
2323
fn next(&mut self) -> Option<Self::Item> {
2424
self.0.remove_first()
2525
}
26+
#[inline]
27+
fn size_hint(&self) -> (usize, Option<usize>) {
28+
let len = self.0.len();
29+
(len, Some(len))
30+
}
2631
}
2732

2833
impl<T> DoubleEndedIterator for ListDrainIter<'_, T> {

src/listends.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* License, v. 2.0. If a copy of the MPL was not distributed with this
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
6-
//! The defenition of the ListEnds type
6+
//! The definition of the ListEnds type
77
//!
88
use core::{default::Default, fmt, mem};
99
use crate::listindex::ListIndex;

src/listnode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* License, v. 2.0. If a copy of the MPL was not distributed with this
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
6-
//! The difinition of the ListNode type
6+
//! The definition of the ListNode type
77
//!
88
use core::{default::Default, fmt, mem};
99
use crate::listindex::ListIndex;

0 commit comments

Comments
 (0)