Hello, Itamae From Docker!!

Itamae の plugin 書いててそろそろ test 欲しいなあとおもって2年くらいたちましたが、 ようやく Docker 使って test 書きました。

方向性

ここでテストを実施するとして何に対しておこなうのかというのを考えましたが、E2Eのテストだけで良いだろうという方向でテストを書くようにしています。 VMを立てて実行することにはチョット大袈裟だろうということでもっと簡単に、楽にということで docker を選択しています。

itamae で E2E のテストなのでここでは serverspec を利用してテストを行います。 E2E のテスト準備として docker 内で最初に目的のレシピを実施し、そのレシピが正しく動作しているかをテストしています。

準備とテスト実施

準備として以下の gem を追加しますが、 docker-api.gem の方はなくてもとくに問題ないです。 serverspec の対象バックエンドで docker指定できるようになりますが、 docker 内 (docker run -t hoge bundle exec serverspec など) で serverspec を実施すれば実行できますので、好みで追加しましょう。

1
2
spec.add_development_dependency "docker-api"
spec.add_development_dependency "serverspec"

次に Dockerfile を作成します。ここでは単純に必要な準備を実施、テストしたいディレクトリの追加、 bundler を利用して gem のインストールを実施してから、対象のディレクトリで itamaelocal を指定して対象の docker にレシピ適用します。そのあと docker runserverspec を実施することでテストをすることが可能となります。

1
2
3
4
5
6
7
8
FROM ruby
RUN echo "gem: --no-rdoc --no-ri" >> /.gemrc
RUN gem install bundler
ADD . /app
WORKDIR /app
RUN bundle install
RUN bundle exec itamae local samples/recipe.rb
CMD bundle exec rake

itamae-plugin-resource-pip

itamae-plugin-resource-pip でできるようになってます。 とくに docker.io とかで公開していませんので、以下に例を

1
2
3
cd /path/to/itamae-plugin-resource-pip
docker build -t katsyoshi/itamae-plugin-resource-pip .
docker run -t katsyoshi/itamae-plugin-resource-pip bundle exec rake

おわり

これやろうと思って調べてたらみつからずに Dockerfile 書き終えて、travis を追加しようとしたときに、@sue445 さんがやってたのをみつけてしまった。。。

RubyKaigi2018

RubyKaigi2018 に行ってきた

1日目

1日目は、 ずっと会場Aにいました。

  1. Matz: Keynote
  2. Aaron Patterson: Analyzing and Reducing Ruby Memory Usage
  3. Kenta Murata, and Yusaku Hatanaka: Deep Learning Programming on Ruby
  4. Bozhidar Batsov: All About RuboCop
  5. Naotoshi Seo: Fast Numerical Computing and Deep Learning in Ruby with Cumo
  6. Emma Haruka Iwao: Exploring Internal Ruby Through C Extensions
  7. Lightning Talks

この日、今年の RubyKaigi 2018 で最も楽しみにしてたのが rubocop の作者のトークで、彼のプロダクト(rubocop, flycheck-ruby)を利用してる身としては非常に楽しみでした。 DLと数値計算のやつは進捗が聞けてよかったっていうのとDeep Learningに関していうと難しいのかなあという感想が… Haruka さんのやつは、やっぱり今の実装十分に速いってのがわかって、これ以上速くするには非常に大変だなってのがよかったですね。 LTはどれもよくて rib も面白いし、 csvの高速化の話が特に好きでした。

2日目

2日目もほとんどA会場できいていたのと、GitHubの電源スペースにいました。

  1. Kouhei Sutou: My way with Ruby
  2. Kouichi Sasada: Guild Prototype
  3. Yuichiro Kaneko: RNode with code positions
  4. Yusuke Endo: Type Profiler: An analysis to guess type signatures
  5. Ruby Commiters vs. the World

この日のトークは特に楽しみにしていたのはなかったのですが、ここ最近毎年聞いていた @v0dro の発表を聞いていなかった。 金子さんの発表がわりとたのしく聞けて、「あーやっぱり型のはなしは興味ないな」って感じで遠藤さんの発表聞いてました。 この日の懇親会で飲みすぎた。

3日目

3日目はパフォーマンス改善を中心として聞いてました。

  1. Benoit Daloze: Parallel and Thread-Safe Ruby at High-Speed with TruffleRuby
  2. Takashi Kokubun: The Method JIT Compiler for Ruby 2.6
  3. Takeshi Watanabe: LuaJIT as a Ruby backend.
  4. Prasun Anand: High Performance GPU computing with Ruby
  5. Vladimir Makarov: Three Ruby performance projects
  6. TRICK 2018 (FINAL)

TuffleRubyとはやくてとてもよさそう。 国分さん、Vladimirの話はいつもどおりで安心した。

