プロ炎 課題Aについての個人的考察と記録

はじめに

某大学ではrubyというプログラミング言語を教えている訳だが、その授業がなんとも教育としては悲しく(注.学問ではない)なんとも初学者に向けたモノとしては不適切であったため、どの点を改善すれば良いのか検討していくための記録としてこれを残すこととした。ソースコードをそのまま載せたりコピペでどうにかなるようにするつもりはないが、参考になれば幸いだ。

プログラムの大まかな実装方針

指定された機能は大きく分けて2つあった

  1. 品詞タグごとの出現頻度を降順で表示する
  2. 元の文章の再生成

ここから以下の操作をすることを考えた。

ファイルを開いて読み込む

→まとまった要素ごとに取り出す

 →アンダーバーの前後で「元々の単語」と「品詞タグ」にわける

  →「品詞タグ」を集めて各種類ごと出現回数を数える

   (このとき0回のタグは表示しなくていい)

   →「品詞タグを出現回数順に並べ替える」

    →タグと品詞名が納められた.csv形式ファイルを読み込む

     (ここでタグをキー、品詞名を値とするハッシュをつくると楽)

     →タグを品詞名に直し、出現回数とともに表示、
      出力ファイルに書き込む

   →「元々の単語」を集めて一つ一つ取り出して文章を作り直す

    →微調整した後、出力ファイルを開いて書き込む

細かい動作はおのおのあると思うが大体の流れはこんな所だと思う。

(2016/12/21 22:34 ここまで公開)

各操作の検討

「ファイルを開いて読み込む」

 File.open{ |filename| } あるいは io = open(filename)で良いだろう。前者はブロック内で動作を終えたあと自動的にファイルを閉じてくれる。後者はどこかで io.closeを置かないとファイルが閉じられず正常に動作しない。

 読み込みについては好みが分かれると思うが、while文で一行ずつ読み込む場合と、io.readlinsで文全体を一気に改行コードごとに配列の要素化してもいい。
(whileは引数(?)がnilまたはfalseのときのみループを抜けるため、逆説的にいえばそれ以外の時は無限にループする。例えば、line = io.getsとしてgetsメソッドで改行コードごとに文字列を取得すると、ファイルの最後の行にまで繰り返したとき、getsは文字列を取得できずnilを返す。このためファイルを全て読み込んだところでwhileループを抜けられるのである)

まとまった要素ごとに取り出す

 ここから「(元の単語)_(品詞タグ)」という要素ごとにまとまった形でとりだしていく。(いったん配列に保存しておくのが手っ取り早いと思うのでその方針で考える)
 まず読み込んだ文字列に対して’split'メソッドを使うとよい。splitはそれを使った原文を、引数で与えた部分で区切り配列にしてくれる。(引数:文字列か正規表現)詳しくはググったり教科書読んだりした方がいい。
一行ごとにこれを行なうと配列がそのたび更新されてしまうので、新たに配列をつくりそこに保存するか、あるいはsplitを使う前から要素をつなげておいて一文とし一度だけ分割すればいいような体制を整えるといい。
(2016/12/21 23:16 更新)

アンダーバーの前後で「元々の単語」と「品詞タグ」にわける

 ここまでで、「(元の単語)_(品詞タグ)」というかたまりが要素となり配列に納められていればあとは簡単に正規表現を使うことで分割できる。(もちろん必ずしも配列である必要は無い)
 配列の要素を一つ一つ取り出すためには'each'メソッドを使うとよい。ary.each{  |elem|  }の形で使えば全ての要素に対して網羅的に、要素1つずつを取り出して操作ができる。ところでsplitは分割するメソッドであったが、今ここで必要なのは(元の単語)の部分と(品詞タグ)の部分である。分割して配列に置換しさらに操作するのでは面倒だ。このように、文字列の一部だけほしいときには正規表現(あるいは後方参照)を使っていくと良いと思う。例えば、sub,gsubメソッドが挙げられる。引数に正規表現をとることで、指定した部分を任意の文字列に置換できる。これはつまり第二引数に””(空文字列)を指定することで第一引数を消去できるということでもある。
 ちょうど都合良く「_(アンダーバー)」が目印としてあるのだから、その前後を指定して(元の単語)と(品詞タグ)だけを取り出してしまおう。繰り返しになるが、複数の文字列を保存しておくには配列を使うのが良い。新たに要素のない配列を作っておき、そこへ逐次追加していくといいだろう。この辺の操作はググるなり教科書で見られるので割愛する。
