Ruby: Enumerableのよさがわかるクイズ(翻訳)
Cassidy Williamsの週刊ニュースレターにある「今週のクイズ」コーナーで出題される問題は、ちょくちょく私のオタク心をくすぐってくれます。典型的なのは、RubyのEnumerable
にある豊富なメソッドをうまく使える解答を見つけられたときです。今週のodd_sum
問題もそういう直球ど真ん中の問題でした。
今週の問題:
数値を含む2つの配列を受け取ったら、それぞれの配列に含まれている数値を1つずつ取り出して足したときに奇数になるすべての組み合わせを返すコードを書いてください。
// (訳注: これはJavaScriptです)
oddSum([9, 14, 6, 2, 11], [8, 4, 7, 20]);
// > [9, 20], [14, 7], [11, 8]
oddSum([2, 4, 6, 8], [10, 12, 14]) > null;
// (falsyな値なら他の何でもよい)
「一般的な言語」における最も力まかせな解法は、以下のように2つの配列のループをネストして、組み合わせた数値の合計が奇数になるかどうかをその都度チェックするというものです。
def odd_sum(a, b)
results = []
a.each do |x|
b.each do |y|
if ((x + y) % 2) == 1
results << [x, y]
end
end
end
results
end
しかし私たちにはもっとよい知恵があります。そしてRubyはこういうときに頼りになります。
2つの数値を足した結果が奇数になるのは、一方が奇数で他方が偶数の場合だけであることを知っていれば(小学校レベルの算数)、数値が奇数かどうかをいちいち計算でチェックしなくても、配列の要素が奇数か偶数かに基づいて解を求められます。
最初に思いついたのは、渡された2つの配列に対して#select
、#product
、#odd?
、#even?
メソッドで以下のようなワンライナーを書けるのではというものでした。
def odd_sum(a, b)
a.select(&:odd?).product(b.select(&:even?)) +
b.select(&:odd?).product(a.select(&:even?))
end
しかし私がこれをSNSにポストしたところ、XavierとAlexからワンライナーにするなら以下の方がよいのではという指摘をいただきました↓。どうやら奇数偶数チェックを避けようとするあまり複雑にしてしまうというワナに陥っていたようです。
def odd_sum(a, b)
a.product(b).select { (_1 + _2).odd? }
end
どちらの書き方を使うとしても、「結果なし」の場合はfalsyな結果(=条件文でfalse
と判定される値)を返す必要がありますが、Rubyの空配列[]
はtruthy(=条件文でtrue
と判定される値)です。
> !![]
#=> true
以下の完全な解決には、Active Supportのあまり知られていない#presence
メソッドも使っています(これについては過去記事で、このささやかながら有能な機能をRailsアプリケーションで活用することをおすすめしたこともあります)。
さらにこの完全版コードにはuniq
メソッドによる一意性チェックも追加しています。これは妥当な改善でしょう。
require "active_support"
def odd_sum(a, b)
(a.select(&:odd?).product(b.select(&:even?)) +
b.select(&:odd?).product(a.select(&:even?)))
.uniq
.presence
end
# または
def odd_sum(a, b)
a.product(b)
.select { (_1 + _2).odd? }
.uniq
.presence
end
このコードのパフォーマンスを最適化できそうですが、その分読みにくくなるかもしれません。このコードがホットパス(=動作中のアプリケーションで大規模データに対して頻繁に実行される箇所)に該当する場合は、ベンチマークを取ったうえで最適化する価値がありそうです!
(メモ: 別記事でベンチマークを行いました
Rubyコードで書いたときの嬉しい点は、解法の基礎となる背後の数学的概念の式が、まるで英文のように読めることです。
odd from
a
with even fromb
, plus
odd fromb
with even froma
a
にある奇数とb
にある偶数(を組み合わせた配列)と、
b
にある奇数とa
にある偶数(を組み合わせた配列)
これも、Rubyで使える読みやすくて楽しい構文の例です。
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。