TRICK、どうしてこんなプログラムが思いつくのかさっぱりわからんがとにかく凄い以上の感想がうかんでこない。

おわり

RubyKaigi 2018に行ってきてたいへんたのしいイベントでした。

(型のはなしはやはり興味がないというかまだまだ理解が浅いのでTaPL読むべきだな)

API キーが凍結されてら

mikutter の Twitter API キーが凍結された ようです。

回避方法

とりあず、twitter の 開発者ページ から API キーを生成してください。 あとは以下の様に編集してください

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
diff --git a/Gemfile b/Gemfile
index 0a88a191..cb48cd4f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,6 +21,7 @@ group :default do
   gem 'pluggaloid', '>= 1.1.1', '< 2.0'
   gem 'delayer-deferred', '>= 2.0', '< 3.0'
   gem 'twitter-text', '>= 2.1.0'
+  gem 'dotenv'
 end

 group :test do
diff --git a/core/config.rb b/core/config.rb
index 6e3d95ba..455e3083 100644
--- a/core/config.rb
+++ b/core/config.rb
@@ -14,8 +14,8 @@ module CHIConfig
   ACRO = "mikutter"

   # 下の2行は馬鹿にしか見えない
-  TWITTER_CONSUMER_KEY = "AmDS1hCCXWstbss5624kVw"
-  TWITTER_CONSUMER_SECRET = "KOPOooopg9Scu7gJUBHBWjwkXz9xgPJxnhnhO55VQ"
+  TWITTER_CONSUMER_KEY = ENV["TWITTER_CONSUMER_KEY"]
+  TWITTER_CONSUMER_SECRET = ENV["TWITTER_SECRET_KEY"]
   TWITTER_AUTHENTICATE_REVISION = 1

   # pidファイル
diff --git a/mikutter.rb b/mikutter.rb
index da302ca1..94025817 100755
--- a/mikutter.rb
+++ b/mikutter.rb
@@ -32,6 +32,8 @@ require 'benchmark'
 require 'webrick'
 require 'thread'
 require 'fileutils'
+require 'dotenv'
+Dotenv.load(".env")

 require_relative 'core/utils'

あとは環境変数TWITTER_CONSUMER_KEYTWITTER_SECRET_KEY を設定してあげると起動できるようになります

dotenv

環境変数を指定して毎回起動したくない、とか .bashrc に書きたくない、とかある場合は dotenv.gem を使うことで良いかんじに読みとってくれるようになります。 つかいかたは Dotenv.load(file_name)file_name にあるファイルのなかに環境変数を書けばよいです。

おわり

おわり。 別に dotenv.gem いらんなこれ

1日はやくはじめた

2018/4/27 に休み入れて一日はやくGWはじめています。

毎月恒例

毎月月末金曜に知り合いの @n_kane と築地で朝御飯を食べにいってて 今月はGW直前だし朝からやってる 根津 鷹匠 で朝からそばもいこうとなっていってきた。 そういうわけで休みを入れていた。

国税局

前日あたりに instagramginzabrewery を眺めててどこだろと ggrks してみたら ここ の便利ページが出てきた。このページはだいたいブリュワリーとパブが一体化した 店が掲載されているよう。でよく行くCampion Ale や最近教えてもらった 十条すいけんブルワリー、 などが掲載されている。

ガハハハ

で午後は @n_kane も予定があったらしく、暇だったので上記国税局の便利ページを参考に ガハハビール に行ってみた。

流石に平日の午後3時だったので店員さん以外は誰もおらず、一人だけでした。 とりあえず Danchi IPA とレバーを頼んでみた。

Danchi IPA は苦味がすくなく美味しいビールでした。

つぎに Marcy IPA とライスなしカツカレーを注文。 こちらは苦味が強くおいしいビールでした。

そんなことより、謎のメニューが幾つかあり、店員さんに聞いてみたら、「だってお酒のむ人お米とか麺とかあんまりすきじゃないですよね」っていっておりました。 あと、軟骨バッファローチキンは頼んでないけどとてもよさそうだったので今度いく時に注文しよう。

おわり

国税局を調べたおかげで良いビール屋を見付けることができてよかったよかった。

ってガハハビールの情報探してたら老舗クラフトビールの紹介サイトの ビアクルーズ さんで 紹介されてた

NLP100 Knock Section IV

NLP100本ノック第4節おわりましたのでまとめます

第4章 形態素解析

形態素解析やからむずいやろとおもってた

準備

この章では、形態素解析済ファイルを作成する必要がありますが、毎回対象ファイルをダウンロード、解析して解いています。 ここではいつもどおり HashMap を利用するため ANALYZED_MECAB_KEYS を作成してこれをキーにします。 またよく利用する品詞を enum で定義しておき、変換関数 inspect を作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ANALYZED_MECAB_KEYS: [&str; 9] = ["pos", "pos1", "pos2", "pos3", "a", "b", "base", "read", "speech"];
enum PartOfSpeech {
    VERB,
    NOUN,
    PARTICLE,
}

