select + mapを一つのメソッドでの続き。
さしあたって5個書いてみました。
設定課題
- 絞込:文字列から数値をてきとうに抽出する
- 加工:数値に変換。+1して返す。
each
他の言語から移ったばかりにとってはまぁ標準的なスタイルかと思います。
def filter_number_and_succ(xs)
res = []
xs.each do |x|
res << $&.to_i.succ if /\d+/ =~ x
end
return res
end
p filter_number_and_succ(%w(test-101 102 hoge 003 a002))
##Pros
- 初心者でもわかりやすい
- 色々と処理を詰め込める
Cons
- eachという単語から"配列を変更して返す"という意味が期待できない(resという変数名があるけど・・・)
- 副作用があるコードが違和感なく書ける
select + map
Ruby初心者脱出した辺りで書きたくなるコードかと思います。
def filter_number_and_succ(xs)
xs.select { |x|
/\d+/ =~ x
}.map { |x|
$&.to_i.succ if /\d+/ =~ x
}
end
Pros
- 初心者でも勉強してもらえば分かってもらえる
- 絞込と加工が別フェーズになっていて処理が明確
Cons
- (この例のように)絞込と加工が同じ動作を兼ねる場合は処理が冗長になる
- 絞込方法、加工方法を外からブロックなどで渡そうとした際にキモいことになる
flat_map
flat_mapは空配列を除去する、という性質があることを利用した方法です。MayBeモナ・・・?
def filter_number_and_succ(xs)
xs.flat_map { |x|
next [] unless /\d+/ =~ x
$&.to_i.succ
}
end
Pros
- 一回のループで複数の結果を返すことが出来る
- nextで空配列を返せば、「結果はいらないよ」ということをそれなりに表現できる
Cons
- 本当に配列を返したい時は、
[[x, y, z]]
とか書かなきゃダメでぱっと見よくわからない。
map + compact
結果がいらない場合はnilを返して、compactで皆殺しにする作戦です。
def filter_number_and_succ(xs)
xs.map { |x|
$&.to_i.succ if /\d+/ =~ x
}.compact
end
Pros
- nilを返せば(または戻り値を指定しなければ)、「結果はいらないよ」ということをそれなりに表現できる
Cons
- 本当にnilを返したい時はどうにもならない
inject
関数型言語のサンプルで見るやつです。
def filter_number_and_succ(xs)
xs.inject([]) { |acc, x|
if /\d+/ =~ x
acc + [$&.to_i.succ]
else
acc
end
}
end
Pros
- それなりに直感的
Cons
- ブロックが「Array#+で配列の連結ができる」ということを知ってなければならず、配列がデータ構造の組み立て方を意識しなればならない。
- その気になれば、ブロック側が途中の計算結果を破壊できるわけで