Implementing Doubly Linked List in Rust

Implementing Doubly Linked List in Rust


Rust

Doubly linked list is a data structure similar to singly linked list, but with the addition of bidirectional connections between pairs of nodes. Before leaning about the data structure I had no idea what we can do with it. However, it’s truly amazing when combining it with other data structure such as hashmap.

LeetCode provides an interesting problem that requires implementing your own LRU cache. I managed to solved it by utilizing the doubly linked list data structure. As you can imagine, writing linked list in Rust is challenging since it involves dealing with many Rc and RefCell. Nonetheless, tackling this problem for a week taught me a great deal. To be honest, I use this beautiful solution as a reference to solve it, but with some slight changes as follows:

Avoid unwrapping

It’s not ideal to use .unwrap() to get the data inside Result and Option so I work it around with different approach, mainly pattern matching.

// Instead of:
if tail.is_some() {
    tail.as_ref().unwrap().borrow_mut().next = Some(node.clone());
}

// We can do this:
if let Some(tail) = &tail {
    tail.borrow_mut().next = Some(node.clone());
}

Avoid deep cloning

According to The Rust Book , it’s a convention to use Rc::clone(&node) to copy a pointer of an RC. So I tried to avoid .clone() as much as possible so that it could minimize the cost:

// Instead of:
self.remove(target.clone());

// We can do this:
self.remove(Rc::clone(&target));

As you can see it makes the code longer and eventually might affect code’s readability. But it’s a recommended way as it doesn’t deep-copy the data.

And here is my solution:

(I couldn’t finish it without the help of code editor 😿)

use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};

struct LRUCache {
    capacity: usize,
    cache: HashMap<i32, Rc<RefCell<LinkedNode>>>,
    dll: DoublyLinkedList,
}

impl LRUCache {
    fn new(capacity: i32) -> Self {
        Self {
            capacity: capacity as usize,
            cache: HashMap::new(),
            dll: DoublyLinkedList::new(),
        }
    }

    fn get(&mut self, key: i32) -> i32 {
        if let Some(node) = self.cache.get(&key) {
            // Cache hit -> update doubly linked list
            self.dll.move_to_tail(Rc::clone(&node));
            return node.borrow().val.clone();
        }
        -1
    }

    fn put(&mut self, key: i32, value: i32) {
        match self.cache.get(&key) {
            Some(node) => {
                node.borrow_mut().val = value;
                self.dll.move_to_tail(Rc::clone(&node));
            }
            None => {
                let new_node = Rc::new(RefCell::new(LinkedNode::new(key, value)));
                self.cache.insert(key, Rc::clone(&new_node));
                self.dll.push_tail(Rc::clone(&new_node));
                if self.capacity < self.cache.len() {
                    let key = self.dll.pop_head();
                    self.cache.remove(&key);
                }
            }
        }
    }
}

struct DoublyLinkedList {
    head: Option<Rc<RefCell<LinkedNode>>>,
    tail: Option<Rc<RefCell<LinkedNode>>>,
}

impl DoublyLinkedList {
    pub fn new() -> Self {
        Self {
            head: None,
            tail: None,
        }
    }

    fn get_head(&self) -> Option<Rc<RefCell<LinkedNode>>> {
        match &self.head {
            Some(head) => Some(Rc::clone(head)),
            None => None,
        }
    }

    fn get_tail(&self) -> Option<Rc<RefCell<LinkedNode>>> {
        match &self.tail {
            Some(tail) => Some(Rc::clone(tail)),
            None => None,
        }
    }

    pub fn push_tail(&mut self, node: Rc<RefCell<LinkedNode>>) {
        match &self.get_tail() {
            Some(prev_tail) => {
                prev_tail.borrow_mut().next.replace(Rc::clone(&node));
                node.borrow_mut().prev = Some(Rc::clone(&prev_tail));
            }
            None => {
                // This node should be the 1st node
                node.borrow_mut().prev = None;
                self.head = Some(Rc::clone(&node));
            }
        }
        node.borrow_mut().next = None;
        self.tail = Some(Rc::clone(&node));
    }

    pub fn move_to_tail(&mut self, node: Rc<RefCell<LinkedNode>>) {
        self.remove(Rc::clone(&node));
        self.push_tail(Rc::clone(&node));
    }

    pub fn pop_head(&mut self) -> i32 {
        if let Some(head) = &self.head {
            return self.remove(Rc::clone(&head));
        }
        -1
    }

    pub fn remove(&mut self, node: Rc<RefCell<LinkedNode>>) -> i32 {
        let prev = self.get_prev(Rc::clone(&node));
        let next = self.get_next(Rc::clone(&node));
        let key = node.borrow().key;
        match (prev, next) {
            (Some(prev), Some(next)) => {
                // node was in the middle of doubly linked list
                prev.borrow_mut().next.replace(Rc::clone(&next));
                next.borrow_mut().prev.replace(Rc::clone(&prev));
            }
            (Some(prev), None) => {
                // The node should be the tail
                prev.borrow_mut().next.take();
                self.tail.replace(Rc::clone(&prev));
            }
            (None, Some(next)) => {
                // The node should be the head
                next.borrow_mut().prev.take();
                self.head.replace(Rc::clone(&next));
            }
            (None, None) => {
                // The node should be head and tail
                self.head.take();
                self.tail.take();
            }
        }
        key
    }

    fn get_prev(&self, node: Rc<RefCell<LinkedNode>>) -> Option<Rc<RefCell<LinkedNode>>> {
        match &node.borrow().prev {
            Some(prev) => Some(Rc::clone(&prev)),
            None => None,
        }
    }

    fn get_next(&self, node: Rc<RefCell<LinkedNode>>) -> Option<Rc<RefCell<LinkedNode>>> {
        match &node.borrow().next {
            Some(next) => Some(Rc::clone(&next)),
            None => None,
        }
    }
}

struct LinkedNode {
    key: i32,
    val: i32,
    prev: Option<Rc<RefCell<LinkedNode>>>,
    next: Option<Rc<RefCell<LinkedNode>>>,
}

impl LinkedNode {
    pub fn new(key: i32, val: i32) -> Self {
        Self {
            key,
            val,
            prev: None,
            next: None,
        }
    }
}

Thanks for reading ✌️

© 2024 Hiro