use PartOfSpeech::*;

fn inspect(val: PartOfSpeech) -> String {
    match val {
        VERB => "動詞",
        NOUN => "名詞",
        PARTICLE => "助詞",
    }.to_string()
}

30. 形態素解析結果の読み込み

Map を使えと指定があるので素直に利用します

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fn feature(node: &Node) -> HashMap<String, String> {
    let mut h: HashMap<String, String> = HashMap::new();
    let surface: String = (&(node.surface)[..node.length as usize]).to_string();
    h.insert("surface".to_string(), surface);
    let values: Vec<String> = node.feature.split(",").map(|m| m.to_string()).collect();
    for (a, b) in ANALYZED_MECAB_KEYS.iter().zip(values.iter()) {
        h.insert(a.to_string(), b.to_string());
    }
    h
}

fn main() {
    let url = "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/neko.txt".to_string();
    let neco: Vec<String> = NLP100::get(url).split("\n").filter(|f| f.ne(&"")).map(|m| m.to_string()).collect();
    let mut morph = Vec::new();

    for line in neco {
        let mut tagger: Tagger = mecab::Tagger::new("");
        let nodes: Node = tagger.parse_to_node(line);

        let mut mecabu: Vec<HashMap<String, String>> = Vec::new();
        for node in nodes.iter_next() {
            match node.stat as i32 {
                mecab::MECAB_BOS_NODE => (),
                mecab::MECAB_EOS_NODE => (),
                _ => {
                    mecabu.push(feature(&node));
                }
            }
        }
        morph.push(mecabu);
    }
    for morph in morphs {
        for mecab in morph {
            println!("{}", format!("surface: {}, base: {}, pos: {}, pos1: {}", mecab["surface"], mecab["base"], mecab["pos"], mecab["pos1"]));
        }
        println!("");
    }
}

31. 動詞

動詞だけ抽出するので以下のコードで抽出し、表層形("surface")を取得する

1
2
3
fn verb(nodes: Vec<HashMap<String, String>>) -> Vec<HashMap<String, String>> {
    nodes.iter().filter(|m| m["pos"] == inspect(VERB)).map(|hm| hm.clone()).collect()
}

32. 動詞の原形

動詞だけ抽出するので上記のコードで抽出し、原形("base")を取得する

33. サ変名詞

サ変接続を抽出

1
2
3
fn sa_noun(nodes: Vec<HashMap<String, String>>) -> Vec<HashMap<String, String>>{
    noun(nodes).iter().filter(|node| node["pos1"] == "サ変接続").map(|hm| hm.clone()).collect()
}

34. 「AのB」

「の」を挾んでいる名詞を抽出

1
2
3
4
5
6
7
8
9
10
11
fn between_noun(node: &Node) -> Option<String> {
    let mecab = feature(node);
    if mecab["surface"] == "の" && mecab["pos"] == inspect(PARTICLE) && mecab["pos1"] == "連体化" {
        let prev = feature(&node.prev().unwrap());
        let next = feature(&node.next().unwrap());

        Some(format!("{}{}{}", &prev["surface"], &mecab["surface"], &next["surface"]))
    } else {
        None
    }
}

35. 名詞の連接

連続した名詞を抽出するが、mecab でうまく関数化できなかったので割愛(あとでうかんだら追記)します。

36. 単語の出現頻度

単純に頻度をまとめ、 sort するとよい

1
2
3
4
5
6
7
8
fn word_histgram(nodes: Vec<HashMap<String, String>>) -> HashMap<String, u64> {
    let mut results: HashMap<String, u64> = HashMap::new();
    for node in nodes {
        let base = &node["base"];
        *results.entry(base.to_string()).or_insert(0) += 1;
    }
    results
}

37. 頻度上位10語

上記の結果より .take(10) するだけです。

38. ヒストグラム

37 と違いがわからずおわり。

39. Zipfの法則

単純に両対数グラフ化でおわり。

おわり

おわり

NLP 100 Knock Section 3

正規表現苦手なようでだいぶ時間がかかりました。

第3章 正規表現

正規表現ときいて楽勝やろと思ってた時期もあったんですが・・・

準備

この章では、gzファイルに入ったJSONをパースする必要があるのでさきにgzファイルから読み込むようにします。

1
2
3
4
5
6
fn read_gzip(path: String) -> Vec<String> {
    let mut file = File::open(path).unwrap();
    let mut string = String::new();
    Decoder::new(&mut file).unwrap().read_to_string(&mut string).unwrap();
    string.split("\n").filter(|m| m.ne(&"")).map(|m| m.to_string()).collect::<Vec<String>>()
}