(2016/12/21 23:37 更新)

 
微調整した後、出力ファイルを開いて書き込む

 文字列をつくった後、空白やその他フォーマットに合うように微調整をするが、これはテキストを読み込んで見落としがないか確認することにつきる。幸い出力結果の見本もページにアップされているのでそれとにらめっこするべきだ。例外があるとすれば「ピリオドの直後の空白」くらいだと思う。(辛かった)さて今度はそれをファイルに書き込むわけだがファイルの開き方はきちんと把握しているだろうか。読み込み専用、書き込み専用とあと一つは何だったか?大文字と小文字の違いは?記号がくっつくとどう変わる?開くときの注意は?この辺をおろそかにしていると後々面倒なことになりかねないので今の問いに答えられない場合教科書を読み直した方がいいと思われる。頑張ろう。
 ファイル名から.txtを取り除いて「-text.txt」を付け直さねばならない訳であるが、どうしたらよいだろうか?例えば、元々のファイル名が変数で渡されていれば、それに対して正規表現による置換を行えばいい。いちいちプログラム内でファイル名を書いてそれにしか対応出来ないような仕様にするのになれてしまうと、あとになって大量のデータを扱うことになったとき辛くなること必死である。工夫して一般化した処理をするように心がけたい。
 さて書き込む方法であるが、多様なやり方があるらしい。私は「io.print(文字列)」で文字列を書き込む方法を採っていたが、「<<」で渡せるらしい(?)これはあとで確認する必要がある。
 ここまでくれば晴れて実行結果チェックに進める。試行錯誤を繰り返しつつもこのチェックさえ通れば提出することだけはとりあえず出来るはずだ。視力を振り絞って頑張ってほしい。
(2016/12/22 00:19 更新)

 

「元々の単語」を集めて一つ一つ取り出して文章を作り直す

 ここまで配列ばかり使っていて申し訳ないがそうした方がわかりやすく段階を追って出来るのでそうしている。さて、「元々の単語」が一つずつ入った配列があればあとはそれを一つずつ取り出して文を作ればいいだけである。注意することとしては、課題テキストの要件をもう一度きちんと読むということと、ピリオドの直後に入るかもしれない空白を取り除くことだ。(私はこれに気づかず何時間も溶かした、空白だから見えなくて当然なのだが)
 ここでもしかすると改行コードの問題が現れるかもしれない。どこで空白行の表現をするのか悩ましく思うだろうが、その場合はもう一度ファイル読み込みの段階まで戻って考えてほしい。空白行を読み込んだときそこにはまとまった要素として紹介した文字列はなくただ改行コードがあるはずだ。(あるいchomp!していたなら””(空要素)となっているかもしれない)deleteしていないかぎりそれは配列に保存されて残っているはずなので、if文等を効果的に活用し段落を分けられるように工夫しよう。
(2016/12/21 23:58 更新)

「品詞タグ」を集めて各種類ごと出現回数を数える

 リストに載っているタグを網羅的に検索し、出現回数が0回のタグがあったとして、それについては出力しなくてもいいことがわかっている。(この注意に関してはテキストに書いておらず不適切だと考える)よって、タグだけを集めてある配列から一つずつ取り出して、それらを種類別にわけて数えていくことになる。eachを使おう。配列.each{ |変数| いくつかの処理}の形でブロックを渡せば簡単に要素ごとの処理ができる。