Open10

「なっとく!関数型プログラミング」をRustで

Yos_KYos_K

第2章

struct ShoppingCart {
    items: Vec<String>,
    book_added: bool,
}

impl ShoppingCart {
    fn new() -> Self {
        Self {
            items: vec![],
            book_added: false,
        }
    }
    fn add_item(&mut self, item: &str) {
        self.items.push(item.to_owned());
        if item == "Book" {
            self.book_added = true;
        }
    }
    fn get_items(&self) -> Vec<String> {
        self.items.clone()
    }
}
Yos_KYos_K

removeを追加

struct ShoppingCart {
    items: Vec<String>,
    book_added: bool,
}

impl ShoppingCart {
    fn new() -> Self {
        Self {
            items: vec![],
            book_added: false,
        }
    }
    fn add_item(&mut self, item: &str) {
        self.items.push(item.to_owned());
        if item == "Book" {
            self.book_added = true;
        }
    }
    fn get_items(&self) -> Vec<String> {
        self.items.clone()
    }
    fn remove_item(&mut self, item: &str) {
        if let Some(pos) = self.items.iter().position(|i| i == item) {
            self.items.remove(pos);
            if item == "Book" {
                self.book_added = false;
            }
        };
    }
}

// 上の書き方だと、本を2冊追加してから1冊削除した場合、本はあるのに
// book_addedがfalseになってしまう

fn main() {
    let mut cart = ShoppingCart::new();
    cart.add_item("Book");
    cart.add_item("Book");
    cart.remove_item("Book");
    dbg!(&cart.items);
}
Yos_KYos_K

book_addedを消して、都度判定するように変更

struct ShoppingCart {
    items: Vec<String>,
}

impl ShoppingCart {
    fn new() -> Self {
        Self {
            items: vec![],
        }
    }
    fn add_item(&mut self, item: &str) {
        self.items.push(item.to_owned());
    }
    fn get_discount_percentage(&self) -> usize {
        if self.items.contains(&"Book".to_string()) {
            return 5;
        } else {
            return 0;
        }
    }
    fn get_items(&self) -> Vec<String> {
        self.items.clone()
    }
    fn remove_item(&mut self, item: &str) {
        if let Some(pos) = self.items.iter().position(|i| i == item) {
            self.items.remove(pos);
        };
    }
}
Yos_KYos_K

itemsを外から渡すようにして純粋な関数に

struct ShoppingCart;

impl ShoppingCart {
    fn get_discount_percentage(items: Vec<String>) -> usize {
        if items.contains(&"Book".to_string()) {
            return 5;
        } else {
            return 0;
        }
    }
}

fn main() {
    let mut items = vec![];
    items.push("Apple".to_string());
    dbg!("{}", ShoppingCart::get_discount_percentage(items));
}
Yos_KYos_K

第3章

最初の良くないコードはRustでの再現方法が分からなかったので修正後のみ

fn replan(plan: &Vec<String>, new_city: &str, before_city: &str) -> Vec<String> {
    let mut new_plan = plan.clone();
    if let Some(new_city_index) = plan.iter().position(|c| c == before_city) {
        new_plan.insert(new_city_index, new_city.to_owned());
    };
    new_plan
}

fn main() {
    let mut plan_a = vec![];
    plan_a.push("Paris".to_string());
    plan_a.push("Berlin".to_string());
    plan_a.push("Krakow".to_string());
    let plan_b = replan(&plan_a, "Vienna", "Krakow");
    dbg!(&plan_a);
    dbg!(&plan_b);
}
Yos_KYos_K

第4章

use std::cmp::Ordering;

/// 単語のスコアは'a'以外の文字ごとに1ポイント
/// 単語のリストが与えられたらスコアの高い順に並べ替えたリストを返す

fn score(word: &str) -> usize {
    word.replace("a", "").len()
}

fn score_comparator(w1: &str, w2: &str) -> Ordering {
    score(w2).cmp(&score(w1))
}