20. JSONデータの読み込み

こいつは、serdeserde_json を利用して from_str で読み込むだけです。

1
2
3
4
let v: Value = match serde_json::from_str(&l.as_str()) {
     Ok(v) => v,
     Err(_) => { continue; },
};

21. カテゴリ名を含む行を抽出 && 22. カテゴリ名の抽出

早見表 を参考に Regex::captures で抽出することで解決。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

extern crate regex;
extern crate serde_json;

use serde_json::Value;
use regex::Regex;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let keywords = &args[1..];
    let file = File::open(path).unwrap();
    let lines = BufReader::new(file).lines();
    let re = Regex::new(r"\[\[Category:.+\]\]").unwrap();
    let cap = Regex::new(r"\[\[Category:(?P<name>.+?)(?:\|.*)*\]\]").unwrap();

    for l in lines {
        let v: Value = serde_json::from_str(&l.unwrap()).unwrap();

        let title = &v["title"].as_str().unwrap().to_string();
        if keywords.contains(title) {
            for content in v["text"].as_str().unwrap().to_string().split("\n").filter(|m| re.is_match(m)) {
                for c in cap.captures_iter(content) {
                    println!("{}", &c["name"]);
                }
            }
        }
    }
}

23. セクション構造

こちらは前節の問題と同様に解けばよいですが、=の数 - 1 という罠があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

extern crate regex;
extern crate serde_json;

use serde_json::Value;
use regex::Regex;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let keywords = &args[1..];
    let file = File::open(path).unwrap();
    let lines = BufReader::new(file).lines();
    let re = Regex::new(r"(?P<level>==+)(?P<headline>.+?)(?:=+)").unwrap();

    for l in lines {
        let v: Value = serde_json::from_str(&l.unwrap()).unwrap();

        let title = &v["title"].as_str().unwrap().to_string();
        if keywords.contains(title) {
            for content in v["text"].as_str().unwrap().to_string().split("\n").filter(|m| re.is_match(m)) {
                for caps in re.captures_iter(content) {
                    println!("level: {}, headline: {}", &caps["level"].len() - 1, &caps["headline"]);
                }
            }
        }
    }
}

24. ファイル参照の抽出

このあたりは問題ないとおもいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

extern crate regex;
extern crate serde_json;

use serde_json::Value;
use regex::Regex;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let keywords = &args[1..];
    let file = File::open(path).unwrap();
    let lines = BufReader::new(file).lines();
    let re = Regex::new(r"\[\[(File|ファイル):(?P<filename>.+?)(?:\|.*)*(?:\|.*)*\]\]").unwrap();

    for l in lines {
        let v: Value = serde_json::from_str(&l.unwrap()).unwrap();

        let title = &v["title"].as_str().unwrap().to_string();
        if keywords.contains(title) {
            for content in v["text"].as_str().unwrap().to_string().split("\n").filter(|m| re.is_match(m)) {
                for caps in re.captures_iter(content) {
                    println!("filename: {}", &caps["filename"]);
                }
            }
        }
    }
}

25. テンプレートの抽出

こいつは大変でしたので参考サイト1 を参考に以下の正規表現でとりあえず抽出しています。

1
let re = Regex::new(r"(?ms)(?:^\{\{基礎情報.*?$)(?P<dict>.+?)(?:^\}\}$)").unwrap();

26. 強調マークアップの除去

こちらも同様に参考サイト2を参考に以下の正規表現で

1
let strong = Regex::new(r"'{2,5}").unwrap();

27. 内部リンクの除去

こちらも同様に参考サイト3を参考に以下の正規表現で

1
let link = Regex::new(r"(?:\[{1,2})(?P<link>.+?)(?:\|.+)?(?:\]{1,2})").unwrap();

28. MediaWikiマークアップの除去

