y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

『言語処理 100 本ノック』に PHP で挑む (問題 00 ~ 04)

東北大学の乾・岡崎研究室のウェブサイトで、『言語処理 100 本ノック』という問題集が公開されています。実践的なプログラミングの課題に取り組みながら自然言語処理の知識を習得できる、全 100 問の問題集です。
www.cl.ecei.tohoku.ac.jp

プログラミング言語Python が想定されていますが、他の言語にも対応しているとのことです。私は PHP が好きなので、PHP でこの問題集に取り組んでみます*1。今回は第一章の問題 00 から問題 04 までを解いてみます。

00. 文字列の逆順

文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

<?php
$input = 'stressed';
$answer = strrev($input);
echo $answer, "\n";

PHP では strrev 関数で文字列を反転できます。echo で結果を表示しているところはカンマで区切っていますが、このあたりの書き方は好みでしょうか。全体をダブルクォートで囲ったり、カンマではなくピリオドで文字列連結としても同じ結果になります。また、改行は PHP_EOL という定数を使えば環境に合わせて "\r\n" になったりしますが、面倒なので直接 "\n" と書いています。

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

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

<?php
$input = 'パタトクカシーー';
$answer = '';
foreach ([1, 3, 5, 7] as $n) {
    $answer .= mb_substr($input, $n - 1, 1);
}
echo $answer, "\n";

PHP には文字列のスライスがないので、いきなり面倒になりました。foreach を使って一文字ずつ取り出して連結します。また、入力文字列が日本語なのでマルチバイト文字列関数を利用する必要があります。

foreach の処理を関数的に書くこともできますが、PHP にはラムダ式の構文がないので、以下のように結構な記述量になってしまいます。あまりすっきりしませんね。

<?php
$answer = implode(array_map(
    function ($n) use ($input) { return mb_substr($input, $n - 1, 1); },
    [1, 3, 5, 7]));
02. 「パトカー」+「タクシー」=「パタトクカシーー」

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

<?php
$input1 = 'パトカー';
$input2 = 'タクシー';
$answer = '';
for ($i = 0; $i < mb_strlen($input1); ++$i) {
    $answer .= mb_substr($input1, $i, 1);
    $answer .= mb_substr($input2, $i, 1);
}
echo $answer, "\n";

この問題も、特に工夫することなく foreach で文字列を連結します。入力文字列の長さが異なる場合などの対応も特にしていません。

03. 円周率

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

<?php
$sentence= 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.';
$words = str_word_count($sentence, 1);
$answer = array_map('strlen', $words);
echo implode(',', $answer), "\n";

str_word_count 関数を使って入力文を単語に分解します。この関数は第二引数の値によって戻り値の内容が変わり、1 を指定すると第一引数の文字列を単語に分解した配列を戻します。問題の入力文にはカンマやピリオドが含まれますが、この関数はアルファベット以外の文字を除去してくれます。詳細はリファレンスマニュアルを参照してください。
PHP: str_word_count - Manual

各単語の文字数を数える処理には array_map を利用しました。問題 01 で述べたような無名関数の場合とは異なり、定義済みの関数を利用する場合は関数名を文字列として渡すだけでよく、簡単に書けます。

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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

<?php
$sentence = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.';
$oneLetterIndices = [1, 5, 6, 7, 8, 9, 15, 16, 19];
$words = str_word_count($sentence, 1);
$answer = [];
for ($i = 0; $i < count($words); ++$i) {
    $length = in_array($i + 1, $oneLetterIndices) ? 1 : 2;
    $key = substr($words[$i], 0, $length);
    $answer[$key] = $i + 1;
}
echo json_encode($answer, JSON_PRETTY_PRINT), "\n";

問題 03 と同様に str_word_count で入力文を単語に分解した後、題意の連想配列を作成します。

*1:この記事を書いている時点で全 100 問を終えたわけではありません。いつになるか分かりませんが、できるところまで少しずつ進めてみるつもりです。