PowerShellで言語処理100本ノック 第1章～第3章

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)

## 第1章: 準備運動

### 00. 文字列の逆順
文字列 ***"stressed"*** の文字を逆に（末尾から先頭に向かって）並べた文字列を得よ．

```powershell
$s = "stressed"
-join $s[$s.length..0]
```
### 01. 「パタトクカシーー」
「***パタトクカシーー***」という文字列の**1,3,5,7**文字目を取り出して連結した文字列を得よ．

```powershell
-join "パタトクカシーー"[0,2,4,6]
```

### 02. 「パトカー」＋「タクシー」＝「パタトクカシーー」
「***パトカー***」＋「***タクシー***」の文字を先頭から交互に連結して文字列「***パタトクカシーー***」を得よ．

```powershell
$enmr = "パトカー","タクシー" | foreach { ,$_.getenumerator() }
-join $(while(!($enmr.movenext() -eq 0).count) { $enmr.current })
```

### 03. 円周率
***"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."*** という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ．

```powershell
$s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
$s -split "[., ]+" | foreach length
```

### 04. 元素記号
***"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."*** という文を単語に分解し，**1, 5, 6, 7, 8, 9, 15, 16, 19**番目の単語は先頭の**1**文字，それ以外の単語は先頭の**2**文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ．

```powershell
$s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
$s.split() | foreach { $i = 1; $h = [ordered]@{} } { $h[$_.substring(0, ($i++ -notin 1,5,6,7,8,9,15,16,19) + 1)] = $i } { $h }
```

### 05. n-gram
与えられたシーケンス（文字列やリストなど）から***n-gram***を作る関数を作成せよ．この関数を用い，***"I am an NLPer"***という文から単語bi-gram，文字bi-gramを得よ．

```powershell
function ngram($p, $n) {
    $spr = if ($p -is "string") { "" } else { " " }
    0..([math]::max($p.length - $n, 0)) | foreach { $p[$_..($_ + $n - 1)] -join $spr } | select -u
}

$s = "I am an NLPer"
ngram $s 2
ngram $s.split() 2
```

### 06. 集合
***"paraparaparadise"*** と ***"paragraph"*** に含まれる文字bi-gramの集合を，それぞれ, ***X***と***Y***として求め，*X*と*Y*の和集合，積集合，差集合を求めよ．さらに，***'se'*** というbi-gramが*X*および*Y*に含まれるかどうかを調べよ．

```powershell
$x = ngram "paraparaparadise" 2
$y = ngram "paragraph" 2

"union"
$x + $y | select -u
"intersection"
$x | where { $_ -in $y }
"difference (x-y)"
$x | where { $_ -notin $y }

gv x,y | foreach { "{0} contains se:{1}" -f $_.name, ("se" -in $_.value) }
```

### 07. テンプレートによる文生成
引数 ***x, y, z*** を受け取り「***x 時の y は z***」という文字列を返す関数を実装せよ．さらに，***x=12, y="気温", z=22.4*** として，実行結果を確認せよ．

```powershell
function getstr($x, $y, $z) { "${x}時の${y}は${z}" }

getstr -x:12 -y:気温 -z:22.4
```

### 08. 暗号文
与えられた文字列の各文字を，以下の仕様で変換する関数 ***cipher*** を実装せよ．

* 英小文字ならば( ***219 - 文字コード*** )の文字に置換
* その他の文字はそのまま出力

この関数を用い，英語のメッセージを暗号化・復号化せよ．

```powershell
filter cipher { [regex]::replace($_, "[a-z]", { 219 - $args.value[0] -as "char" }) }

"Hello World!" | cipher
"Hello World!" | cipher | cipher
```

### 09. Typoglycemia
スペースで区切られた単語列に対して，各単語の先頭と末尾の文字は残し，それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ．ただし，長さが**４**以下の単語は並び替えないこととする．適当な英語の文（例えば***"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."***）を与え，その実行結果を確認せよ．

```powershell
$s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
[regex]::replace($s, "(?<=\b\S)\S{3,}(?=\S\b)", { -join ($args.value -as "char[]" | sort { random }) })                #
```
 




## 第2章: Linuxコマンド

***hightemp.txt*** は，日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである．以下の処理を行うプログラムを作成し，*hightemp.txt* を入力ファイルとして実行せよ．さらに，同様の処理をUNIXコマンドでも実行し，プログラムの実行結果を確認せよ．

### エンコード変換
毎回エンコード指定するのが面倒なのでUTF16LEに変換しておく。

```powershell
(gc hightemp.txt -enc utf8) | sc hightemp.txt
```

### 10. 行数のカウント
行数をカウントせよ．

```powershell
(gc hightemp.txt).count
```

### 11. タブをスペースに置換
タブ1文字につきスペース1文字に置換せよ．

```powershell
(gc hightemp.txt).replace("`t", " ")
```

### 12. 1列目をcol1.txtに，2列目をcol2.txtに保存
各行の1列目だけを抜き出したものを ***col1.txt*** に，2列目だけを抜き出したものを ***col2.txt*** としてファイルに保存せよ．

```powershell
gc hightemp.txt | cfs | foreach { $_.p1 >> col1.txt; $_.p2 >> col2.txt }
```

### 13. col1.txtとcol2.txtをマージ
12で作った ***col1.txt*** と ***col2.txt*** を結合し，元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ．

```powershell
gc col1.txt,col2.txt | group readcount | foreach { $_.group -join "`t" } > merged.txt
```

### 14. 先頭からN行を出力
自然数 ***N*** をコマンドライン引数などの手段で受け取り，入力のうち先頭の ***N*** 行だけを表示せよ．

```powershell
$n = 3
gc hightemp.txt -head $n
```

### 15. 末尾のN行を出力
自然数 ***N*** をコマンドライン引数などの手段で受け取り，入力のうち末尾の ***N*** 行だけを表示せよ．

```powershell
gc hightemp.txt -tail $n
```

### 16. ファイルをN分割する
自然数 ***N*** をコマンドライン引数などの手段で受け取り，入力のファイルを行単位で ***N*** 分割せよ．

```powershell
function fsplit($path, $n) {
    $c = (gc $path | measure).count
    $rc = [math]::floor($c / $n)
    $head = $c % $n * ($rc + 1)
    @{ r = $rc + 1; head = $head; }, @{ r = $rc; tail = $c - $head } |
    foreach { $i = 1 } { gc $path @_ | foreach { $_ > ("part{0}.txt" -f $i++) } }
}

