📌

Rustで単方向LinkedListを書いてみる

2024/01/31に公開

概要

https://www.amazon.co.jp/みんなのデータ構造-Pat-Morin/dp/4908686068/ref=sr_1_1?__mk_ja_JP=カタカナ&crid=3HFHM4XE0TZ37&keywords=みんなのデータ構造&qid=1705709264&sprefix=みんなのデータこうぞう,aps,205&sr=8-1

「みんなのデータ構造」という本を読んでいて、Rustの練習に良さそうなので、色々書いてみる。

まずは単方向のLinkedListを書いてみる。

インタフェース

単方向リストのインタフェースは次の通り。

LIFO(Last In First Out)のスタック操作とFIFOのキュー操作を持つ。

pub trait ListInterface<T: std::fmt::Debug> {
    fn new() -> Self;
    /* スタックの操作 */
    fn push(&mut self, element: T);
    fn pop(&mut self) -> Option<T>;
    /* キューの操作 */
    fn add(&mut self, element: T);
    fn remove(&mut self) -> Option<T>;
}

データ構造

データの構造は、格納データを持つ Node と Nodeへの参照を持つ List を使う。

#[derive(Debug)]
struct Node<T> {
    // 格納するデータ
    element: T,
    // 次のノードへの参照
    next: Option<Arc<Mutex<Node<T>>>>,
}

#[derive(Debug)]
pub struct List<T: std::fmt::Debug> {
    // 先頭のノードへの参照
    head: Option<Arc<Mutex<Node<T>>>>,
    // 最後のノードへの参照
    tail: Option<Arc<Mutex<Node<T>>>>,
    // Listのサイズ
    size: usize,
}

ノードへの参照は、Arcを使う。
Arcは基本的に、参照先の変更を許していないため、参照の書き換えを行うためにMutexでNodeをラップする。

スタック操作

値の追加(push)

// 先頭に値を追加する
pub fn push(&mut self, element: T) {
    let new_node = Arc::new(Mutex::new(Node {
        element,
	// NOTE: takeは、Option型から値を取り出して、なければNoneを返す。
        next: self.head.take(),
    }));
    self.head = Some(Arc::clone(&new_node));

    // NOTE: Stackの場合は、最初の値が末尾になるため、sizeが0の場合のみtailを更新する
    if self.size == 0 {
        self.tail = Some(Arc::clone(&new_node));
    }
    self.size += 1;
}

rust特有のコードとしては、Option型のtake()とArc::cloneあたり。
Arc::clone(&node)node.clone() は等価だが Arcの参照クローンを明示するために Arc::cloneを使う。

値の取得(pop)

// 先頭から値を取り出す
pub fn pop(&mut self) -> Option<T> {
    if self.size == 0 {
        return None;
    }
    // NOTE: 先に self.tail を None にしておかないと、headから値を取り出せない(Arc::try_unwrap)
    if self.size == 1 {
        self.tail = None;
    }
    match self.head.take() {
        None => None,
        Some(old_head) => {
            // NOTE: このcloneは、Option::cloneだが、Optionの中身はArcなので、Arc::cloneと同じ挙動をする
            self.head = old_head.lock().unwrap().next.clone();

            self.size -= 1;
            match Arc::try_unwrap(old_head) {
                Ok(node) => Some(node.into_inner().unwrap().element),
                Err(_arc) => None,
            }
        }
    }
}

Arc::try_unwrap は、Arc<T> の値を取り出して所有権の移動を行うが、参照カウントが1(最後の参照)でない場合はエラーになってしまう。

そのため、self.size == 1 の場合は、先にtailにNoneを入れ、参照カウントを1にしてから、Arc::try_unwrapで値を取り出している。(self.size == 1のときは、tailとheadに同じ値の参照が入るため)

old_head.lock().unwrap().next.clone() は、headの次の値を取り出して、cloneしている。
nextが指す値は、実際は Option<Arc<Mutex<Node<T>>>> 型だが、Optionにcloneを掛けたときは、Optionの中の値がcloneされるため、Arc<Mutex<Node<T>>> がクローン、つまり、Arc::cloneと同じ挙動になる。

キュー操作

値の追加

// 末尾に値を追加する
pub fn add(&mut self, element: T) {
    let new_node = Arc::new(Mutex::new(Node {
        element,
        next: None,
    }));

    match self.tail.take() {
	// NOTE: tailに値がない = リストの要素が0
        None => {
            self.head = Some(Arc::clone(&new_node));
            self.tail = Some(Arc::clone(&new_node));
        }
        Some(old_tail) => {
            // NOTE: mutexを使わない場合、Arcの値を書き換えることができない
            old_tail.lock().unwrap().next = Some(Arc::clone(&new_node));
            self.tail = Some(Arc::clone(&new_node));
        }
    }
    self.size += 1;
}

