about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2021-07-02 11:35:28 +0200
committerGitHub <noreply@github.com>2021-07-02 11:35:28 +0200
commitccdbda6688886bbb16246928f111bb5552a2ace6 (patch)
treeb6b23bf842faa5f85828c4d596a2345a06fdf678
parentb7654a3258b73002d99c5139a5b40c49245e9695 (diff)
parentc4ad273fe168cba92a298e35826b37f7177ce599 (diff)
downloadrust-ccdbda6688886bbb16246928f111bb5552a2ace6.tar.gz
rust-ccdbda6688886bbb16246928f111bb5552a2ace6.zip
Rollup merge of #86714 - iwahbe:add-linked-list-cursor-end-methods, r=Amanieu
Add linked list cursor end methods

I add several methods to `LinkedList::CursorMut` and `LinkedList::Cursor`. These methods allow you to access/manipulate the ends of a list via the cursor. This is especially helpful when scanning through a list and reordering. For example:

```rust
let mut c = ll.back_cursor_mut();
let mut moves = 10;
while c.current().map(|x| x > 5).unwrap_or(false) {
    let n = c.remove_current();
    c.push_front(n);
    if moves > 0 { break; } else { moves -= 1; }
}
```
I encountered this problem working on my bachelors thesis doing graph index manipulation.

While this problem can be avoided by splicing, it is awkward. I asked about the problem [here](https://internals.rust-lang.org/t/linked-list-cursurmut-missing-methods/14921/4) and it was suggested I write a PR.

All methods added consist of
```rust
Cursor::front(&self) -> Option<&T>;
Cursor::back(&self) -> Option<&T>;
CursorMut::front(&self) -> Option<&T>;
CursorMut::back(&self) -> Option<&T>;
CursorMut::front_mut(&mut self) -> Option<&mut T>;
CursorMut::back_mut(&mut self) -> Option<&mut T>;
CursorMut::push_front(&mut self, elt: T);
CursorMut::push_back(&mut self, elt: T);
CursorMut::pop_front(&mut self) -> Option<T>;
CursorMut::pop_back(&mut self) -> Option<T>;
```
#### Design decisions:
I tried to remain as consistent as possible with what was already present for linked lists.
The methods `front`, `front_mut`, `back` and `back_mut` are identical to their `LinkedList` equivalents.

I tried to make the `pop_front` and `pop_back` methods work the same way (vis a vis the "ghost" node) as `remove_current`. I thought this was the closest analog.

`push_front` and `push_back` do not change the "current" node, even if it is the "ghost" node. I thought it was most intuitive to say that if you add to the list, current will never change.

Any feedback would be welcome :smile:
-rw-r--r--library/alloc/src/collections/linked_list.rs143
-rw-r--r--library/alloc/src/collections/linked_list/tests.rs47
2 files changed, 190 insertions, 0 deletions
diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs
index 1a58ad51f78..ea216786ea2 100644
--- a/library/alloc/src/collections/linked_list.rs
+++ b/library/alloc/src/collections/linked_list.rs
@@ -1243,6 +1243,20 @@ impl<'a, T> Cursor<'a, T> {
             prev.map(|prev| &(*prev.as_ptr()).element)
         }
     }