fn ranked_words<'a>(words: &'a [&'a str]) -> Vec<&'a str> {
    let mut new_words = words.clone().to_owned();
    new_words.sort_by(|a, b| score_comparator(a, b));
    new_words
}


fn main() {
    let words = vec!["ada", "haskell", "scala", "java", "rust"];
    let ranking = ranked_words(&words);
    dbg!(&ranking);
}
Yos_KYos_K

比較する関数を引数で渡すように変更

use std::cmp::Ordering;

/// 単語のスコアは'a'以外の文字ごとに1ポイント
/// 単語のリストが与えられたらスコアの高い順に並べ替えたリストを返す

fn score(word: &str) -> usize {
    word.replace("a", "").len()
}

fn score_comparator(w1: &str, w2: &str) -> Ordering {
    score(w2).cmp(&score(w1))
}

fn ranked_words<'a>(comparator: fn(&str, &str) -> Ordering, words: &'a [&'a str]) -> Vec<&'a str> {
    let mut new_words = words.clone().to_owned();
    new_words.sort_by(|a, b| comparator(a, b));
    new_words
}


fn main() {
    let words = vec!["ada", "haskell", "scala", "java", "rust"];
    let ranking = ranked_words(score_comparator, &words);
    dbg!(&ranking);
}
Yos_KYos_K

外部から渡すように変更したことでスコア計算に変更が出ても対応できるようになった

use std::cmp::Ordering;

/// 単語のスコアは'a'以外の文字ごとに1ポイント
/// 単語のリストが与えられたらスコアの高い順に並べ替えたリストを返す
/// 単語に'c'が含まれている場合は5ポイントのボーナススコアを加算する

fn score(word: &str) -> usize {
    word.replace("a", "").len()
}

fn score_with_bonus(word: &str) -> usize {
    let base = score(word);
    if word.contains("c") {
        return base + 5;
    } else {
        return base;
    }
}

fn score_comparator(w1: &str, w2: &str) -> Ordering {
    score(w2).cmp(&score(w1))
}

fn score_with_bonus_comparator(w1: &str, w2: &str) -> Ordering {
    score_with_bonus(w2).cmp(&score_with_bonus(w1))
}

fn ranked_words<'a>(comparator: fn(&str, &str) -> Ordering, words: &'a [&'a str]) -> Vec<&'a str> {
    let mut new_words = words.clone().to_owned();
    new_words.sort_by(|a, b| comparator(a, b));
    new_words
}


fn main() {
    let words = vec!["ada", "haskell", "scala", "java", "rust"];
    let ranking = ranked_words(score_with_bonus_comparator, &words);
    dbg!(&ranking);
}

ただ、似たような処理が出てしまう

Yos_KYos_K

ボーナスは加点される値だけ求める関数に変更し、最終的なスコアを求める関数はclosureで定義するように変更。これによりペナルティのような要件が追加されても対応しやすくなる

fn score(word: &str) -> isize {
    word.replace("a", "").len().try_into().unwrap()
}

fn bonus(word: &str) -> isize {
    if word.contains("c") {
        return 5;
    } else {
        return 0;
    }
}

fn penalty(word: &str) -> isize {
    if word.contains("s") {
        return 7;
    } else {
        0
    }
}

fn ranked_words<'a>(word_score: fn(&str) -> isize, words: &'a [&'a str]) -> Vec<&'a str> {
    let mut new_words = words.clone().to_owned();
    new_words.sort_by(|a, b| word_score(b).cmp(&word_score(a)));
    new_words
}


fn main() {
    let words = vec!["ada", "haskell", "scala", "java", "rust"];
    let score_func: fn(&str) -> isize = |x| {(score(x) + bonus(x) - penalty(x)).try_into().unwrap()};
    let ranking = ranked_words(score_func, &words);
    dbg!(&ranking);
}
Yos_KYos_K

与えられたリストに対して、各要素をスコアに変換したリストを返す関数

fn word_scores<'a>(word_score: fn(&str) -> isize, words: &'a [&'a str]) -> Vec<isize> {
    words.iter().map(|w| word_score(w)).collect()
}