どうよう4 (ry

1
2
3
let link = Regex::new(r"(?:\[{1,2})(?P<link>.+?)(?:\|.+)?(?:\]{1,2})").unwrap();
let lang = Regex::new(r"(?:\{\{lang\|.+?\|)(?P<lang>.+?)(?:\}\})").unwrap();
let markup = Regex::new(r"</*.+?>").unwrap();

29. 国旗画像のURLを取得する

こちらもさんこおおう5にしてますが、主にURL部分だけです。 こちらは Hyper を利用することで解決しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate libflate;
extern crate regex;
extern crate serde_json;
extern crate tokio_core;

use futures::{Future, Stream};
use hyper::Client;
use hyper_tls::HttpsConnector;
use libflate::gzip::Decoder;
use regex::Regex;
use serde_json::Value;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::Read;
use tokio_core::reactor::Core;

fn get_image(query: String) -> String {
    let re = Regex::new(r" ").unwrap();
    let uri = format!("https://en.wikipedia.org/w/api.php?action=query&titles=File:{}&format=json&prop=imageinfo&iiprop=url", re.replace_all(&query, "%20"));
    let uri = uri.parse().unwrap();
    let mut core = Core::new().unwrap();
    let handle = core.handle();
    let client = Client::configure()
        .connector(HttpsConnector::new(4, &handle).unwrap())
        .build(&handle);
    let work = client.get(uri).and_then(|res| {
        res.body().concat2().and_then(move |body| {
            let v: Value = serde_json::from_slice(&body).unwrap();
            let page_ids = v["query"]["pages"].as_object().unwrap().keys().map(|m| m.to_string()).collect::<Vec<String>>();
            let imageinfo = &v["query"]["pages"][&page_ids[0]]["imageinfo"];
            let r = format!("{}", imageinfo[0]["url"].as_str().unwrap());
            Ok(r)
        })
    });
    core.run(work).unwrap()
}

fn read_gzip(path: String) -> Vec<String> {
    let mut file = File::open(path).unwrap();
    let mut string = String::new();
    Decoder::new(&mut file).unwrap().read_to_string(&mut string).unwrap();
    string.split("\n").filter(|m| m.ne(&"")).map(|m| m.to_string()).collect::<Vec<String>>()
}

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let keywords = &args[1..];
    let lines = read_gzip(path.to_string());
    let re = Regex::new(r"(?ms)(?:^\{\{基礎情報.*?$)(?P<dict>.+?)(?:^\}\}$)").unwrap();
    let bar_hat = Regex::new(r"(?ms)(?:^\|)").unwrap();
    let dict = Regex::new(r"(?P<key>.+?)\s*=\s*(?P<val>.+)").unwrap();
    let media = Regex::new(r"\[\[(File|ファイル):(?P<filename>.+?)(?:\|.*)*(?:\|.*)*\]\]").unwrap();
    let strong = Regex::new(r"'{2,5}").unwrap();
    let link = Regex::new(r"(?:\[{1,2})(?P<link>.+?)(?:\|.+)?(?:\]{1,2})").unwrap();
    let lang = Regex::new(r"(?:\{\{lang\|.+?\|)(?P<lang>.+?)(?:\}\})").unwrap();
    let markup = Regex::new(r"</*.+?>").unwrap();

    for l in lines {
        let v: Value = match serde_json::from_str(&l.as_str()) {
            Ok(v) => v,
            Err(_) => { continue; },
        };
        let text: String = match v["text"].as_str() {
            Some(x) => x.to_string(),
            None => { continue; },
        };
        let title: String = match v["title"].as_str() {
            Some(x) => x.to_string(),
            None => { continue; },
        };

        if keywords.contains(&title) {
            let mut results: HashMap<String, String> = HashMap::new();
            let re: String = match re.captures(&text) {
                Some(caps) => caps["dict"].to_string(),
                None => {
                    println!("cannot capture dict!!");
                    continue;
                },
            };
            for line in bar_hat.split(&re).filter(|f| f.ne(&"")).map(|m| m.to_string()) {
                let line = line.replace("\n", "");
                let dict = match dict.captures(&line) {
                    Some(x) => x,
                    None => {continue;},
                };

                let val = dict["val"].to_string();
                let key = dict["key"].to_string();
                let file: String = media.replace_all(&val, "$filename").trim().to_string();
                let strong: String = strong.replace_all(&file, "").trim().to_string();
                let link: String = link.replace_all(&strong, "$link").trim().to_string();
                let markup: String = markup.replace_all(&link, "").trim().to_string();
                let val: String = match key.as_ref() {
                    "国旗画像" => get_image(markup.to_string()),
                    _ => lang.replace_all(&markup, "$lang").trim().to_string(),
                };
                results.insert(key, val);
            }
            for (k, v) in results {
                println!("{}: {}", k, v);
            }
        }
    }
}

おわり

とりあえず3章まで終わらせましたので形態素解析へと進みたいとおもいます(思っているだけ)

参考資料


  1. 素人の言語処理100本ノック:25, segavvy, https://qiita.com/segavvy/items/e402ad0a5b0f52453d7f

  2. 素人の言語処理100本ノック:26, segavvy, https://qiita.com/segavvy/items/f6d0f3d6eee5acc33c58

  3. 素人の言語処理100本ノック:27, segavvy, https://qiita.com/segavvy/items/9a8137f045852bc299d6

  4. 素人の言語処理100本ノック:28, segavvy, https://qiita.com/segavvy/items/8c4567ec1124320d3354

  5. 素人の言語処理100本ノック:29, segavvy, https://qiita.com/segavvy/items/fc7257012d8a590185e5

