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