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