Generic Unique Function for Int and Float Types in Rust

May 16, 2017   #Rust 

はじめに

 EXERCISMの問題で、integer/float型のジェネリクスとして、ユニークな集合を求めるメソッド定義するのに少しハマった。Rustのfloat型は、OrdトレイトではなくPartialOrdトレイトを実装しているので、注意が必要。

Rustのfloat型

 Rustのfloat型(f32とf64)はstd::cmp::Ordstd::hash::Hashトレイトを実装していない。このため、これらの実装を要求するstd::collections::BTreeSetや、std::collections::HashSetによる集合(Set)への変換はできない。これらのトレイトをプリミティブ型に実装することもできると思うが、ちょっと面倒。

 float型では、Ordの変わりにstd::cmp::PartialOrdトレイトを実装している。flaot型がPartial(部分的に)Ord(比較可能)なのは、NAN値では以下が成り立つためである(NANはある値より小さくなく、等しいか大きくも無い)。

use std::f32::NAN;
assert!((NAN < 0.0) == false && (NAN >= 0.0) == false)

float型とinteger型のジェネリクス

 Ordトレイトのcmpメソッドに対応するものとして、PartialOrdにはpartial_cmpメソッドがある。また、Ordトレイトの実装にはPartialOrdトレイトの実装を要求する。つまり、partial_cmpメソッドはfloat型とinteger型に存在する。今回は、このpartial_cmpメソッドを使ってPartialOrdトレイトを実装する型TのVec型に対するsort用のジェネリックなクロージャーを定義した。

let mut v: [f32; 3] = [3.0, 1.0, 2.0];
v.sort_by(|a, b| a.partial_cmp(b).unwrap());

 NAN値がある場合はpanicする。
 後は、O(n log n)でsortしたVecに対して、連続して現れる同一の要素をO(n)で削除するだけである。

Triangle in Rust

 EXERCISMの問題Triangleで、floatの重複を省きたい状況に遭遇した。問題自体はかなり簡単だったけど、integerとfloatのジェネリクスにするのに結構ハマった。

extern crate num_traits;

use std::error::Error;
use std::fmt;
use num_traits::{Num, NumCast};

pub struct Triangle<T> {
    uniq_sides: Vec<T>,
}

impl<T> Triangle<T>
    where T: Num + NumCast + Copy + PartialOrd {

    pub fn build(sides: [T; 3]) -> Result<Triangle<T>, TriangleError> {
        let mut sides: Vec<T> = sides.iter().cloned().collect();
        sides.sort_by(|a, b| a.partial_cmp(b).unwrap());

        if sides[0] + sides[1] <= sides[2] {
            return Err(TriangleError::FailedToConstruct);
        }

        let mut uniq_sides: Vec<T> = Vec::new();
        for side in sides.into_iter() {
            if let Some(last_side) = uniq_sides.last() {
                if *last_side == side {
                    continue;
                }
            }
            uniq_sides.push(side);
        }

        Ok(Triangle { uniq_sides: uniq_sides })
    }

    pub fn is_equilateral(&self) -> bool {
        self.uniq_sides.len() == 1
    }

    pub fn is_isosceles(&self) -> bool {
        self.uniq_sides.len() == 2
    }

    pub fn is_scalene(&self) -> bool {
        self.uniq_sides.len() == 3
    }
}

#[derive(Debug)]
pub enum TriangleError {
    FailedToConstruct,
}

impl fmt::Display for TriangleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            TriangleError::FailedToConstruct => write!(f, "Failed to construct traiangle"),
        }
    }
}

impl Error for TriangleError {
    fn description(&self) -> &str {
        match *self {
            TriangleError::FailedToConstruct => "Failed to construct a new triagnle",
        }
    }
}

おわりに

 実行可能なファイルは、GitHubにもアップロードした1

参考

関連記事