Implementing Cursors
好了,我们现在讨论 CursorMut。就像我最初的设计一样,它有一个包含 None 的 "幽灵 "元素,用来指示列表的开始/结束,你可以 "跨过它",绕到列表的另一边。要实现它,我们需要
- 指向当前节点的指针
- 指向列表的指针
- 当前索引
等等,当我们指向 "幽灵 "时,索引是多少?
好吧,游标 (cursors)上的索引返回一个 Option<usize>
,这很合理。Std 的实现做了一堆垃圾来避免将其存储为一个 Option,但是...... 我们是一个链接列表,这很好。此外,std 还有 cursor_front/cursor_back 功能,它可以在前/后元素上启动光标,感觉很直观,但当列表为空时,又要做一些奇怪的事情。
如果你愿意,也可以实现这些东西,但我打算减少所有重复的垃圾和角落情况,只做一个从 ghost 处开始的 cursor_mut 方法,人们可以使用 move_next/move_prev 来获取他们想要的元素(如果你真的愿意,也可以将其封装为 cursor_front)。
让我们开始吧:
非常简单直接,上面的需求列表每一项都有一个字段!
#![allow(unused)] fn main() { pub struct CursorMut<'a, T> { cur: Link<T>, list: &'a mut LinkedList<T>, index: Option<usize>, } }
现在是cursor_mut
方法:
#![allow(unused)] fn main() { impl<T> LinkedList<T> { pub fn cursor_mut(&mut self) -> CursorMut<T> { CursorMut { list: self, cur: None, index: None, } } } }
既然我们从幽灵节点开始,我们所以开始节点都是 None
,简单明了!下一个是 move_next
:
#![allow(unused)] fn main() { impl<'a, T> CursorMut<'a, T> { pub fn index(&self) -> Option<usize> { self.index } pub fn move_next(&mut self) { if let Some(cur) = self.cur { unsafe { // We're on a real element, go to its next (back) self.cur = (*cur.as_ptr()).back; if self.cur.is_some() { *self.index.as_mut().unwrap() += 1; } else { // We just walked to the ghost, no more index self.index = None; } } } else if !self.list.is_empty() { // We're at the ghost, and there is a real front, so move to it! self.cur = self.list.front; self.index = Some(0) } else { // We're at the ghost, but that's the only element... do nothing. } } } }
所以这有4种有趣的情况:
- 正常情况
- 正常情况,但我们移动到了幽灵节点
- 幽灵节点开始,向列表头部节点移动
- 幽灵节点开始,列表是空的,所以什么都不做
move_prev
的逻辑完全相同,但前后颠倒,索引变化也颠倒:
#![allow(unused)] fn main() { pub fn move_prev(&mut self) { if let Some(cur) = self.cur { unsafe { // We're on a real element, go to its previous (front) self.cur = (*cur.as_ptr()).front; if self.cur.is_some() { *self.index.as_mut().unwrap() -= 1; } else { // We just walked to the ghost, no more index self.index = None; } } } else if !self.list.is_empty() { // We're at the ghost, and there is a real back, so move to it! self.cur = self.list.back; self.index = Some(self.list.len - 1) } else { // We're at the ghost, but that's the only element... do nothing. } } }
接下来,让我们添加一些方法来查看游标周围的元素:current
、peek_next
和 peek_prev
。 一个非常重要的注意事项:这些方法必须通过 &mut self
借用我们的游标,并且结果必须与借用绑定。我们不能让用户获得可变引用的多个副本,也不能让他们在持有该引用的情况下使用我们的 insert/remove/split/splice API!
值得庆幸的是,这是 rust 在使用生命周期省略规则时的默认设置,因此我们将默认做正确的事情!
#![allow(unused)] fn main() { pub fn current(&mut self) -> Option<&mut T> { unsafe { self.cur.map(|node| &mut (*node.as_ptr()).elem) } } pub fn peek_next(&mut self) -> Option<&mut T> { unsafe { self.cur .and_then(|node| (*node.as_ptr()).back) .map(|node| &mut (*node.as_ptr()).elem) } } pub fn peek_prev(&mut self) -> Option<&mut T> { unsafe { self.cur .and_then(|node| (*node.as_ptr()).front) .map(|node| &mut (*node.as_ptr()).elem) } } }
Split
首先是 split_before 和 split_after,它们会将当前元素之前/之后的所有内容以 LinkedList 的形式返回(在幽灵元素处停止,在这种情况下,我们只返回整个 List,光标现在指向一个空 list):
这个逻辑其实并不复杂,所以我们得一步一步来。
我发现 split_before 有四种潜在的情况:
- 正常情况
- 正常情况,但 prev 是幽灵节点
- 幽灵节点情况,我们返回整个列表,然后变成空列表
- 幽灵节点情况,但列表是空的,所以什么也不做,返回空列表
让我们先从极端情况开始。我认为第三种情况
#![allow(unused)] fn main() { mem::replace(self.list, LinkedList::new()) }
对不对?我们是空的了,并返回了整个列表,而我们的字段都应该是 "None",所以没什么可更新的。不错。哦,嘿嘿,这在第四种情况下也对!
现在是普通情况......,我需要画下图。最常见的情况是这样的
list.front -> A <-> B <-> C <-> D <- list.back
^
cur
我们想变成这样:
list.front -> C <-> D <- list.back
^
cur
return.front -> A <-> B <- return.back
因此,我们需要打破当前数据和前一个数据之间的联系,而且......天哪,需要改变的东西太多了。好吧,我只需要把它分成几个步骤,这样我就能说服自己这是有意义的。虽然有点啰嗦,但我至少能说得通:
#![allow(unused)] fn main() { pub fn split_before(&mut self) -> LinkedList<T> { if let Some(cur) = self.cur { // We are pointing at a real element, so the list is non-empty. unsafe { // Current state let old_len = self.list.len; let old_idx = self.index.unwrap(); let prev = (*cur.as_ptr()).front; // What self will become let new_len = old_len - old_idx; let new_front = self.cur; let new_idx = Some(0); // What the output will become let output_len = old_len - new_len; let output_front = self.list.front; let output_back = prev; // Break the links between cur and prev if let Some(prev) = prev { (*cur.as_ptr()).front = None; (*prev.as_ptr()).back = None; } // Produce the result: self.list.len = new_len; self.list.front = new_front; self.index = new_idx; LinkedList { front: output_front, back: output_back, len: output_len, _boo: PhantomData, } } } else { // We're at the ghost, just replace our list with an empty one. // No other state needs to be changed. std::mem::replace(self.list, LinkedList::new()) } } }
你可能注意到,我们没有处理 prev 是幽灵节点的情况。但据我所知,其他一切都只是顺便做了正确的事。等我们写测试的时候就知道了!(复制粘贴完成 split_after)。
Splice
还有一个老大难,那就是 splice_before 和 splice_after,我估计这是最容易出错的一个。这两个函数接收一个 LinkedList,并将其内容嫁接到我们的列表中。我们的列表可能是空的,他们的列表也可能是空的,我们还有幽灵节点要处理......叹口气,让我们一步一步来吧,从 splice_before 开始。
- 如果他们的列表是空的,我们就什么都不用做。
- 如果我们的列表是空的,那么我们的列表就变成了他们的列表。
- 如果我们指向的是幽灵节点,则追加到后面(更改 list.back)
- 如果我们指向的是第一个元素(0),则追加到前面(更改 list.front)
- 一般情况下,我们会进行大量的指针操作
一般情况:
input.front -> 1 <-> 2 <- input.back
list.front -> A <-> B <-> C <- list.back
^
cur
变成这样:
list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back
好的,让我们来写一下:
#![allow(unused)] fn main() { pub fn splice_before(&mut self, mut input: LinkedList<T>) { unsafe { if input.is_empty() { // Input is empty, do nothing. } else if let Some(cur) = self.cur { if let Some(0) = self.index { // We're appending to the front, see append to back (*cur.as_ptr()).front = input.back.take(); (*input.back.unwrap().as_ptr()).back = Some(cur); self.list.front = input.front.take(); // Index moves forward by input length *self.index.as_mut().unwrap() += input.len; self.list.len += input.len; input.len = 0; } else { // General Case, no boundaries, just internal fixups let prev = (*cur.as_ptr()).front.unwrap(); let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); (*prev.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(prev); (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); // Index moves forward by input length *self.index.as_mut().unwrap() += input.len; self.list.len += input.len; input.len = 0; } } else if let Some(back) = self.list.back { // We're on the ghost but non-empty, append to the back // We can either `take` the input's pointers or `mem::forget` // it. Using take is more responsible in case we do custom // allocators or something that also needs to be cleaned up! (*back.as_ptr()).back = input.front.take(); (*input.front.unwrap().as_ptr()).front = Some(back); self.list.back = input.back.take(); self.list.len += input.len; // Not necessary but Polite To Do input.len = 0; } else { // We're empty, become the input, remain on the ghost *self.list = input; } } } }
好吧,这个程序真的很可怕,现在真的感觉到 Option
#![allow(unused)] fn main() { self.list.len += input.len; input.len = 0; }
好了,现在在分支 "we're empty" 中有以下错误。所以我们应该使用 swap
:
Use of moved value:
input
#![allow(unused)] fn main() { // We're empty, become the input, remain on the ghost std::mem::swap(self.list, &mut input); }
在我反向思考下面这种情况时,我发现了这个 unwrap
有问题(因为 cur 的 front 在前面已经被设置为其它值了):
#![allow(unused)] fn main() { if let Some(0) = self.index { } else { let prev = (*cur.as_ptr()).front.unwrap(); } }
这行也是重复的,可以提升:
#![allow(unused)] fn main() { *self.index.as_mut().unwrap() += input.len; }
好了,把上面的问题修改后得到这些:
#![allow(unused)] fn main() { if input.is_empty() { // Input is empty, do nothing. } else if let Some(cur) = self.cur { // Both lists are non-empty if let Some(prev) = (*cur.as_ptr()).front { // General Case, no boundaries, just internal fixups let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); (*prev.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(prev); (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); } else { // We're appending to the front, see append to back below (*cur.as_ptr()).front = input.back.take(); (*input.back.unwrap().as_ptr()).back = Some(cur); self.list.front = input.front.take(); } // Index moves forward by input length *self.index.as_mut().unwrap() += input.len; } else if let Some(back) = self.list.back { // We're on the ghost but non-empty, append to the back // We can either `take` the input's pointers or `mem::forget` // it. Using take is more responsible in case we do custom // allocators or something that also needs to be cleaned up! (*back.as_ptr()).back = input.front.take(); (*input.front.unwrap().as_ptr()).front = Some(back); self.list.back = input.back.take(); } else { // We're empty, become the input, remain on the ghost std::mem::swap(self.list, &mut input); } self.list.len += input.len; // Not necessary but Polite To Do input.len = 0; // Input dropped here }
还是不对,下面的代码存在bug:
#![allow(unused)] fn main() { (*back.as_ptr()).back = input.front.take(); (*input.front.unwrap().as_ptr()).front = Some(back); }
我们使用 take
拿走了 input.front 的值,然后在下一行将其 unwrap
!boom,panic!
#![allow(unused)] fn main() { // We can either `take` the input's pointers or `mem::forget` // it. Using `take` is more responsible in case we ever do custom // allocators or something that also needs to be cleaned up! if input.is_empty() { // Input is empty, do nothing. } else if let Some(cur) = self.cur { // Both lists are non-empty let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); if let Some(prev) = (*cur.as_ptr()).front { // General Case, no boundaries, just internal fixups (*prev.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(prev); (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); } else { // No prev, we're appending to the front (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); self.list.front = Some(in_front); } // Index moves forward by input length *self.index.as_mut().unwrap() += input.len; } else if let Some(back) = self.list.back { // We're on the ghost but non-empty, append to the back let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); (*back.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(back); self.list.back = Some(in_back); } else { // We're empty, become the input, remain on the ghost std::mem::swap(self.list, &mut input); } self.list.len += input.len; // Not necessary but Polite To Do input.len = 0; // Input dropped here }
总之,我已经筋疲力尽了,所以 insert
和 remove
以及所有其他应用程序接口就留给读者练习。
下面是 Cursor 的最终代码,我做对了吗?我只有在写下一章并测试这个怪东西时才能知道!
#![allow(unused)] fn main() { pub struct CursorMut<'a, T> { list: &'a mut LinkedList<T>, cur: Link<T>, index: Option<usize>, } impl<T> LinkedList<T> { pub fn cursor_mut(&mut self) -> CursorMut<T> { CursorMut { list: self, cur: None, index: None, } } } impl<'a, T> CursorMut<'a, T> { pub fn index(&self) -> Option<usize> { self.index } pub fn move_next(&mut self) { if let Some(cur) = self.cur { unsafe { // We're on a real element, go to its next (back) self.cur = (*cur.as_ptr()).back; if self.cur.is_some() { *self.index.as_mut().unwrap() += 1; } else { // We just walked to the ghost, no more index self.index = None; } } } else if !self.list.is_empty() { // We're at the ghost, and there is a real front, so move to it! self.cur = self.list.front; self.index = Some(0) } else { // We're at the ghost, but that's the only element... do nothing. } } pub fn move_prev(&mut self) { if let Some(cur) = self.cur { unsafe { // We're on a real element, go to its previous (front) self.cur = (*cur.as_ptr()).front; if self.cur.is_some() { *self.index.as_mut().unwrap() -= 1; } else { // We just walked to the ghost, no more index self.index = None; } } } else if !self.list.is_empty() { // We're at the ghost, and there is a real back, so move to it! self.cur = self.list.back; self.index = Some(self.list.len - 1) } else { // We're at the ghost, but that's the only element... do nothing. } } pub fn current(&mut self) -> Option<&mut T> { unsafe { self.cur.map(|node| &mut (*node.as_ptr()).elem) } } pub fn peek_next(&mut self) -> Option<&mut T> { unsafe { self.cur .and_then(|node| (*node.as_ptr()).back) .map(|node| &mut (*node.as_ptr()).elem) } } pub fn peek_prev(&mut self) -> Option<&mut T> { unsafe { self.cur .and_then(|node| (*node.as_ptr()).front) .map(|node| &mut (*node.as_ptr()).elem) } } pub fn split_before(&mut self) -> LinkedList<T> { // We have this: // // list.front -> A <-> B <-> C <-> D <- list.back // ^ // cur // // // And we want to produce this: // // list.front -> C <-> D <- list.back // ^ // cur // // // return.front -> A <-> B <- return.back // if let Some(cur) = self.cur { // We are pointing at a real element, so the list is non-empty. unsafe { // Current state let old_len = self.list.len; let old_idx = self.index.unwrap(); let prev = (*cur.as_ptr()).front; // What self will become let new_len = old_len - old_idx; let new_front = self.cur; let new_back = self.list.back; let new_idx = Some(0); // What the output will become let output_len = old_len - new_len; let output_front = self.list.front; let output_back = prev; // Break the links between cur and prev if let Some(prev) = prev { (*cur.as_ptr()).front = None; (*prev.as_ptr()).back = None; } // Produce the result: self.list.len = new_len; self.list.front = new_front; self.list.back = new_back; self.index = new_idx; LinkedList { front: output_front, back: output_back, len: output_len, _boo: PhantomData, } } } else { // We're at the ghost, just replace our list with an empty one. // No other state needs to be changed. std::mem::replace(self.list, LinkedList::new()) } } pub fn split_after(&mut self) -> LinkedList<T> { // We have this: // // list.front -> A <-> B <-> C <-> D <- list.back // ^ // cur // // // And we want to produce this: // // list.front -> A <-> B <- list.back // ^ // cur // // // return.front -> C <-> D <- return.back // if let Some(cur) = self.cur { // We are pointing at a real element, so the list is non-empty. unsafe { // Current state let old_len = self.list.len; let old_idx = self.index.unwrap(); let next = (*cur.as_ptr()).back; // What self will become let new_len = old_idx + 1; let new_back = self.cur; let new_front = self.list.front; let new_idx = Some(old_idx); // What the output will become let output_len = old_len - new_len; let output_front = next; let output_back = self.list.back; // Break the links between cur and next if let Some(next) = next { (*cur.as_ptr()).back = None; (*next.as_ptr()).front = None; } // Produce the result: self.list.len = new_len; self.list.front = new_front; self.list.back = new_back; self.index = new_idx; LinkedList { front: output_front, back: output_back, len: output_len, _boo: PhantomData, } } } else { // We're at the ghost, just replace our list with an empty one. // No other state needs to be changed. std::mem::replace(self.list, LinkedList::new()) } } pub fn splice_before(&mut self, mut input: LinkedList<T>) { // We have this: // // input.front -> 1 <-> 2 <- input.back // // list.front -> A <-> B <-> C <- list.back // ^ // cur // // // Becoming this: // // list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back // ^ // cur // unsafe { // We can either `take` the input's pointers or `mem::forget` // it. Using `take` is more responsible in case we ever do custom // allocators or something that also needs to be cleaned up! if input.is_empty() { // Input is empty, do nothing. } else if let Some(cur) = self.cur { // Both lists are non-empty let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); if let Some(prev) = (*cur.as_ptr()).front { // General Case, no boundaries, just internal fixups (*prev.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(prev); (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); } else { // No prev, we're appending to the front (*cur.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(cur); self.list.front = Some(in_front); } // Index moves forward by input length *self.index.as_mut().unwrap() += input.len; } else if let Some(back) = self.list.back { // We're on the ghost but non-empty, append to the back let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); (*back.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(back); self.list.back = Some(in_back); } else { // We're empty, become the input, remain on the ghost std::mem::swap(self.list, &mut input); } self.list.len += input.len; // Not necessary but Polite To Do input.len = 0; // Input dropped here } } pub fn splice_after(&mut self, mut input: LinkedList<T>) { // We have this: // // input.front -> 1 <-> 2 <- input.back // // list.front -> A <-> B <-> C <- list.back // ^ // cur // // // Becoming this: // // list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back // ^ // cur // unsafe { // We can either `take` the input's pointers or `mem::forget` // it. Using `take` is more responsible in case we ever do custom // allocators or something that also needs to be cleaned up! if input.is_empty() { // Input is empty, do nothing. } else if let Some(cur) = self.cur { // Both lists are non-empty let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); if let Some(next) = (*cur.as_ptr()).back { // General Case, no boundaries, just internal fixups (*next.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(next); (*cur.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(cur); } else { // No next, we're appending to the back (*cur.as_ptr()).back = Some(in_front); (*in_front.as_ptr()).front = Some(cur); self.list.back = Some(in_back); } // Index doesn't change } else if let Some(front) = self.list.front { // We're on the ghost but non-empty, append to the front let in_front = input.front.take().unwrap(); let in_back = input.back.take().unwrap(); (*front.as_ptr()).front = Some(in_back); (*in_back.as_ptr()).back = Some(front); self.list.front = Some(in_front); } else { // We're empty, become the input, remain on the ghost std::mem::swap(self.list, &mut input); } self.list.len += input.len; // Not necessary but Polite To Do input.len = 0; // Input dropped here } } } }