以下の結果は何なのかという話。空の配列でバグったら格好悪いので復習する。
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の配列とする。
# イメージとしては
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 を返してしまうことには注意。成り立つわけがない評価ほど見逃しやすい。
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 が返る。個人的には min
や max
で対処を忘れてしまいやすい。
[].sample #=> nil
[].minmax #=> [nil, nil]
all?
の例でも使ったが、 inject
で空の配列を扱うなら初期値を指定しておく必要がある(それを決めるのが難しい…)。例えば「与えられた全ての数の積」を計算するなら、0個のときは1を返すのが望ましい。
[].inject(:*) #=> nil
[].inject(1, :*) #=> 1