🪩

Rust で抽象化したグラフを扱う

2023/02/06に公開

メモ程度の軽いやつです.

抽象化

有向グラフの隣接リストの「与えられた頂点から直接辺が伸びている頂点を列挙できる」という機能をトレイトとして抜き出し,辺を陽に持たないグリッドグラフなどにも impl したいねという話です.

まずコード全部出したあと下で説明します

mod graph {
    use std::{iter, slice};

    pub trait Graph<'a> {
        type Node;
        type Iter: Iterator<Item = Self::Node>;
        fn next(&'a self, _: Self::Node) -> Self::Iter;
    }

    pub struct AdjList(Vec<Vec<usize>>);
    impl AdjList {
        pub fn new(n: usize) -> AdjList {
            AdjList(vec![Vec::new(); n])
        }
        pub fn add_directed(&mut self, from: usize, to: usize) {
            self.0[from].push(to);
        }
        pub fn add_undirected(&mut self, u: usize, v: usize) {
            self.add_directed(u, v);
            self.add_directed(v, u);
        }
    }
    impl<'a> Graph<'a> for AdjList {
        type Node = usize;
        type Iter = iter::Cloned<slice::Iter<'a, usize>>;
        fn next(&'a self, node: usize) -> Self::Iter {
            self.0[node].iter().cloned()
        }
    }

    pub struct Grid4 {
        height: usize,
        width: usize,
    }
    impl Grid4 {
        pub fn new(height: usize, width: usize) -> Grid4 {
            Grid4 { height, width }
        }
    }
    pub const DIR4: [(usize, usize); 4] = [(1, 0), (0, 1), (!0, 0), (0, !0)];
    pub struct Grid4Iter<'a> {
        r: usize,
        c: usize,
        grid: &'a Grid4,
        iter: std::slice::Iter<'static, (usize, usize)>,
    }
    impl<'a> Iterator for Grid4Iter<'a> {
        type Item = (usize, usize);
        fn next(&mut self) -> Option<Self::Item> {
            for &(i, j) in &mut self.iter {
                let r = self.r.wrapping_add(i);
                let c = self.c.wrapping_add(j);
                if r < self.grid.height && c < self.grid.width {
                    return Some((r, c));
                }
            }
            None
        }
    }
    impl<'a> Graph<'a> for Grid4 {
        type Node = (usize, usize);
        type Iter = Grid4Iter<'a>;
        fn next(&'a self, (r, c): (usize, usize)) -> Grid4Iter<'a> {
            Grid4Iter {
                r,
                c,
                grid: self,
                iter: DIR4.iter(),
            }
        }
    }
}

トレイト

重みなしの場合

pub trait Graph<'a> {
    type Node;
    type Iter: Iterator<Item = Self::Node>;
    fn next(&'a self, _: Self::Node) -> Self::Iter;
}

Node は頂点を表す型です.隣接リストなら usize,グリッドなら (usize, usize) みたいな. next が次の頂点を列挙する関数です.イテレータを返すので,その型も Iter として与えます.

隣接リスト

これを隣接リスト

pub struct AdjList(Vec<Vec<usize>>);

に impl すると,

use std::{iter, slice};

impl<'a> Graph<'a> for AdjList {
    type Node = usize;
    type Iter = iter::Cloned<slice::Iter<'a, usize>>;
    fn next(&'a self, node: usize) -> Self::Iter {
        self.0[node].iter().cloned()
    }
}

となります.

グリッド

4 方向のグリッドグラフ

pub struct Grid4 {
    height: usize,
    width: usize,
}

に impl する場合,

pub const DIR4: [(usize, usize); 4] = [(1, 0), (0, 1), (!0, 0), (0, !0)];
pub struct Grid4Iter<'a> {
    r: usize,
    c: usize,
    grid: &'a Grid4,
    iter: std::slice::Iter<'static, (usize, usize)>,
}

を使って

impl<'a> Graph<'a> for Grid4 {
    type Node = (usize, usize);
    type Iter = Grid4Iter<'a>;
    fn next(&'a self, (r, c): (usize, usize)) -> Grid4Iter<'a> {
        Grid4Iter {
            r,
            c,
            grid: self,
            iter: DIR4.iter(),
        }
    }
}

impl<'a> Iterator for Grid4Iter<'a> {
    type Item = (usize, usize);
    fn next(&mut self) -> Option<Self::Item> {
        for &(i, j) in &mut self.iter {
            let r = self.r.wrapping_add(i);
            let c = self.c.wrapping_add(j);
            if r < self.grid.height && c < self.grid.width {
                return Some((r, c));
            }
        }
        None
    }
}

といった感じかな……

DIR4pub にしているのは,何かに使うかも〜という気持ちです.

わざわざ Grid4Iter<'a> を付けて中で &'a Grid4 を持ってるのは, Grid4Iter のライフタイムが元の Grid4 を超えるようなコードはまずそうな感じがするからです.

Discussion