Memoize |
BasicWerk
EC Support
Technique
Facebook
|
20140818221222_ruby_regex_grep_match_scan |
|
ruby_regex_grep_match_scan
% ruby -v ruby 2.0.0p451 (2014-02-24 revision 45167) [universal.x86_64-darwin13]
1.9 以降のバージョンでは grep メソッドは配列に対して動作する。 Shell の grep コマンドが行指向だということを思い出せばなるほどと思う。
# sample.csv
> puts `cat sample.csv`
id,name,price,currency
1,item-A,2980,JPY
2,item-B,900,JPY
3,item-C,3980,JPY
4,item-D,1980,JPY
# 行ごとに配列の要素となるように読み込む
> lines = IO.readlines "sample.csv"
=> ["id,name,price,currency\n", "1,item-A,2980,JPY\n",
"2,item-B,900,JPY\n", "3,item-C,3980,JPY\n",
"4,item-D,1980,JPY\n"]
# 数字から始まっている行だけにマッチさせる(= ヘッダーを取り除く)
> lines.grep /^\d/
=> ["1,item-A,2980,JPY\n", "2,item-B,900,JPY\n",
"3,item-C,3980,JPY\n", "4,item-D,1980,JPY\n"]
Ruby の正規表現リテラルには g フラグがない。 グローバルマッチはメソッド側でコントロールする。 置換であれば単純に sub と gsub の違いである。
# 先にヘッダー以外の行を変数に格納しておこう
> rows = lines.grep /^\d/
=> ["1,item-A,2980,JPY\n", "2,item-B,900,JPY\n",
"3,item-C,3980,JPY\n", "4,item-D,1980,JPY\n"]
# 最初の , だけを : に置換
> rows.map {|row| row.sub(/,/, ":")}
=> ["1:item-A,2980,JPY\n", "2:item-B,900,JPY\n",
"3:item-C,3980,JPY\n", "4:item-D,1980,JPY\n"]
# 全ての , を | に置換
> rows.map {|row| row.gsub(/,/, "|")}
=> ["1|item-A|2980|JPY\n", "2|item-B|900|JPY\n",
"3|item-C|3980|JPY\n", "4|item-D|1980|JPY\n"]
置換ではなくマッチとキャプチャの組み合わせ(つまりキャプチャしたマッチ文字列だけ抜き出したい)は、 match メソッドが最初にマッチした部分を返し、scan メソッドがマッチした全ての部分文字列を返す。
# 最初に見つけた数字だけ取り出す
# まずはキャプチャと $n の組み合わせ
> rows.map {|row| row.match(/(\d+)/); $1}
=> ["1", "2", "3", "4"]
# こちらの記法のほうが簡潔
> rows.map {|row| row[/(\d+)/, 1]}
=> ["1", "2", "3", "4"]
# row に含まれる数値部分をすべて取り出す
# scan を使った下記の例では、正規表現内のキャプチャは不要(=マッチ文字列全体がキャプチャされる)
> rows.map {|row| row.scan /\d+/}
=> [["1", "2980"], ["2", "900"],
["3", "3980"], ["4", "1980"]]
# では scan の正規表現内でキャプチャを使ったらどうなるのか?
> rows.map {|row| row.scan /(\d+)/}
=> [[["1"], ["2980"]], [["2"], ["900"]],
[["3"], ["3980"]], [["4"], ["1980"]]]
> rows.map {|row| row.scan /(\d+),([^,\n]+)/}
=> [[["1", "item-A"], ["2980", "JPY"]],
[["2", "item-B"], ["900", "JPY"]],
[["3", "item-C"], ["3980", "JPY"]],
[["4", "item-D"], ["1980", "JPY"]]]
# ご覧のとおり、キャプチャ部分を配列にして返してくれる
# フラットなリストで欲しい時はその名の通り flatten を使おう
> rows.map {|row| row.scan(/(\d+),/).flatten}
=> [["1", "2980"], ["2", "900"],
["3", "3980"], ["4", "1980"]]
> rows.map {|row| row.scan(/(\d+),([^,\n]+)/).flatten}
=> [["1", "item-A", "2980", "JPY"],
["2", "item-B", "900", "JPY"],
["3", "item-C", "3980", "JPY"],
["4", "item-D", "1980", "JPY"]]
|
| © Shin Nakamura/BasicWerk 2014 |