470 likes | 551 Views
情報処理 III www による情報発信とサービス提供 II. 第 5 回 2014 年 10 月 23 日 大塚 智宏. 本日の予定. 前回 までの 課題について Ruby の基礎(5) 正規表現の基礎 いろいろな正規表現の文法 今回の課題. 前回 までの 課題について. 第 3 回課題,第 2 回 課題の 再提出分 採点が終了していないため,もう少しお待ちください 第 4 回課題 明日 10/24 (金) 23:59 締切です. 第2回課題. スライドの練習問題 5 問をそれぞれ解くためのプログラムを作成せよ。
E N D
情報処理IIIwwwによる情報発信とサービス提供II情報処理IIIwwwによる情報発信とサービス提供II 第5回 2014年10月23日 大塚 智宏
本日の予定 • 前回までの課題について • Rubyの基礎(5) • 正規表現の基礎 • いろいろな正規表現の文法 • 今回の課題
前回までの課題について • 第3回課題,第2回課題の再提出分 • 採点が終了していないため,もう少しお待ちください • 第4回課題 • 明日 10/24(金) 23:59締切です
第2回課題 スライドの練習問題5問をそれぞれ解くためのプログラムを作成せよ。 半径12.5の円の円周の長さと面積を求めよ。 1年は何秒か。平年とうるう年の両方について求めよ。 私が生まれてから約11億3800万秒経過しているとすると,私は何歳か。 入力として数値を受け取り,それを半径とする円の円周の長さと面積を求めて表示するプログラムを書け。 入力として文字列と数値を順に受け取り,文字列を1行に1つずつ数値の行数だけ繰り返して表示するプログラムを書け。
第2回課題の解答例 半径12.5の円の円周の長さと面積を求めよ。 # 円周率と円の半径 pi = 3.14 r = 12.5 # 円周の長さを求める peri = 2 * pi * r # 面積を求める area = pi * r**2 # 結果を表示 puts "perimeter: #{peri}" puts "area: #{area}"
第2回課題の解答例 1年は何秒か。平年とうるう年の両方について求めよ。 # 1日の秒数 sec_per_day = 60 * 60 * 24 # 平年の秒数 common = 365 * sec_per_day # うるう年の秒数 leap = 366 * sec_per_day # 結果を表示 puts "common year: #{common} seconds" puts "leap year: #{leap} seconds"
第2回課題の解答例 私が生まれてから約11億3800万秒経過しているとすると,私は何歳か。 # 経過時間(秒) elapsed = 11_3800_0000 # 1年あたりの秒数 # 何歳かが分かればよいので,おおよその値で十分 sec_per_year = 60 * 60 * 24 * 365.25 # 年齢の算出 (小数点以下は切り捨て) age = (elapsed / sec_per_year).to_i # 結果を表示 puts "#{age} years old"
第2回課題の解答例 入力として数値を受け取り,それを半径とする円の円周の長さと面積を求めて表示するプログラムを書け。 # 円周率 pi = 3.14 # 入力として半径(小数)を受け取り,変数に代入 print "input radius? " r = gets.to_f # 円周の長さと面積を求める peri = 2 * pi * r area = pi * r**2 # 結果を表示 puts "perimeter: #{peri}" puts "area: #{area}"
第2回課題の解答例 入力として文字列と数値を順に受け取り,文字列を1行に1つずつ数値の行数だけ繰り返して表示するプログラムを書け。 # 入力として文字列を受け取り,変数に代入 # 改行コードは取り除かないでおく print "input string? " str = gets # 入力として行数(整数)を受け取り,変数に代入 print "input # of repeat? " rep = gets.to_i # 改行を含む文字列に繰り返し演算を適用することで,「1行に1つずつ,# 数値の行数だけ繰り返して」 表示される puts str * rep
プログラムの基本的な構成要素 • 値 (数値,文字列など) • 情報の記憶 (変数) • 入出力 • 制御構造 • 順次実行,条件分岐,ループ • サブルーチン,関数 • データ構造 • 配列,リスト,スタック,キュー,ハッシュ,木,… • アルゴリズム • 探索,整列(ソート),数値計算,データ解析,最適化,…
正規表現とは 文字列の集合(パターン)を表現する記法 例) Rubyに組み込まれた一種の 「言語」 最近のプログラミング言語の多くに標準で装備されている 他の言語やツールでも共通に使える ただし,それぞれの文法は微妙に異なる場合がある 正規表現で何ができるか? 文字や文字列の検索,置換 テキストやデータの整形,フィルタリング ユーザの入力内容等の形式チェック,など ABC ABc AbC aBC Abc aBc abC abc 正規表現で表すと, となる [Aa][Bb][Cc]
正規表現の基本的な使い方 調べたい文字列に対し,正規表現を 「マッチ」 させる 成功 (マッチする),失敗 (マッチしない) のいずれかとなる 例) 正規表現 マッチする文字列 マッチしない文字列 正規表現がマッチする文字列の個数は,1個だったり,複数だったり,無限にあったりといろいろな場合がある [Aa][Bb][Cc] など ABC abc Abc aBC XYZ 1234 bcd ACB など
Rubyにおけるマッチの判定 =~演算子を使い,以下の式で表す 文字列の中から正規表現にマッチする部分文字列を探し,1ヶ所でも見つかればマッチ成功 (真),全く見つからなければマッチ失敗 (偽) となる 判定したい文字列=~ /正規表現/ str = "aiueo kakikukeko" if str =~ /kaki/ then # マッチする(真) puts "matched A" end if str =~ /sashi/ then # マッチしない(偽) puts "matched B" end
メタキャラクタ 正規表現において,特殊な意味を持つ文字 .*+?|^$()[]{}\ 正規表現中でメタキャラクタ自体を表したい場合は,直前に\(バックスラッシュ) を置く バックスラッシュ自体は \\で表す eqn = "1 + 2 - 3 * 4 / 5" if eqn =~ /3 * 4/ then # マッチしない (* はメタキャラクタ) puts "matched A" end if eqn =~ /3 \* 4/ then # 「3 * 4」 の部分にマッチ puts "matched B" end
ワイルドカード文字 「.」 改行文字を除く,あらゆる1文字にマッチ .(ピリオド)自身は \.で表す .でもピリオド自身にマッチするが,他の文字にもマッチしてしまう str = "hoge.rb" str =~ /h..e/ # 「hoge」 の部分にマッチ str =~ /h...e/ # マッチしない(.は1文字のみにマッチ) str =~ /......./ # 「hoge.rb」(全体)にマッチ str =~ /......../ # マッチしない (7文字しかないため) str =~ /\.rb/ # 「.rb」 の部分にマッチ
量指定子 「*」 直前の文字の0回以上の繰り返しにマッチ *(アスタリスク)自身は \*で表す str1 = "AABBCC" str2 = "AABCC" str3 = "AACC" str1 =~ /AB*C/ # 「ABBC」 の部分にマッチ str1 =~ /A*B*C*/ # 「AABBCC」(全体)にマッチ str2 =~ /AB*C/ # 「ABC」 の部分にマッチ str2 =~ /A*B*C*/ # 「AABCC」(全体)にマッチ str3 =~ /AB*C/ # 「AC」 の部分にマッチ str3 =~ /A*B*C*/ # 「AACC」(全体)にマッチ
量指定子 「+」 直前の文字の1回以上の繰り返しにマッチ +自身は \+で表す str1 = "AABBCC" str2 = "AABCC" str3 = "AACC" str1 =~ /AB+C/ # 「ABBC」 の部分にマッチ str1 =~ /A+B+C+/ # 「AABBCC」(全体)にマッチ str2 =~ /AB+C/ # 「ABC」 の部分にマッチ str2 =~ /A+B+C+/ # 「AABCC」(全体)にマッチ str3 =~ /AB+C/ # マッチしない str3 =~ /A+B+C+/ # マッチしない
量指定子 「?」 直前の文字がオプションであることを示す ?自身は \?で表す str1 = "AABBCC" str2 = "AABCC" str3 = "AACC" str1 =~ /AB?C/ # マッチしない str1 =~ /A?B?C?/ # 「AB」 の部分にマッチ str2 =~ /AB?C/ # 「ABC」 の部分にマッチ str2 =~ /A?B?C?/ # 「ABC」 の部分にマッチ str3 =~ /AB?C/ # 「AC」 の部分にマッチ str3 =~ /A?B?C?/ # 「AC」 の部分にマッチ
選択 「|」 「または」 の意味になる |(縦線)自身は \|で表す str1 = "AABBCC" str2 = "ACBDEF" str3 = "BADCFE" str1 =~ /ABC|DEF/ # マッチしない str1 =~ /AB|CD|EF/ # 「AB」 の部分にマッチ str2 =~ /ABC|DEF/ # 「DEF」 の部分にマッチ str2 =~ /AB|CD|EF/ # 「EF」 の部分にマッチ str3 =~ /ABC|DEF/ # マッチしない str3 =~ /AB|CD|EF/ # マッチしない
グループ化 「()」 パターンの一部をグループにまとめる (自身は \( で,)自身は\)で表す グループ化されたパターンは1文字のように扱える 量指定子を付けると,グループに対する繰り返し指定になる str1 = "ABABAB" str2 = "AAABBB" str1 =~ /(AB)*/ # 「ABABAB」(全体)にマッチ str1 =~ /(BA)*/ # 「BABA」 の部分にマッチ str2 =~ /AA(AA|BB)/ # 「AABB」 の部分にマッチ str2 =~ /BB(AA|BB)/ # マッチしない
プログラム例 入力を1行ずつ読み込み,正規表現にマッチした行のみを表示 print "> " while line = gets do line.chomp! if line =~ /(ABC?)+/ then # 任意の正規表現に置き換え可能 puts line end print "> " end
実行例 • 前のスライドのプログラムを実行
Tips: エスケープシーケンスの使用 正規表現では,ダブルクォート文字列と同じエスケープシーケンスを使用できる \n(改行),\t(タブ),\044(8進数による文字指定),\x2f(16進数による文字指定) 等 str = "A\tB\n" str =~ /A\tB/ # マッチする str =~ /B\n/ # マッチする str.chomp =~ /B\n/ # マッチしない
Tips: パターン中の式展開 ダブルクォート文字列と同様,正規表現のパターン内でも式展開を行うことができる str = "Ruby" print "> " while line = gets do line.chomp! # 入力行の中に 「Ruby」 が含まれていたらその行を表示 if line =~ /#{str}/ then puts "'#{str}' found in '#{line}'." end print "> " end
パーセント記法 「%r」 /の代わりに任意の記号を使える記法 パターン中に /が多いときなどに便利 (<[{のいずれかを使う場合は左右対になるようにする uri = "http://www.keio.ac.jp/" uri =~ /http:\/\// # /はエスケープが必要 uri =~ %r|http://| # /のエスケープは不要 uri =~ %r!http://! # 同上 uri =~ %r(http://) # 左右対の記号を使用 uri =~ %r[http://] # 同上
オプション修飾子 「i」 大文字と小文字を区別せずにマッチさせる オプション修飾子は 「フラグ」 とも呼ばれる str = "AbCdEfGhI" str =~ /ABC/i # 「AbC」 の部分にマッチ str =~ /def/i # 「dEf」の部分にマッチ str =~ %r|GHI|i # 「GhI」 の部分にマッチ str =~ %r!abc!i # 「AbC」 の部分にマッチ str =~ %r(DeF)i # 「dEf」 の部分にマッチ str =~ %r[gHi]i # 「GhI」 の部分にマッチ
文字クラス []の中に並べた文字のいずれかにマッチさせる -(ハイフン)を使うと,文字の範囲を指定できる 文字クラス内では,ハイフン自身は \-で表す [の直後に ^(キャレット)を置くと,「~以外」(否定)になる type = "HAL-9000" type =~ /[AHL]+/ # 「HAL」 の部分にマッチ type =~ /[A-Z]+/ # 同上 type =~ /[a-z]+/i # 同上 type =~ /[AHL09\-]+/ # 「HAL-9000」(全体)にマッチ type =~ /[A-Z0-9\-]+/ # 同上 type =~ /[^A-Z]+/ # 「-9000」 の部分にマッチ type =~ /[^0-9]+/ # 「HAL-」の部分にマッチ
文字クラスのショートカット 頻繁に使われる文字クラスの略記法 \d数字 ([0-9]と同じ) \wワード文字 ([A-Za-z0-9_]と同じ) \s空白文字 ([ \t\n\r\f]と同じ) type = "HAL -9000\t\n" type =~ /\d+/ # 「9000」 の部分にマッチ type =~ /\w+/ # 「HAL」 および 「9000」 の部分にマッチ type =~ /\s+/ # 「」 および 「\t\n」の部分にマッチ
ショートカットの否定形 ショートカットの文字集合 「以外」 の文字を表す \D数字以外 ([^0-9]や [^\d]と同じ) \Wワード文字以外 ([^A-Za-z0-9_]や [^\w]と同じ) \S空白文字以外 ([^ \t\n\r\f]や [^\s]と同じ) type = "HAL -9000\t\n" type =~ /\D+/ # 「HAL -」 および 「\t\n」 の部分にマッチ type =~ /\W+/ # 「-」 および 「\t\n」 の部分にマッチ type =~ /\S+/ # 「HAL」 および 「-9000」の部分にマッチ
アンカー 「^」 「$」 パターンを文字列の先頭および末尾に固定する ^文字列の先頭にマッチさせる (「~で始まる文字列」) $文字列の末尾にマッチさせる (「~で終わる文字列」) 文字列全体にマッチさせたい場合は,両方指定する str1 = "ABC" str2 = "BABCAB" str1 =~ /^AB/ # 「AB」 の部分にマッチ str2 =~ /^AB/ # 「AB」 は先頭にないのでマッチしない str1 =~ /AB$/ # 「AB」 は末尾にないのでマッチしない str2 =~ /AB$/ # 「AB」(末尾)の部分にマッチ str1 =~ /^ABC$/ # 「ABC」(全体)にマッチ str2 =~ /^ABC$/ # マッチしない
正規表現の例 さまざまな文字列,数値などを表現できる ここでは文字列全体にマッチさせるためにアンカー (^と$) を指定しているが,部分文字列にマッチさせたい場合はアンカーを外せばよい # 2桁の数 (10 ~99) /^[1-9][0-9]$/ /^[1-9]\d$/ # ショートカットを使用 # 24時間形式の時刻 (00:00 ~23:59) /^([01][0-9]|2[0-3]):[0-5][0-9]$/ /^([01]\d|2[0-3]):[0-5]\d$/ # ショートカットを使用 # HTMLのa要素の開始タグ (<a href="…">) # (他の属性が入る場合などもあるので完全ではない) /^<a +href=".*?">$/i # http または https で始まるURI (これも不完全) %r|^https?://.+|
キャプチャとキャプチャ変数 グループ化を行うと ()内の文字列が記憶され,後で利用することができる 「キャプチャ」 と呼ばれる 左括弧(()の出現順に,特殊変数 $1,$2,… で参照可能 str = "AB CD EF" if str =~ /(\w+) (\w+) (\w+)/ then puts "#{$3} #{$2} #{$1}" # EF CD ABと表示 end if str =~ /(\w+ (\w+) \w+)/ then puts "#{$1} #{$2}" # AB CD EF CDと表示 end
変数への代入 正規表現はデータの一種なので,変数に代入して保持しておくことができる regex = /(Perl|Ruby)/ print "> " while line = gets do line.chomp! # 入力行の中に 「Perl」 か 「Ruby」 が含まれていたら表示 if line =~ regex then puts "'#{$1}' found in '#{line}'." end print "> " end
プログラム例 パターンをテストするプログラム $&$`$'は特殊な変数で,それぞれ文字列の 「マッチした部分」,「それより前の部分」,「それより後ろの部分」 が格納される regex = /[A-Z]+(-\d+)?/i # 任意のパターンに置き換え可能 print "> " while line = gets do line.chomp! if line =~ regex then puts "matched: |#{$`}<#{$&}>#{$'}|" else puts "no match: |#{line}|" end print "> " end
実行例 前のスライドのプログラムを実行
Tips: マッチする部分が複数ある場合 文字列を先頭から調べ,「最初に見つかった」 「一番長い」 部分にマッチする 「欲張り」(greedy)なマッチ,と呼ばれる できるだけ短い部分でマッチしようとする 「控え目」(non-greedy)な量指定子 *?+???もある str = "ABC_DEF_DCC" str =~ /[A-Z]+/ # 「ABC」 にマッチする str =~ /[A-Z]+?/ # 「A」 にマッチする str =~ /A\w*C/ # 「ABC_DEF_DCC」 にマッチする str =~ /A\w*?C/ # 「ABC」 にマッチする str =~ /D\w?C/ # 「DCC」 にマッチする str =~ /D\w??C/ # 「DC」 にマッチする
Tips: 汎用量指定子 「{}」 直前の文字の繰り返し回数を指定 {m,n}m回以上n回以下の繰り返し {m,} m回以上の繰り返し {m} ちょうどm回の繰り返し {,n}という形式はない ({0,n}と書く必要がある) それぞれ 「控え目」 なバージョンもある str = "ABCDEFGHIJKLMN" str =~ /\w{3,6}/ # 「ABCDEF」 の部分(6文字)にマッチ str =~ /\w{3,6}?/ # 「ABC」 の部分(3文字)にマッチ str =~ /\w{3,}/ # 全体(14文字)にマッチ str =~ /\w{3,}?/ # 「ABC」 の部分(3文字)にマッチ str =~ /\w{3}/ # 「ABC」の部分(3文字)にマッチ
Tips: アンカーと改行文字 改行文字(\n)を含む文字列の場合,^は改行文字の直後,$は改行文字の直前にもマッチする str1 = "123456" str2 = "123\n456\n" str1 =~ /^1/ # マッチする str2 =~ /^1/ # マッチする str1 =~ /^4/ # マッチしない str2 =~ /^4/ # マッチする str1 =~ /6$/ # マッチする str2 =~ /6$/ # マッチする str1 =~ /3$/ # マッチしない str2 =~ /3$/ # マッチする
Tips: ワード境界アンカー ワードの先頭と末尾にマッチ 「ワード」 とは,/\w+/にマッチするものを指す \bワードの先頭と末尾にマッチ \B\bの否定 (\bがマッチしない全ての場所にマッチ) # cat catalog scat concatenate /\bcat/ # ○ ○ × × /cat\b/ # ○ × ○ × /\bcat\b/ # ○ × × × /\Bcat/ # × × ○ ○ /cat\B/ # × ○ × ○ /\Bcat\B/ # × × × ○
Tips: パターンの優先順位 パターンの各部分は,以下の優先順位で結合する グループ化 () 量指定子 a*a+a?a{n,m} アンカー,並び ^aa$abc 選択 a|b|c 基本要素 a[abc]\d /^Perl|Ruby$/ # 選択(|)はアンカーより優先順位が低いため, #「『Perlで始まる』または『Rubyで終わる』 # 文字列」となってしまう /^(Perl|Ruby)$/ # 「『Perl』 または 『Ruby』 という文字列」 に # マッチさせたいなら,このようにグループ化する
課題 まず,以下のそれぞれを表す正規表現を作成せよ。 (日本の)郵便番号 (108-8345 等) 値の範囲は考慮しなくてよい。 (日本の)携帯電話番号 (090-XXXX-YYYY 等) 市外局番の部分は 070,080,090 のいずれかで始まる。 ハイフンはあってもなくてもマッチするようにせよ。 IPv4アドレス (131.113.134.20 等) A.B.C.D の形で,A~D は 0~255 の範囲。 ドメイン名 (www.keio.ac.jp 等) ドット(.)で複数のパートに区切られる。 パートの数は1以上で,各パートはワード文字とハイフン(-)のみで構成される。大・小文字は区別しない(どちらでも受け付ける)。 数字とハイフンは各パートの先頭以外の文字にのみ使用可能。 ただし,一番最後のパートは2文字以上の英文字のみで構成される。
課題(続き) 次に,キーボード入力を1行ずつ読み込んで,入力された文字列に対し,4つの正規表現それぞれがマッチするかどうかを判定するプログラムを作成せよ。 なお,ここでの 「マッチ」 の対象は文字列全体 (先頭から末尾まで) とする。 例えば,「108-8345」 は郵便番号として正しいが,「108-83457」 は郵便番号ではない。 プログラムは1つにまとめることが望ましいが,できなければ4つに分けてもよい。 いろいろな文字列を入力してテストし,正しく動作するかどうか確認すること。 いつも通りプログラムにはコメントを入れること。特に今回は,各正規表現の内容について詳しく説明すること。
課題(続き2) 提出物 すべてのプログラムファイルをまとめたZipファイル 提出期限 10月31日(金) 23:59 keio.jp 「授業支援」 上で提出