+
+    /// Provides a reference to the front element of the cursor's parent list,
+    /// or None if the list is empty.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn front(&self) -> Option<&'a T> {
+        self.list.front()
+    }
+
+    /// Provides a reference to the back element of the cursor's parent list,
+    /// or None if the list is empty.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn back(&self) -> Option<&'a T> {
+        self.list.back()
+    }
 }
 
 impl<'a, T> CursorMut<'a, T> {
@@ -1506,6 +1520,135 @@ impl<'a, T> CursorMut<'a, T> {
         self.index = 0;
         unsafe { self.list.split_off_before_node(self.current, split_off_idx) }
     }
+
+    /// Appends an element to the front of the cursor's parent list. The node
+    /// that the cursor points to is unchanged, even if it is the "ghost" node.
+    ///
+    /// This operation should compute in O(1) time.
+    // `push_front` continues to point to "ghost" when it addes a node to mimic
+    // the behavior of `insert_before` on an empty list.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn push_front(&mut self, elt: T) {
+        // Safety: We know that `push_front` does not change the position in
+        // memory of other nodes. This ensures that `self.current` remains
+        // valid.
+        self.list.push_front(elt);
+        self.index += 1;
+    }
+
+    /// Appends an element to the back of the cursor's parent list. The node
+    /// that the cursor points to is unchanged, even if it is the "ghost" node.
+    ///
+    /// This operation should compute in O(1) time.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn push_back(&mut self, elt: T) {
+        // Safety: We know that `push_back` does not change the position in
+        // memory of other nodes. This ensures that `self.current` remains
+        // valid.
+        self.list.push_back(elt);
+        if self.current().is_none() {
+            // The index of "ghost" is the length of the list, so we just need
+            // to increment self.index to reflect the new length of the list.
+            self.index += 1;
+        }
+    }
+
+    /// Removes the first element from the cursor's parent list and returns it,
+    /// or None if the list is empty. The element the cursor points to remains
+    /// unchanged, unless it was pointing to the front element. In that case, it
+    /// points to the new front element.
+    ///
+    /// This operation should compute in O(1) time.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn pop_front(&mut self) -> Option<T> {
+        // We can't check if current is empty, we must check the list directly.
+        // It is possible for `self.current == None` and the list to be
+        // non-empty.
+        if self.list.is_empty() {
+            None
+        } else {
+            // We can't point to the node that we pop. Copying the behavior of
+            // `remove_current`, we move on the the next node in the sequence.
+            // If the list is of length 1 then we end pointing to the "ghost"
+            // node at index 0, which is expected.
+            if self.list.head == self.current {
+                self.move_next();
+            } else {
+                self.index -= 1;
+            }
+            self.list.pop_front()
+        }
+    }
+
+    /// Removes the last element from the cursor's parent list and returns it,
+    /// or None if the list is empty. The element the cursor points to remains
+    /// unchanged, unless it was pointing to the back element. In that case, it
+    /// points to the "ghost" element.
+    ///
+    /// This operation should compute in O(1) time.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn pop_back(&mut self) -> Option<T> {
+        if self.list.is_empty() {
+            None
+        } else {
+            if self.list.tail == self.current {
+                // The index now reflects the length of the list. It was the
+                // length of the list minus 1, but now the list is 1 smaller. No
+                // change is needed for `index`.
+                self.current = None;
+            } else if self.current.is_none() {
+                self.index = self.list.len - 1;
+            }
+            self.list.pop_back()
+        }
+    }
+
+    /// Provides a reference to the front element of the cursor's parent list,
+    /// or None if the list is empty.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn front(&self) -> Option<&T> {
+        self.list.front()
+    }
+
+    /// Provides a mutable reference to the front element of the cursor's
+    /// parent list, or None if the list is empty.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn front_mut(&mut self) -> Option<&mut T> {
+        self.list.front_mut()
+    }
+
+    /// Provides a reference to the back element of the cursor's parent list,
+    /// or None if the list is empty.
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn back(&self) -> Option<&T> {
+        self.list.back()
+    }
+
+    /// Provides a mutable reference to back element of the cursor's parent
+    /// list, or `None` if the list is empty.
+    ///
+    /// # Examples
+    /// Building and mutating a list with a cursor, then getting the back element:
+    /// ```
+    /// #![feature(linked_list_cursors)]
+    /// use std::collections::LinkedList;
+    /// let mut dl = LinkedList::new();
+    /// dl.push_front(3);
+    /// dl.push_front(2);
+    /// dl.push_front(1);
+    /// let mut cursor = dl.cursor_front_mut();
+    /// *cursor.current().unwrap() = 99;
+    /// *cursor.back_mut().unwrap() = 0;
+    /// let mut contents = dl.into_iter();
+    /// assert_eq!(contents.next(), Some(99));
+    /// assert_eq!(contents.next(), Some(2));
+    /// assert_eq!(contents.next(), Some(0));
+    /// assert_eq!(contents.next(), None);
+    /// ```
+    #[unstable(feature = "linked_list_cursors", issue = "58533")]
+    pub fn back_mut(&mut self) -> Option<&mut T> {
+        self.list.back_mut()
+    }
 }
 
 /// An iterator produced by calling `drain_filter` on LinkedList.
diff --git a/library/alloc/src/collections/linked_list/tests.rs b/library/alloc/src/collections/linked_list/tests.rs
index ad643a7bdf1..5a65ed7a962 100644
--- a/library/alloc/src/collections/linked_list/tests.rs
+++ b/library/alloc/src/collections/linked_list/tests.rs
@@ -428,3 +428,50 @@ fn test_cursor_mut_insert() {
     check_links(&m);
     assert_eq!(m.iter().cloned().collect::<Vec<_>>(), &[200, 201, 202, 203, 1, 100, 101]);
 }
+
+#[test]
+fn test_cursor_push_front_back() {
+    let mut ll: LinkedList<u32> = LinkedList::new();
+    ll.extend(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+    let mut c = ll.cursor_front_mut();
+    assert_eq!(c.current(), Some(&mut 1));
+    assert_eq!(c.index(), Some(0));
+    c.push_front(0);
+    assert_eq!(c.current(), Some(&mut 1));
+    assert_eq!(c.peek_prev(), Some(&mut 0));
+    assert_eq!(c.index(), Some(1));
+    c.push_back(11);
+    drop(c);
+    let p = ll.cursor_back().front().unwrap();
+    assert_eq!(p, &0);
+    assert_eq!(ll, (0..12).collect());
+    check_links(&ll);
+}
+
+#[test]
+fn test_cursor_pop_front_back() {
+    let mut ll: LinkedList<u32> = LinkedList::new();
+    ll.extend(&[1, 2, 3, 4, 5, 6]);
+    let mut c = ll.cursor_back_mut();
+    assert_eq!(c.pop_front(), Some(1));
+    c.move_prev();
+    c.move_prev();
+    c.move_prev();
+    assert_eq!(c.pop_back(), Some(6));
+    let c = c.as_cursor();
+    assert_eq!(c.front(), Some(&2));
+    assert_eq!(c.back(), Some(&5));
+    assert_eq!(c.index(), Some(1));
+    drop(c);
+    assert_eq!(ll, (2..6).collect());
+    check_links(&ll);
+    let mut c = ll.cursor_back_mut();
+    assert_eq!(c.current(), Some(&mut 5));
+    assert_eq!(c.index, 3);
+    assert_eq!(c.pop_back(), Some(5));
+    assert_eq!(c.current(), None);
+    assert_eq!(c.index, 3);
+    assert_eq!(c.pop_back(), Some(4));
+    assert_eq!(c.current(), None);
+    assert_eq!(c.index, 2);
+}