360 likes | 434 Views
情報処理 III www による情報発信とサービス提供 II. 第 6 回 2014 年 10 月 30 日 大塚 智宏. 本日の予定. 前回 までの 課題について Ruby の基礎(6) 正規表現によるテキスト処理 ファイル入出力 今回の課題. 前回 までの 課題について. 第 3 回 課題 採点結果の返却を行いましたので,ダウンロードして確認 してください 再提出の指示があった人は, 11/5 ( 水)までに再提出して ください それまで に再提出がない場合,今回の採点結果で確定となります 第 4 回課題 採点が終了していないため,もう少しお待ちください
E N D
情報処理IIIwwwによる情報発信とサービス提供II情報処理IIIwwwによる情報発信とサービス提供II 第6回 2014年10月30日 大塚 智宏
本日の予定 • 前回までの課題について • Rubyの基礎(6) • 正規表現によるテキスト処理 • ファイル入出力 • 今回の課題
前回までの課題について • 第3回課題 • 採点結果の返却を行いましたので,ダウンロードして確認してください • 再提出の指示があった人は,11/5(水)までに再提出してください • それまでに再提出がない場合,今回の採点結果で確定となります • 第4回課題 • 採点が終了していないため,もう少しお待ちください • 第5回課題 • 明日 10/31(金) 23:59締切です
プログラムの基本的な構成要素 • 値 (数値,文字列など) • 情報の記憶 (変数) • 入出力 • 制御構造 • 順次実行,条件分岐,ループ • サブルーチン,関数 • データ構造 • 配列,リスト,スタック,キュー,ハッシュ,木,… • アルゴリズム • 探索,整列(ソート),数値計算,データ解析,最適化,…
置換: subメソッド 正規表現がマッチした部分を,置き換え用の文字列で置換した文字列を返す 直接書き換える(破壊的バージョン) sub! もある /正規表現/の部分はパーセント記法でも可 文字列.sub(/正規表現/, 置換文字列) str = "I like programming." str2 = str.sub(/programming/, "Ruby") puts str2 # I like Ruby.と表示 uri = "http://www.waseda.ac.jp/" uri.sub!(%r|waseda|, "keio") puts uri # http://www.keio.ac.jp/と表示
subメソッドによる置換の例 量指定子やアンカー,空文字列などを組み合わせることで,さまざまな置換が可能 str = "I went bowling with Terry." # 「with Terry」 を 「with Dory」 に置換 str.sub!(/with \w+/, "with Dory") # 先頭に文字列を挿入 str.sub!(/^/, "Yesterday, ") # 先頭の文字列を削除 str.sub!(/^\w+, /, "") # 末尾に文字列を追加 str.sub!(/\.$/, ", and I won.")
プログラム例 入力を読み込み,各行の先頭に行番号を追加して表示,ただし空行は表示しない input = [] print "> " while line = gets do input << line.chomp print "> " end i = 0 input.each do |str| i += 1 next if str =~ /^\s*$/ # 空白文字のみの行はスキップ puts str.sub(/^/, "#{i}: ") end
実行例 -w はrubyコマンドの起動オプションで,「文法間違いではないが,バグが起こりやすいコード」 などに対して警告メッセージを表示してくれる 今後は常に付けることを推奨
ブロック付きsubメソッド(1) 置換文字列の代わりに {}で囲まれたブロックを指定 ブロック内には式を書くことができ,式の値が置換文字列となるため,比較的複雑な置換も可能 ブロックの先頭に |変数|を置くと,マッチした部分が変数に代入される str = "abc def 123" # "ABC def 123"となる (upcaseは英文字を大文字に変換) str.sub!(/^\w+/) {|s| s.upcase } # "ABC def 321"となる (reverseは文字列を逆順にする) str.sub!(/\d+$/) {|s| s.reverse }
ブロック付きsubメソッド(2) ブロック内ではキャプチャ変数も使うことができる $&$`$'も使えるようになる いずれも通常の置換文字列中では使えない str = "I went bowling with Terry." # "I went bowling against Terry's team."になる str.sub!(/with (\w+)/) { "against #{$1}'s team" } eqn = "123 + 456" # resultは "579"という文字列となる result = eqn.sub(/^(\d+) \+ (\d+)$/) { $1.to_i + $2.to_i # 値は自動的に文字列に変換される }
グローバルな置換: gsubメソッド マッチする文字列すべてを置換の対象とするsub 破壊的バージョンの gsub! もある str = "home, sweet home!\n" # sub では最初の 「home」 しか置換されない str2 = str.sub(/home/, "cave") puts str2 # cave, sweet home!と表示 # gsub では2ヶ所の 「home」 が両方とも置換される str3 = str.gsub(/home/, "cave") puts str3 # cave, sweet cave!と表示 # 連続した空白文字を1つのスペースに圧縮 str4 = "A B C\t\t D E" str4.gsub!(/\s+/, " ") puts str4 # A B C D Eと表示
ハッシュによる置換 置換文字列の代わりにハッシュを使うこともできる マッチした文字列をキーとし,置換文字列を値とするハッシュを指定 複数の置換を一度に行うことができる tag = '<html lang="ja">' # HTMLの特殊文字を文字実体参照に変換 tag.gsub!(/[&<>"]/, { '&' => '&', '<' => '<', '>' => '>', '"' => '"' }) # tagは'<html lang="ja">'になる
Tips: subメソッドが返す値 sub や gsub は置換後の新しい文字列を返す 置換が失敗した (正規表現がマッチしなかった) 場合,元の文字列のコピーを返す 破壊的バージョンの sub! とgsub! は元の文字列をそのまま返すが,置換が失敗した場合には nil を返す str = "I went bowling with Terry." # "I went bowling with Dory." という文字列を返す str2 = str.sub(/Terry/, "Dory") # 置換に失敗した場合は元の文字列(のコピー)を返す str3 = str2.sub(/Alice/, "Bob") # sub! は失敗した場合にnilを返す if str.sub!(/Dory/, "Michael") then puts "successfully substituted!" end
splitメソッド パターンで指定したセパレータによって文字列を分割し,配列に格納して返す (joinメソッドの逆の処理) text = "abc:def:g:h" fields = text.split(/:/) # 配列 ["abc", "def", "g", "h"]が返される fields = ":abc:def::g:h::".split(/:/) # 配列 ["", "abc", "def", "", "g", "h"]が返される # 末尾の空フィールドは捨てられる fields = " This is\t\ta pen.\n".split(/\s+/) # 配列 ["", "This", "is", "a", "pen."]が返される fields = " This is\t\ta pen.\n".split(" ") # 配列 ["This", "is", "a", "pen."]が返される # 特殊なパターンで,この場合は先頭の空フィールドも捨てられる # 引数指定(" ")は省略してもよい
その他の機能 正規表現には,他にもいろいろな機能がある もっと知りたい人は,Webや以下の書籍を参考にするとよい Jeffrey E. F. Friedl著,株式会社ロングテール/長尾 高弘 訳,『詳説 正規表現 第3版』,オライリー・ジャパン,2008年 通称 「フクロウ本」 Rubyに特化した記述は少ないが,正規表現のことを網羅的に学ぶのには最適
ファイル入出力とは データをファイルから読み出す/ファイルへ書き込む 入出力 = プログラムと外の世界とのコミュニケーション これまでに扱った入出力は,以下の2種類のみだった print/puts による画面への出力 gets によるキーボードからの入力 Rubyでは,これらの書式に少し手を加えるだけで,ファイル入出力も容易に扱えるようになっている ここでの fhは 「ファイルハンドル」 と呼ばれ,ファイルに関する情報を格納したデータを保持する変数 # ファイルへの出力 fh.puts "Hello, world!" # ファイルからの入力 (一行読み込む) line = fh.gets
ファイル入出力の流れ • 「オープン」,「読み出し,書き込み」,「クローズ」 の3ステップからなる • Rubyのファイル入出力では通常,この3ステップを1つのブロック構文として記述する ### 例: ファイルの内容を画面に表示するプログラム # ファイル data.txt をオープンし,ファイルハンドル fhを得る File.open("data.txt", "r") do |fh| # ファイルからの1行読み出しを,ファイルの末尾まで繰り返す while line = fh.gets do # 読み出した行をそのまま画面に表示 print line end end # ブロックが終了した際,ファイルは自動的にクローズされる
ファイルのオープン オープンの際は,読み書きのモードを指定する ファイル 「data.txt」 を読み出しモードでオープン モード指定を省略した場合も読み出しモードになる ファイルが存在しなかった場合はエラーとなる ファイル 「output.txt」 を書き込みモードでオープン ファイルが存在していた場合は上書きされる ファイル 「log.txt」 を追加書き込みモードでオープン ファイルが存在していた場合は末尾に追加書き込みされる File.open("data.txt", "r") do ... File.open("data.txt") do ... # モード指定を省略 File.open("output.txt", "w") do ... File.open("log.txt", "a") do ...
ファイルオープン時のエラー ファイルのオープンは,さまざまな理由で失敗する Rubyの場合,オープンに失敗した場合はエラー(例外)となってプログラムの実行はそこで終了する 他の言語のように,エラー処理のコードを付けておく必要はない 逆に,失敗した際にも実行を継続したり別の処理を行ったりしたい場合は 「例外処理」 を記述する必要がある (次回以降に解説) ファイルオープン時の主なエラー要因 ファイルが存在しない ファイルを置くディレクトリが存在しない 読み出し/書き込みの権限がない
ファイルへの書き込み printやputsをファイルハンドルのメソッドとして使う 画面に出力する場合と全く同じように扱える # ファイル output.txt を書き込みモードでオープン File.open("output.txt", "w") do |fh| # printによるファイルへの書き込み fh.print "Hello, world!\n" # putsによるファイルへの書き込み a, b, c = 3, 5, 7 fh.puts a, b, c # 式展開等ももちろん可能 fh.puts "#{a} + #{b} + #{c}" end
ファイルからの読み出し getsをファイルハンドルのメソッドとして使う キーボード入力を読み込む場合と全く同じように扱える # ファイル data.txt を読み出しモードでオープン File.open("data.txt") do |fh| # ファイルから1行読み出す line = fh.gets # ファイルから1行ずつ読み出しながら処理 # (ファイルの末尾に達するまで繰り返す) while line = fh.gets do line.chomp! puts "> #{line}" end end
読み出し・書き込み時のエラー ファイルの読み出し・書き込みも失敗する場合がある Rubyでは読み出し・書き込みの失敗はエラー(例外)となり,プログラムは強制終了される 読み出し・書き込み時の主なエラー要因 ファイルがオープンされていない ファイルハンドルとして指定した変数がファイル以外のデータを保持している 読み出しモードでオープンしたファイルに書き込みを行おうとした 書き込み/追加書き込みモードでオープンしたファイルから読み出しを行おうとした # 読み出しモードのファイルに対して書き込みを行う File.open("data.txt") do |fh| fh.puts "Hello, world!" # エラー(例外)が発生 end
プログラム例: ファイルのコピー print "input source filename: " srcfile = gets.chomp # コピー元のファイル名を入力 print "input destination filename: " dstfile = gets.chomp # コピー先のファイル名を入力 # コピー元のファイルを読み出しモードでオープン File.open(srcfile) do |ifh| # コピー先のファイルを書き込みモードでオープン File.open(dstfile, "w") do |ofh| # コピー元ファイルから1行ずつ読み出し,コピー先ファイルに書き込む while line = ifh.gets do ofh.print line end end end
実行例 • ファイル 「data.txt」 を 「output.txt」 にコピー
ブロック構文を使わない書き方 オープン/クローズをそれぞれ別々に記述 他の多くの言語と同様の構文 記述の柔軟性は高くなるが,closeメソッドによって明示的にファイルのクローズを行う必要がある 実際は明示的なクローズを行わなくてもプログラム終了時に自動的にクローズされるが,予期せぬ問題が発生することを避けるため,処理し終わったファイルは必ずクローズしておくこと # ファイルを書き込みモードでオープンし,ファイルハンドルを得る fh = File.open("output.txt", "w") fh.puts("Hello, world!") # 明示的にファイルをクローズ fh.close
readメソッド 1行ずつではなく,ファイルの内容全体を読み込む 先ほどのファイルコピーのプログラムは,以下のように書き直すことができる # (ファイル名の入力部分は省略) File.open(srcfile) do |ifh| File.open(dstfile, "w") do |ofh| # コピー元ファイルの内容全体を読み込み,コピー先ファイルに書き込む input = ifh.read ofh.print input # 上の2行は,以下のようにも書ける # ofh.print ifh.read end end
フォーマット出力printf (1) printやputsでは困難な細かい書式指定が可能 文字列中の %で始まるシーケンス (フォーマット指定子) を後続の引数の値で置き換え,指定されたフォーマットで出力する 主なフォーマット指定子 %d,%o,%x: 10進数,8進数,16進数 %f,%e: 浮動小数形式,指数形式 %g: 浮動小数形式または指数形式 (表記が短くなる方を自動で選択) %s: 文字列 主な修飾子 %nd: n桁の幅で右寄せ表示 %-nd: n桁の幅で左寄せ表示 %0nd: n桁の幅で表示し,足りない桁を0で埋める %.mf: 小数点以下m桁まで表示 printf フォーマット文字列, 引数1, 引数2, ...
フォーマット出力printf (2) 使用例 # 時刻をフォーマット表示 printf "%04d/%02d/%02d %02d:%02d:%02d\n", year, mon, day, hour, min, sec # 「2014/10/30 17:35:04」 等と表示 # 右詰めと左詰め printf "%-11s|%8d\n", filename, size # 「hoge.txt____|____4096」 等と表示 (_はスペース) # 小数の表示 printf "%10.3f\n", 1.0/3.0 # 「_____0.333」 と表示 (_はスペース) # printやputs同様,ファイルへの書き込みも可能 File.open("output.txt", "a") do |fh| fh.printf "%g\n", x end
フォーマット文字列を返すsprintf 画面やファイルに出力する代わりに文字列を返すprintf # フォーマットされた文字列を変数に代入 date = sprintf "%04d/%02d/%02d %02d:%02d:%02d", year, mon, day, hour, min, sec fstat = sprintf "%-11s|%8d", filename, size num = sprintf "%10.3f", 1.0/3.0 # %演算子によるsprintfの省略形 num = "%10.3f" % (1.0/3.0) # %演算子で引数が複数ある場合は配列を使う fstat = "%-11s|%8d" % [filename, size] date = "%04d/%02d/%02d %02d:%02d:%02d" % [year, mon, day, hour, min, sec]
課題 与えられたテキストファイルの内容を読み込み,指定したフォーマットに変換して別のファイルに出力するプログラムを作成せよ。 与えられるテキストファイルのフォーマットは以下の通り 各行が1つのデータレコードを表し,1つのレコードは :(コロン) で7つのフィールドに区切られる。 各フィールドは,空白文字,,(カンマ),"(ダブルクォート)を文字として含む場合がある。 # で始まる行はコメント行であり,データレコードではない。
課題(続き) 出力ファイルのフォーマットは以下の通りとする 各行を1つのデータレコードとし,各フィールドを ,(カンマ)で区切る。 フィールドに文字として ,(カンマ) および "(ダブルクォート) を含む場合は,フィールド全体をダブルクォート " " で囲む。ただし,その際もともとフィールドに含まれていた " は ""(ダブルクォート2個) に変換する。 例えば,フィールドが A,B,C であった場合は "A,B,C" となり,"ABC" であった場合は """ABC""" となる。 元のテキストファイルに含まれるコメント行は含めないこと。 この出力ファイルの形式はCSV (Comma-Separated Values) と呼ばれ,Microsoft Excel等で扱うことができる 出力ファイルのファイル名は任意でよい
課題(続き2) 提出物 以下をZipファイルにまとめたもの プログラムファイル コメントとして処理内容の説明を適宜入れること 出力ファイル (プログラムを実行した結果作成されたもの) 提出期限 11月7日(金) 23:59 keio.jp 「授業支援」 上で提出