くーも

2018-09-11 00:13:45 +0900

Rubyで cuda を利用して GPGPU 使いたいの で cumo を導入してみた。

install

はじめに cuda をインストールしておきます。 gem に登録されているので、雑に gem install 実施してみます。

export CUDA_PATH="/opt/cuda"
export CPATH="$CUDA_PATH/include:$CPATH"
export LD_LIBRARY_PATH="$CUDA_PATH/lib64:$CUDA_PATH/lib:$LD_LIBRARY_PATH"
export PATH="$CUDA_PATH/bin:$PATH"
export LIBRARY_PATH="$CUDA_PATH/lib64:$CUDA_PATH/lib:$LIBRARY_PATH"
gem install cumo

とすると以下のようなエラーが出てだめです。

gcc -I. -Iinclude -Inarray -Icuda -I/home/katsyoshi/.rbenv/versions/2.5.1/include/ruby-2.5.0/x86_64-linux -I/home/katsyoshi/.rbenv/versions/2.5.1/include/ruby-2.5.0/ruby/backward -I/home/katsyoshi/.rbenv/versions/2.5.1/include/ruby-2.5.0 -I. -I/home/katsyoshi/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/numo-narray-0.9.1.3/ext/numo/narray -DHAVE_NUMO_NARRAY_H -DHAVE_DLFCN_H -DHAVE_DLOPEN -DHAVE_STDBOOL_H -DHAVE_STDINT_H -DHAVE_TYPE_BOOL -DHAVE_TYPE_U_INT8_T -DHAVE_TYPE_U_INT16_T -DHAVE_TYPE_INT32_T -DHAVE_TYPE_U_INT32_T -DHAVE_TYPE_INT64_T -DHAVE_TYPE_U_INT64_T -DHAVE_EXP10 -DHAVE_RB_CCOMPLEX -DHAVE_RB_THREAD_CALL_WITHOUT_GVL -I/opt/cuda/include -I/home/katsyoshi/.rbenv/versions/2.5.1/include -fPIC -O3 -ggdb3 -Wall -Wextra -Wno-unused-parameter -Wno-parentheses -Wno-long-long -Wno-missing-field-initializers -Wno-tautological-compare -Wno-parentheses-equality -Wno-constant-logical-operand -Wno-self-assign -Wunused-variable -Wimplicit-int -Wpointer-arith -Wwrite-strings -Wdeclaration-after-statement -Wimplicit-function-declaration -Wdeprecated-declarations -Wmisleading-indentation -Wno-packed-bitfield-compat -Wsuggest-attribute=noreturn -Wsuggest-attribute=format -Wduplicated-cond -Wno-maybe-uninitialized -o narray/types/bit.o -c narray/types/bit.c
narray/gen/tmpl/lib.c:22:7: エラー: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘/’ token
 #define m_map(x) m_num_to_data(rb_yield(m_data_to_num(x)))
       ^
narray/gen/tmpl/lib.c:23:29: エラー: 終端する > 文字がありません

                             ^
narray/gen/tmpl/lib.c:23:29: 致命的エラー: static ID id_cast;: そのようなファイルやディレクトリはありません
コンパイルを停止しました。
make: *** [Makefile:453: narray/types/bit.o] エラー 1

公式リポジトリ覗いてもとくに問題なさそうなので gem からのインストールは一旦おいてソースからインストールします。

build

公式リポジトリからダウンロードしてコンパイルをはじめます。

git clone https://github.com/sonotz/cumo.git
cd cumo
export CUDA_PATH="/usr/local/cuda"
export CPATH="$CUDA_PATH/include:$CPATH"
export LD_LIBRARY_PATH="$CUDA_PATH/lib64:$CUDA_PATH/lib:$LD_LIBRARY_PATH"
export PATH="$CUDA_PATH/bin:$PATH"
export LIBRARY_PATH="$CUDA_PATH/lib64:$CUDA_PATH/lib:$LIBRARY_PATH"
bundle install
rake compile
rake install

ってインストールできますが、その前に僕の環境が gcc-8.2.0 だったりして、これを gcc-6.4.0 に戻したりしてインストールしました。

run

これでインストール終わりましたので、 irb で確認してみようと思います。

irb(main):001:0> require 'cumo/narray'
=> true
irb(main):002:0> Cumo::Int32.new(3, 3).fill(0)
=> Cumo::Int32#shape=[3,3]
[[0, 0, 0],
 [0, 0, 0],
 [0, 0, 0]]