NLP100ノック第2章

第2章を一通り終えたので書きます。

10. 行数のカウント

タイトルのままです。 Rust では std::str::Linescount() がありますので利用しておわりです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let mut args = env::args();

    args.next();
    for path in args {
        let f = File::open(path).unwrap();
        let br = BufReader::new(f);

        println!("{}", br.lines().count());
    }
}

11. タブをスペースに置換

コチラも、std::string::String.replace() 利用するだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let mut args = env::args();
    args.next();
    for path in args {
        let file = File::open(path).unwrap();
        let reader = BufReader::new(file);

        for line in reader.lines() {
            println!("{}", line.unwrap().replace("\t", " "));
        }
    }
}

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

こちらは単純にファイルに書き込むのと、分割ができれば問題ないです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::env;
use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::io::prelude::*;

fn main() {
    let mut args = env::args();

    args.next();
    for path in args {
        let f = path.clone().replace(".txt", "");
        let s = path.clone().replace(".txt", "");
        let file = File::open(path).unwrap();
        let br = BufReader::new(file);
        let mut first_column = BufWriter::new(File::create(format!("{}_col1.txt", f)).unwrap());
        let mut second_column = BufWriter::new(File::create(format!("{}_col2.txt", s)).unwrap());

        for line in br.lines() {
            let words = line.unwrap().split("\t").map(|m| m.to_string()).collect::<Vec<String>>();
            first_column.write(format!("{}\n", words[0]).as_bytes()).unwrap();
            second_column.write(format!("{}\n", words[1]).as_bytes()).unwrap();
        }
    }
}

13. col1.txtとcol2.txtをマージ

こちらは以前利用した、zip があれば問題ないです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::env;
use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::io::prelude::*;

fn main() {
    let mut args = env::args();
    if args.len() < 2 { panic!("col1.txt col2.txt"); }

    args.next();

    let first = args.next().unwrap();
    let second = args.next().unwrap();

    let fr = BufReader::new(File::open(first).unwrap()).lines().map(|m| m.unwrap().to_string()).collect::<Vec<String>>();

    let sr = BufReader::new(File::open(second).unwrap()).lines().map(|m| m.unwrap().to_string()).collect::<Vec<String>>();
    let mut merge_file =
        BufWriter::new(File::create("merge.txt".to_string()).unwrap());

    for (x, y) in fr.iter().zip(&sr) {
        merge_file.write(format!("{}\t{}\n", x, y).as_bytes()).unwrap();
    }

}

14. 先頭からN行を出力

head コマンドですので std::iter::Iterator.take() を利用するだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let args = env::args().skip(1).collect::<Vec<String>>();
    let file = File::open(&args[0]).unwrap();

    let br = BufReader::new(file).lines().take((&args[1]).to_string().parse::<usize>().unwrap());

    for line in br.map(|m| m.unwrap().to_string()).collect::<Vec<String>>() {
        println!("{}", line);
    }
}

15. 末尾のN行を出力

tail コマンドです。こちらは std::iter::Iterator.skip() を利用してやるだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let args = env::args().skip(1).collect::<Vec<String>>();
    let file = File::open(&args[0]).unwrap();
    let takes = (&args[1]).to_string().parse::<usize>().unwrap();
    let br = BufReader::new(&file).lines();
    let skips = br.count() - takes;
    let file = File::open(&args[0]).unwrap();
    let lines = BufReader::new(&file).lines().skip(skips);

    for line in lines {
        println!("{}", line.unwrap());
    }
}

16. ファイルをN分割する

こちらの実装は素朴な実装とし、行数で分割しております。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::prelude::*;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let file = File::open(path).unwrap();
    let count: usize = BufReader::new(file).lines().count();
    let div: usize = (&args[1]).to_string().parse().unwrap();
    let file = File::open(path).unwrap();
    let mut br = BufReader::new(file).lines();

    let t = (count as f64/ div as f64).ceil() as usize;

    for x in 1 .. div + 1 {
        let l = br.by_ref();
        let file = File::create(format!("{}.txt",x)).unwrap();
        let mut bw = BufWriter::new(file);
        for y in l.take(t).map(|m| m.unwrap().to_string()).collect::<Vec<String>>() {
            let a = format!("{}\n", y);
            bw.write(a.as_bytes()).unwrap();
            println!("{}: {}", x, y);
        }
    }
}

17. 1列目の文字列の異なり

ファイル読込 + HashSet で実装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

extern crate regex;
use regex::Regex;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let file = File::open(path).unwrap();
    let re = Regex::new(r"\W+").unwrap();
    let hs = BufReader::new(file).lines().map(|m|{
        let l = m.unwrap().clone();
        re.split(&l).next().unwrap().to_string()
    }).collect::<HashSet<_>>();

    for s in hs {
        println!("{}", s);
    }

}

