LoginSignup
3
2

More than 5 years have passed since last update.

空の配列に対するRubyのメソッドの挙動(数学寄り)

Last updated at Posted at 2018-05-12

以下の結果は何なのかという話。空の配列でバグったら格好悪いので復習する。

quiz.rb
p [].any?
p [].all?

p [].combination(0).to_a
p [].combination(1).to_a

p [].cycle.size

p [[], [], []].transpose.transpose

any? と all?

any? は「要素にひとつ以上真なものがあるか」なので、空の配列に対して false を返すのは納得しやすい(真逆である none? も簡単)。それに対して all? は「要素が全て真なものか」なので、空の配列に対してだと何も無いのに全てとは何なのか混乱する。

そこで以下のように考える。簡単のため ary はbooleanの配列とする。

ary.all? の実装
# イメージとしては
init && ary[0] && ary[1] && ... && ary[-1]

# より形式的には
ary.inject(init) { |res,elem| res && elem }

このとき ary.size > 0 に対して妥当な結果になる init を決められれば、それは空の配列に対する戻り値としても妥当である。 false だとダメだが true ならうまくいく。

あるいは、ド・モルガンの法則を使って any? に書き換えても理解できる。一般に ary.all? == !ary.any?(&:!) なので、空の配列に対してだと all?any? と逆の値を返す。

以上より、空の配列に対して all? は true を返す


大抵の場合はこの性質で真偽判定が単純になるはずだが、 all? は与えられた評価が何であっても空の配列だと true を返してしまうことには注意。成り立つわけがない評価ほど見逃しやすい。

vacuous_truth.rb
puts "文字数が'負'な英単語のみをカンマ区切りで入力してください"
ary = gets.chomp.split(',', -1)
p ary

if ary.all? { |str| str.length < 0 }
    puts "成功"
else
    puts "失敗"
end

順列・組み合わせ

配列の要素に対して指定した個数での選び方を列挙するメソッド。並び順を区別するか、同じものを選んでもいい(重複を許す)か、によって 2×2 = 4種類ある。

  • どの場合においても、「0個のものの中から0個を選ぶ方法」は1通りだけある。その結果、「0個を選んだことを表す空の配列」を1つ持つリスト [[]] が作られる1
  • それに対して、「0個のものの中から1個以上を選ぶ方法」は存在しない。結果として、選び方を1つも持たないリスト [] が作られる1
# [[]] を返す
ary = []; r = 0
ary.permutation(r).to_a
ary.combination(r).to_a
ary.repeated_permutation(r).to_a
ary.repeated_combination(r).to_a

# [] を返す
ary = []; r = 1
ary.permutation(r).to_a
ary.combination(r).to_a
ary.repeated_permutation(r).to_a
ary.repeated_combination(r).to_a

cycle

each を無限に繰り返すようなメソッド。(引数で回数指定もできる)

ary = [1, 2, 3]
ary.cycle.size     #=> Infinity
ary.cycle.take(10) #=> [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

では0個を無限回繰り返すとどうなるのだろう?と思ったが、これは0になった。取り出せるものが無いのだから当然か。

[].cycle.size #=> 0
[].cycle.to_a #=> []

無限が有限になるぶんには問題は起きそうにない。空の配列だとループが実行されないのは each でも何でも同じなので cycle 特有の問題ではない。

transpose

2次元配列を行列とみなして、行と列を入れ替える(転置する)メソッド。

空の配列なら0行であり、0行0列は転置しても同じく空の配列になる。

[].transpose #=> []

…と思ったら罠があった。本当は0行なら何列なのかわからないので、上の動作はごく稀に都合が悪い2。わかりやすいところでは、「転置を2回すれば元に戻る」が成り立たない事例がある。

ary = [[], [], []]             # 3行0列
ary.transpose           #=> [] # 0行3列?
ary.transpose.transpose #=> [] # != ary

なお Matrix クラスなら行列の大きさを保持するので空行列でもうまく扱える。空の配列の転置のためだけに使うくらいなら場合分けなどするが。

require 'matrix'

m = Matrix.build(3, 0) {}  #=> Matrix.empty(3, 0)
m.to_a                     #=> [[], [], []]
m.transpose.to_a           #=> []
m.transpose.transpose.to_a #=> [[], [], []]

その他

self時々nilを返す破壊的メソッド」を空の配列に作用させると nil が返る。配列に対する変更が無いことを意味する。

[].keep_if {} #=> []  # 常にself
[].select! {} #=> nil # 普通はselfだが、変更が無いとnil

何か1個を返すつもりのメソッドだとだいたい nil が返る。個人的には minmax で対処を忘れてしまいやすい。

[].sample #=> nil
[].minmax #=> [nil, nil]

all? の例でも使ったが、 inject で空の配列を扱うなら初期値を指定しておく必要がある(それを決めるのが難しい…)。例えば「与えられた全ての数の積」を計算するなら、0個のときは1を返すのが望ましい。

[].inject(:*)    #=> nil
[].inject(1, :*) #=> 1

  1. 正確には、ブロックを与えれば順次実行され、与えなければ Enumerator が返される。 

  2. 実際にこれのため0の場合だけアルゴリズムを適用できないことがあった。 

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2