とこれで実行されたことを確認できます。

と簡単に書きましたが、インストール後にGPUへのアクセス権が無いユーザーで実行したためエラーが出ます(エビデンス取得忘れ)。 手元のPCではGPUにアクセス権がvideo groupにもあるので利用ユーザーをvideo groupへ追加して再起動することでようやく動作確認ができます。

おわり

簡単にGPGPUを利用したプログラムが書けるようになりましたのでおわりです。 技術書典5はこれを使ったはなしを出展しますのできてみてくださいな。

え37でまってます

SATySFi使って本書いてみてる

2018-09-09 14:29:46 +0900

技術書典5出しますので、それを書くために OCaml で作成された SATySFi を使いはじめました。

インストール

今回は OPAM を利用してインストールをしています。まあ単純にパッケージが今普段使いしてるOSにないだけなんですけどね。 インストール手順は 公式のREADME.md を参考にしています。

OPAM

OCaml Package Manager の略らしいです。 こいつは OCaml のインストールもしてくれるのでこれを利用します。 OPAM のインストールは簡単で以下のスクリプトを実行するだけです。

wget https://raw.github.com/ocaml/opam/master/shell/opam_installer.sh -O - | sh -s /usr/local/bin

OCaml

OPAM のインストールが終ったら、 OCaml をインストールします。

opam switch 4.06.0
eval `opam config env`

SATySFi

OPAM の準備が整ったら SATySFi の準備、インストールをします。

opam repository satysfi-external https://github.com/gfngfn/satysfi-external-repo.git
opam update
git clone https://github.com/gfngfn/SATySFi.git
cd SATiSFi

opam pin add satysfi .
opam install satysfi

compile!!!!

これでインストールされたのでとりあえずリポジトリにある demo/demo.saty をコンパイルしてみましょう。

cd demo
make

なにも準備していないといきなりエラーがでますのでそれを公式を参考にして解消します。 解消できたら demo.pdf ができますのでこれでつかえるようになります。

おわり

ちょっと簡単でしたが、 satysfi の導入日記でした。

そんなことより技術書典5に出します。ネタは酒に関するネタでやっていく予定です。 仕上がっていれば え37 に居るとおもわれます。

wireguardをはじめました

2018-08-17 00:38:12 +0900

title 通り、 wiregaurd で家と さくらのVPS にあるサーバーを繋いでみました

install

導入は簡単で gentoo は公式にあるので

sudo emerge wireguard

だけで、 ubuntu の場合もインストールガイドがあるため簡単にインストールできます

sudo apt install software-properties-common
sudo add-apt-repository ppa:wireguard/wireguard
sudo apt-get update
sudo apt-get install wireguard

引込

あとは非常にかんたんここを適宜読み替えることで 接続できます。

おわり

あとは、再起動時に自動で接続するように変更する必要がありそうですがつながったし、おそいのでこれでおわり

Hello, Itamae from docker!!

2018-07-09 22:54:12 +0900

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 を実施すれば実行できますので、好みで追加しましょう。

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

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

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 とかで公開していませんので、以下に例を

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

2018-06-03 23:44:28 +0900

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 キーが凍結されてら

2018-05-06 23:33:23 +0900

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

回避方法

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

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-04-30 01:02:57 +0900

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

毎月恒例

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

国税局

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

ガハハハ

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

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

-> <-

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

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

-> <-

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

おわり

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

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

NLP100 Knock Section IV

2018-02-26 23:45:52 +0900

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

第4章 形態素解析

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

準備

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

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 を使えと指定があるので素直に利用します

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")を取得する

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. サ変名詞

サ変接続を抽出

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」

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

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 するとよい

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

2018-02-09 23:53:24 +0900

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

第3章 正規表現

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

準備

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

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 で読み込むだけです。

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

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

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

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 という罠があります。

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. ファイル参照の抽出

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

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 を参考に以下の正規表現でとりあえず抽出しています。

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

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

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

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

27. 内部リンクの除去

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

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

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

どうよう4 (ry

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 を利用することで解決しています。

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章

2018-01-27 19:46:23 +0900

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

10. 行数のカウント

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

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() 利用するだけです。

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に保存

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

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 があれば問題ないです。

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() を利用するだけです。

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() を利用してやるだけです。

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分割する

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

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 で実装

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コラム目の数値の降順にソート

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

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の問題を更にカウントできるように変更した。

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);}

}

おわり

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