18. 各行を3コラム目の数値の降順にソート

こちら、実数の比較を行う必要があり、すこし めんどう でした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = &args[0].to_string();
    let file = File::open(path).unwrap();
    let mut val = BufReader::new(file).lines().map(|m| m.unwrap().split("\t").skip(2).next().unwrap().parse::<f64>().unwrap()).collect::<Vec<f64>>();

    val.sort_by(|a, b| a.partial_cmp(b).unwrap());
    for v in val { println!("{}", v); }
}

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

18の問題を更にカウントできるように変更した。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

enum Value {
    USIZE(usize),
    NONE(()),
}

fn main() {
    let args = env::args().skip(1).collect::<Vec<String>>();
    let file = File::open(&args[0]).unwrap();
    let mut words: HashMap<String, usize> = HashMap::new();
    for m in BufReader::new(&file).lines() {
        let w = m.unwrap().to_string().split("\t").next().unwrap().to_string();
        let v = match words.get(&w) {
            None => 1,
            Some(n) => n + 1,
        };
        words.insert(w, v);
    }

    let mut vars: Vec<(&String, &usize)> = words.iter().collect();
    vars.sort_by(|a, b| b.1.cmp(a.1));
    for (w, v) in vars {println!("{}: {}", w, v);}

}

おわり

この章は慣れてきたのか比較的楽に解けています。

Nlp 100 Section 1 Part 3

前回、言語処理100本ノック の04までやったので05からやります。

05. ngram

こいつはbi-gramを単語、文字二つを実装するひつようがあります

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn bigram(words: Vec<String>) -> Vec<String> {
    let mut bi: Vec<String> = Vec::new();
    let mut i = 0;

    loop {
        let w = i + 2;
        if w > words.len() { break; }
        bi.push(words[i..w].join(""));
        i += 1;
    }
    bi
}

fn main() {
    let words = "I am an NLPer".split(' ').map(|m| m.to_string()).collect::<Vec<String>>();
    println!("\n===word bi-gram");
    for word in bigram(words) {
        println!("{}", word);
    }

    let words = "I am an NLPer".chars().map(|m| m.to_string()).collect::<Vec<String>>();
    for word in bigram(words) {
        println!("\"{}\"", word);
    }

}

06. 集合

これは単純に HashSet を利用して、解決します。HashSet の差集合は difference を利用し、和集合は union を、積集合は intersection をそれぞれ利用します。また、特定の要素が含有していることを判定するには contains を利用して判定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use std::collections::HashSet;

fn bigram(words: Vec<String>) -> HashSet<String> {
    let mut bi: HashSet<String> = HashSet::new();
    let mut i = 0;

    loop {
        let w = i + 2;
        if w > words.len() { break; }
        bi.insert(words[i..w].join(""));
        i += 1;
    }
    bi
}

fn chars(s: String) -> Vec<String> {
    s.chars().map(|m| m.to_string()).collect::<Vec<String>>()
}

fn main() {
    let s1 = bigram(chars("paraparaparadise".to_string()));
    let s2 = bigram(chars("paragraph".to_string()));

    println!("===UNION===");
    for x in s1.union(&s2) {
        println!("{}", x);
    }

    println!("\n===DIFF===");
    println!("===s1 - s2===");
    for x in s1.difference(&s2) {
        println!("{}", x);
    }
    println!("===s2 - s1===");
    for x in s2.difference(&s1) {
        println!("{}", x);
    }

    println!("\n===intersection===");
    for x in s1.intersection(&s2) {
        println!("{}", x);
    }

    println!("\n===INCLUDE===");
    let se = "se";
    println!("s1: {}", s1.contains(se));
    println!("s2: {}", s2.contains(se));
}

07. テンプレートによる文生成

これは format! を使えば終りです。(問題意図ほんとこれなんか?)

1
2
3
4
5
6
7
8
fn string_template(x: i8, y: &str, z: f32) -> String {
    format!("{}時の{}は{}", x, y, z)
}

fn main() {
    let string = string_template(12, "気温", 22.5);
    println!("{}", string);
}

08. 暗号文

ASCII以外の判定と、小文字のASCIIが判れば簡単です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::ascii::AsciiExt;

fn cipher(src: &str) -> String {
    let chars = src.chars().collect::<Vec<char>>();
    let mut result: String = String::new();
    for c in chars {
        let s = if c.is_ascii() {
            let var: u8 = c as u8;
            match var {
                97 ... 122 => (219 - (var)) as char,
                _ => c,
            }
        } else {
            c
        };
        result.push(s);
    }
    result
}

fn main() {
    println!("{}", cipher("Today is fine."));
    println!("{}", cipher(&cipher("Today is fine.")));
}

