Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Ruby: Enumerableのよさがわかるクイズ(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

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にポストしたところ、XavierAlexからワンライナーにするなら以下の方がよいのではという指摘をいただきました↓。どうやら奇数偶数チェックを避けようとするあまり複雑にしてしまうというワナに陥っていたようです。

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 from b, plus
odd from b with even from a
aにある奇数とbにある偶数(を組み合わせた配列)と、
bにある奇数とaにある偶数(を組み合わせた配列)

これも、Rubyで使える読みやすくて楽しい構文の例です。

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。