First of all, I suggest a minor refactoring of your base definitions:
#[derive(Debug)]
pub
struct Node {
head: i64,
tail: LinkedList,
}
pub use LinkedList::*;
#[derive(Debug)]
pub
enum LinkedList {
Nil,
Cons(Box<Node>),
}
// Let's add a nicer constructor
impl LinkedList {
pub
fn cons (head: i64, tail: Self) -> Self
{
LinkedList::Cons(Box::new(Node {
head,
tail,
}))
}
}
impl LinkedList {
#[inline]
pub
fn take (self: &'_ mut Self) -> Self
{
mem::replace(self, LinkedList::Nil)
}
}
Now we can reversely collect an iterable as in any classic functional language exercise:
impl LinkedList {
pub
fn from_iter_rev (iterable: impl IntoIterator<Item = i64>) -> Self
{
let mut list = LinkedList::Nil;
for x in iterable {
list = LinkedList::cons(x, list);
}
list
}
}
However, Rust allows not only functional constructs, but also pointer/cursor mutation à la C/C++ (but without the need for unsafe
raw pointers):
impl iter::FromIterator<i64> for LinkedList {
fn from_iter<Iterable : IntoIterator<Item = i64>> (
iterable: Iterable,
) -> Self
{
let mut list = LinkedList::Nil;
let mut cursor = &mut list;
for x in iterable {
let singleton = Box::new(Node { head: x, tail: Nil });
debug_assert!(// cursor points to an empty list (end of list)
(*cursor)
.is_empty()
);
// mutate the pointee to append an element to the list
*cursor = LinkedList::Cons(singleton);
// move the cursor forward
cursor = match *cursor {//
| Cons(ref mut node) => &mut node.tail,
| Nil => unreachable!(),
}
}
list
}
}
Finally we can go and implement remove_dups
:
impl LinkedList {
pub
fn remove_dups (self: &'_ mut Self)
{
match *self {//
| Nil => {},
| Cons(ref mut node) => {
with_prev(&mut node.tail, node.head);
},
}
fn with_prev (cursor: &'_ mut LinkedList, prev: i64)
{
match *cursor {//
| Nil => {},
| Cons(ref mut node) => {
if node.head == prev {
// mutate the pointee
*cursor = node.tail.take();
with_prev(cursor, prev);
} else {
// advance the cursor
with_prev(&mut node.tail, node.head);
}
},
}
}
}
}
Note: NLL are really needed here to get a nicer syntax, else Rust has trouble with using a mut cursor: &mut _
. Still, here is the previous function without NLL:
fn with_prev (cursor: &'_ mut LinkedList, prev: i64)
{
// mutate the pointee
*cursor = match *cursor {//
| Nil => return, // early return
| Cons(ref mut node) => {
if node.head == prev {
node.tail.take()
} else {
// early return
return with_prev(&mut node.tail, node.head);
}
},
};
with_prev(cursor, prev);
}
Bonus: LinkedList traversal (e.g., to Display
it)
Although a LinkedList
can lead to a very simple implementation of a stack:
pub
trait MutableStack {
type Item;
fn push (self: &'_ mut Self, value: Self::Item);
fn pop (self: &'_ mut Self) -> Option<Self::Item>;
}
impl MutableStack for LinkedList {
type Item = i64;
#[inline]
fn push (self: &'_ mut Self, value: i64)
{
*self = Self::cons(value, self.take());
}
#[inline]
fn pop (self: &'_ mut Self) -> Option<i64>
{
match self {//
| Nil => None,
| Cons(ref mut node) => {
let (head, tail) = (node.head, node.tail.take());
*self = tail;
Some(head)
},
}
}
}
which, in turn, leads to a trivial implementation of Iterator
:
impl Iterator for LinkedList {
type Item = i64;
#[inline]
fn next (
self: &'_ mut Self,
) -> Option<Self::Item>
{
self.pop()
}
}
but this requires in-place mutation...
To iterate over reads of the contents of the list, functional style favors internal iteration and fold
ing:
impl Visit for LinkedList {
#[inline]
fn try_fold<Acc, Ret, Folder> (
self: &'_ Self,
initial_acc: Acc,
mut folder: Folder,
) -> Ret
where
Ret : Try<Ok = Acc>,
Folder : FnMut(Acc, i64) -> Ret,
{
let mut acc = initial_acc;
let mut cursor = self;
loop {
// Advance cursor
cursor = match *cursor {//
| Nil => return Ret::from_ok(acc),
| Cons(ref node) => {
acc = folder(acc, node.head)?;
&node.tail
},
}
}
}
}
pub
trait Visit {
fn try_fold<Acc, Ret, Folder> (
self: &'_ Self,
initial_acc: Acc,
folder: Folder,
) -> Ret
where
Ret : Try<Ok = Acc>,
Folder : FnMut(Acc, i64) -> Ret,
;
#[inline]
fn fold<Acc, Folder> (
self: &'_ Self,
acc: Acc,
folder: Folder,
) -> Acc
where
Folder : FnMut(Acc, i64) -> Acc,
{
enum Unfallible {}
self.try_fold(acc, {
let mut folder = folder;
move |acc, x| Ok(folder(acc, x))
})
.unwrap_or_else(|unfallible: Unfallible| match unfallible {})
}
#[inline]
fn for_each (
self: &'_ Self,
f: impl FnMut(i64),
)
{
self.fold((), { let mut f = f; move |(), x| f(x) })
}
#[inline]
fn try_for_each<R, F> (
self: &'_ Self,
f: F,
) -> R
where
F : FnMut(i64) -> R,
R : Try<Ok = ()>,
{
self.try_fold((), { let mut f = f; move |(), x| f(x) })
}
}
Thus allowing to easily define methods based on this fold
ing:
impl fmt::Display for LinkedList {
fn fmt (
self: &'_ Self,
stream: &'_ mut fmt::Formatter<'_>,
) -> fmt::Result
{
self.try_for_each(|x| write!(stream, "{} :: ", x))?;
write!(stream, "[]")
}
}
Running it
fn main ()
{
use iter::FromIterator;
const NUMBERS: &[i64] = &[1, 1, 5, 3, 5, 4, 2, 7, 7, 7, 8];
let reversed_list = LinkedList::from_iter_rev(
NUMBERS
.iter()
.cloned()
);
eprintln!("{}", reversed_list);
let mut test_list = LinkedList::from_iter(
NUMBERS
.iter()
.cloned()
);
eprintln!("{}", test_list);
test_list.remove_dups();
eprintln!("{}", test_list);
}
displays (Playground):
8 :: 7 :: 7 :: 7 :: 2 :: 4 :: 5 :: 3 :: 5 :: 1 :: 1 :: []
1 :: 1 :: 5 :: 3 :: 5 :: 4 :: 2 :: 7 :: 7 :: 7 :: 8 :: []
1 :: 5 :: 3 :: 5 :: 4 :: 2 :: 7 :: 8 :: []