キューは末尾に値を追加するため、tailのnextを追加する要素の参照に更新する必要がある。
Arcの値は、本来書き換え不可だが、Mutexによるlockを行うことで書き換えることができる(old_tail.lock()箇所)

値の取り出し

pub fn remove(&mut self) -> Option<T> {
    if self.size == 0 {
        return None;
    }
    if self.size == 1 {
        self.tail = None;
    }
    match self.head.take() {
        None => None,
        Some(old_head) => {
            self.head = old_head.lock().unwrap().next.clone();

            self.size -= 1;
            match Arc::try_unwrap(old_head) {
                Ok(node) => Some(node.into_inner().unwrap().element),
                Err(_arc) => None,
            }
        }
    }
}

removeについては、先頭から値を取り出すため、popと同じ挙動になる。

コード全体

コード
use std::sync::{Arc, Mutex};

pub trait ListInterface<T: std::fmt::Debug> {
    fn new() -> Self;
    /* スタックの操作 */
    fn push(&mut self, element: T);
    fn pop(&mut self) -> Option<T>;
    /* キューの操作 */
    fn add(&mut self, element: T);
    fn remove(&mut self) -> Option<T>;
}

#[derive(Debug)]
struct Node<T> {
    element: T,
    next: Option<Arc<Mutex<Node<T>>>>,
}

#[derive(Debug)]
pub struct List<T: std::fmt::Debug> {
    head: Option<Arc<Mutex<Node<T>>>>,
    tail: Option<Arc<Mutex<Node<T>>>>,
    size: usize,
}

impl<T: std::fmt::Debug> Default for List<T> {
    fn default() -> Self {
        Self {
            head: Default::default(),
            tail: Default::default(),
            size: Default::default(),
        }
    }
}

impl<T: std::fmt::Debug> List<T> {
    pub fn new() -> Self {
        Default::default()
    }

    // 先頭に値を追加する
    pub fn push(&mut self, element: T) {
        let new_node = Arc::new(Mutex::new(Node {
            element,
            next: self.head.take(),
        }));
        self.head = Some(Arc::clone(&new_node));

        // NOTE: Stackの場合は、最初の値が末尾になるため、sizeが0の場合のみtailを更新する
        if self.size == 0 {
            self.tail = Some(Arc::clone(&new_node));
        }
        self.size += 1;
    }

    // 先頭から値を取り出す
    pub fn pop(&mut self) -> Option<T> {
        if self.size == 0 {
            return None;
        }
        // NOTE: 先に self.tail を None にしておかないと、headから値を取り出せない(Arc::try_unwrap)
        if self.size == 1 {
            self.tail = None;
        }
        match self.head.take() {
            None => None,
            Some(old_head) => {
                // NOTE: このcloneは、Option::cloneだが、Optionの中身はArcなので、Arc::cloneと同じ挙動をする
                self.head = old_head.lock().unwrap().next.clone();

                self.size -= 1;
                match Arc::try_unwrap(old_head) {
                    Ok(node) => Some(node.into_inner().unwrap().element),
                    Err(_arc) => None,
                }
            }
        }
    }

    // 末尾に値を追加する
    pub fn add(&mut self, element: T) {
        let new_node = Arc::new(Mutex::new(Node {
            element,
            next: None,
        }));

        match self.tail.take() {
            // NOTE: tailに値がない = リストの要素が0
            None => {
                self.head = Some(Arc::clone(&new_node));
                self.tail = Some(Arc::clone(&new_node));
            }
            Some(old_tail) => {
                // NOTE: mutexを使わない場合、Arcの値を書き換えることができない
                old_tail.lock().unwrap().next = Some(Arc::clone(&new_node));
                self.tail = Some(Arc::clone(&new_node));
            }
        }
        self.size += 1;
    }

    // 先頭から値を取り出す
    pub fn remove(&mut self) -> Option<T> {
        if self.size == 0 {
            return None;
        }
        if self.size == 1 {
            self.tail = None;
        }
        match self.head.take() {
            None => None,
            Some(old_head) => {
                self.head = old_head.lock().unwrap().next.clone();

                self.size -= 1;
                match Arc::try_unwrap(old_head) {
                    Ok(node) => Some(node.into_inner().unwrap().element),
                    Err(_arc) => None,
                }
            }
        }
    }
}

fn main() {
    let mut list = List::new();
    list.push(1);
    list.push(2);
    list.pop();
    list.pop();
    list.add(3);
    list.add(4);
    list.remove();
    list.remove();
    println!("{:?}", list);
}

Discussion