fsplit hightemp.txt $n
```

### 17. １列目の文字列の異なり
1列目の文字列の種類（異なる文字列の集合）を求めよ．

```powershell
gc hightemp.txt | cfs | select p1 -u
```

### 18. 各行を3コラム目の数値の降順にソート
各行を3コラム目の数値の逆順で整列せよ（注意: 各行の内容は変更せずに並び替えよ）．

```powershell
gc hightemp.txt | sort { $_.split()[2] -as "double" } -d
```

### 19. 各行の1コラム目の文字列の出現頻度を求め，出現頻度の高い順に並べる
各行の1列目の文字列の出現頻度を求め，その高い順に並べて表示せよ．

```powershell
gc hightemp.txt | cfs | group p1 | sort count -d
```


## 第3章: 正規表現

*Wikipedia*の記事を以下のフォーマットで書き出したファイル ***jawiki-country.json.gz*** がある．

1行に1記事の情報がJSON形式で格納される
各行には記事名が ***"title"*** キーに，記事本文が ***"text"*** キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ．

### 20. JSONデータの読み込み
*Wikipedia*記事のJSONファイルを読み込み，「**イギリス**」に関する記事本文を表示せよ．問題21-29では，ここで抽出した記事本文に対して実行せよ

```powershell
$s = gc jawiki-country.json -enc utf8 | ConvertFrom-Json | where title -eq "イギリス" | foreach text
```

### 21. カテゴリ名を含む行を抽出
記事中でカテゴリ名を宣言している行を抽出せよ．


```powershell
$s.split("`n") | sls -s "[[Category:"
```

### 22. カテゴリ名の抽出
記事のカテゴリ名を（行単位ではなく名前で）抽出せよ．

```powershell
[regex]::matches($s, "(?<=\[\[Category:).+?(?=\||\])").value
```

### 23. セクション構造
記事中に含まれるセクション名とそのレベル（例えば"== セクション名 =="なら1）を表示せよ．

```powershell
[regex]::matches($s, "=(=+)(.+?)\1=") | foreach { $_.groups[1].length, $_.groups[2] -join ":" }
```

### 24. ファイル参照の抽出
記事から参照されているメディアファイルをすべて抜き出せ．

```powershell
[regex]::matches($s, "(?<=\[\[(ファイル|File):).*?(?=\|)").value
```

### 25. テンプレートの抽出
記事中に含まれる「**基礎情報**」テンプレートのフィールド名と値を抽出し，辞書オブジェクトとして格納せよ．


```powershell
function Get-BasicInfo($text) {
    [regex]::match($text, "((?'o'{)|(?'c-o'})|.)*","s").groups["c"].captures.value |
    where { $_.startswith("基礎情報") } | foreach { $_ -split "\n\||\|\n" } | select -skip 1
}

function ConvertTo-HashTable {
    Begin   { $h = [ordered]@{} }
    Process { $k, $v = $_ -split "=", 2 | foreach trim; $h[$k] = $v }
    End     { $h }
}

Get-BasicInfo $s | ConvertTo-HashTable
```

### 26. 強調マークアップの除去
25の処理時に，テンプレートの値から*MediaWiki*の強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: [マークアップ早見表](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）

```powershell
filter Remove-Emphasis { $_ -replace "'('+)(.+?)\1'", '$2' }

Get-BasicInfo $s | Remove-Emphasis | ConvertTo-HashTable
```

### 27. 内部リンクの除去
26の処理に加えて，テンプレートの値から*MediaWiki*の内部リンクマークアップを除去し，テキストに変換せよ．

```powershell
filter Remove-InternalLink { $_ -replace "(#REDIRECT )?\[\[([^\]]+?\|)*([^\]]+?)\]\]", '$3' }

Get-BasicInfo $s | Remove-Emphasis | Remove-InternalLink | ConvertTo-HashTable
```

### 28. MediaWikiマークアップの除去
27の処理に加えて，テンプレートの値から*MediaWiki*マークアップを可能な限り除去し，国の基本情報を整形せよ．

```powershell
filter Remove-Template { $_ -replace "{{([^}]+?\|)*([^}]+?)}}", '$2' }
filter Remove-HtmlTag { $_ -replace "(?:<([a-z]+)[ >][\s\S]*?</\1>)|(<[a-z] ?[^<>]*?/>)|(<\!--.+?-->)" }

$h = Get-BasicInfo $s | Remove-Emphasis | Remove-Template | Remove-InternalLink | Remove-HtmlTag | ConvertTo-HashTable
```


### 29. 国旗画像のURLを取得する
テンプレートの内容を利用し，国旗画像のURLを取得せよ．（ヒント: MediaWiki APIの ***imageinfo*** を呼び出して，ファイル参照をURLに変換すればよい）

```powershell
irm https://en.wikipedia.org/w/api.php -b @{
    action = "query"
    format = "json"
    prop = "imageinfo"
    iiprop = "url"
    titles = "File:" + $h["国旗画像"]
} | foreach { $_.query.pages.psobject.properties.value.imageinfo.url }
```