09. Typoglycemia

こちらは、 Vecshuffle 的なものがないので、rand を呼び出して shuffle を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
extern crate rand;
use rand::Rng;

fn words(src: &str) -> Vec<String> {
    let mut result: Vec<String> = Vec::new();

    for s in src.split(' ').collect::<Vec<&str>>() {
        let mut chars = s.chars().collect::<Vec<char>>();
        if chars.len() < 5 {
            result.push(s.to_string());
        } else {
            let last_index = chars.len() - 1;
            let first_char = chars[0];
            let last_char = chars[last_index];

            let rand_chars = &mut chars[1..last_index];
            shuffle(rand_chars);
            let mut rand_string = String::new();
            for c in rand_chars { rand_string.push(*c) }
            result.push(format!("{}{}{}", first_char, rand_string, last_char));
        }
    }
    result
}

fn shuffle(chars: &mut [char]) {
    rand::thread_rng().shuffle(chars);
}

fn main() {
    let paragraph = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .";

    for w in words(paragraph) {
        println!("{}", w);
    }
}

おわり

ということで Rust で言語処理100本ノック1章をやってみました。 最近 Ruby しか書いていなかったので、新鮮で楽しいですね Rust

NLP100本ノック Section 1 Part 2

前回、言語処理100本ノック の01までやったので02からやっていきます。

extern crate nlp100;

ってやれるように Cargo を作成

1
cargo new nlp100

02. 「パトカー」+「タクシー」=「パタトクカシーー」

これはムズカシイので素直に zip を利用する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn concat(t: (Vec<char>, Vec<char>)) {
    let (f, s) = t;
    let mut r = String::new();

    for (x, y) in f.iter().zip(s.iter()) {
        r.push_str(&format!("{}{}", x, y));
    }
    println!("{}", r);
}

fn main() {
    let p: Vec<char> = String::from("パトカー").chars().collect();
    let t: Vec<char> = String::from("タクシー").chars().collect();
    let f = (p, t);
    concat(f);
}

03. 円周率

この問題は思い切り勘違いしてたので、「これのどこが円周率なの?」って思ってました。こいつは Regex を用いて単語毎に分解、単語毎に文字数数えて解決してます。

1
2
3
4
5
6
7
8
fn char_count_list(w: &str) -> Vec<usize> {
    Regex::new(r"\W+").unwrap().split(w).map(|m| m.len()).collect()
}

fn main() {
    let pi = char_count_list("Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.");
    println!("{:?}", pi);
}

04. 元素記号

これは、英語版「水兵リーベー僕の船」ですので条件に合うときだけ1文字に変更します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::collections::HashMap;

fn main() {
    let atomic_words: Vec<&str> = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.".split(' ').collect();
    let mut atomic_table = HashMap::new();
    for (i, a) in atomic_words.iter().enumerate() {
        let chars = a.chars().map(|v| v.to_string()).collect::<Vec<String>>();
        let r = match i {
            0 | 4...8 | 14...15 | 18 => chars[0].to_string(),
            _ => chars[0..2].join(""),
        };
        atomic_table.insert(r, i + 1);
    }

    for (k, v) in &atomic_table {
        println!("{}: {}", k, v);
    }
}

おわり

やっぱり難しいのを実感

Happy New Year and New Language

いまさらですが、あけましておめでとうございます。

happy new language

年開けから新しくプログラミング言語(Rust)始めました。(ってわけでもない)

雑に rust book 2nd edition を一通り読み終えたので、 言語処理100本ノック をやりはじめました。 とりあえず第1章が終ったので、メモとしてのこします。 基本的に Rust (に限らず)でやりたいことがとくにないので ちょうどよさそうな勉強 として自然言語処理を選択しています。

躓いたところ

躓いていないところがないです。 まあやっていくうちに以下二つは常に書いておくと楽になるかなと。

1
2
3
4
5
6
7
8
9
// 1文字毎
fn chars(string: &str) -> Vec<char> {
    string.chars().collect::<Vec<char>>()
}

// 1単語毎
fn words(sentence: &str) -> Vec<&str> {
    sentence.split(' ').collect::<Vec<&str>>()
}

00. 文字列の逆順

これは簡単で(でもなかった)、1文字ずつ分解して反対化したあと String にします。

1
2
3
fn reverse_string(string: &str) {
    println!("{}", string.chars().rev().collect::<String>());
}

01. 「パタトクカシーー」

これも簡単で、1文字ずつ分解して抜き出します。(絶対違う)

1
2
3
4
fn main() {
    let c = "パタトクカシーー".chars().collect::<Vec<char>>();
    println!("{}{}{}{}", c[0], c[2], c[4], c[6]);
}

おわり

とりあえず2問解いてみたけど、自然言語処理 && Rust 難しい。