金貨と銅貨と空箱の「うそつき問題」(Ruby)
rsc.hatenablog.comまたまた rsc さんのブログから問題を拝借して、Ruby で解いてみました。
問題
コピペさせてもらいます。
A~Eの五つの箱があり、これらの箱は、金貨の入った箱、銅貨の入った箱、空箱の3種類の場合がある。
また、それぞれの箱にはラベルが付いているが、そのラベルの記述の内容は、金貨の入った箱のものは真(真実に一致している)であるが、銅貨の入った箱のものは偽(真実に反している)であり、空箱のものは真の場合も偽の場合もあるという。
このとき、銅貨の入った箱が二つあるとすると、確実に銅貨の入った箱はどれか。
[ラベル]
A:「Bのラベルの記述の内容は真である。」
B:「Aが空箱ならば、この箱も空箱である。」
C:「この箱は、銅貨の入った箱である。」
D:「AかEの少なくとも一方は、銅貨の入った箱である。」
E:「この箱は、金貨の入った箱である。」
解答例
問題が少し曖昧で、空箱の場合、「真の場合も偽の場合もある」というのは「真でも偽でもどちらでも可」なのか、「真の場合も偽の場合も両方とも満たさないといけない」のかわかりませんが、まあ前者だろうと解釈して解きました。
コード。
N = 5 TBL = [*0...N] box = Array.new(N) imp = ->(p, q) {!p || q} label = Array.new(N) label[0] = ->{label[1].()} label[1] = ->{imp.(!box[0], !box[1])} label[2] = ->{box[2] == :copper} label[3] = ->{box[0] == :copper || box[4] == :copper} label[4] = ->{box[4] == :gold} judgement = ->(i) { return true unless box[i] f = label[i].() (box[i] == :gold) ? f : !f } output = ->{ puts (0...N).map {|i| "#{%W(A B C D E)[i]}=>#{box[i].inspect}"}.join(", ") } TBL.combination(2) do |cp2| cp2.each {|c| box[c] = :copper} (0..3).each do |n| (TBL - cp2).combination(n) do |golds| golds.each {|g| box[g] = :gold} (TBL - cp2 - golds).each {|e| box[e] = nil} output.() if (0...N).all? {|i| judgement.(i)} end end end
結果。
A=>nil, B=>:copper, C=>nil, D=>:copper, E=>nil A=>nil, B=>:copper, C=>nil, D=>:copper, E=>:gold A=>nil, B=>:copper, C=>nil, D=>nil, E=>:copper A=>nil, B=>:copper, C=>nil, D=>:gold, E=>:copper
つまり答えは B ということになります。
簡単な解説
箱は A ~ E ということですが、コードでは 0 ~ 4 に置き換えています。
基本的に「総当り法」で、まず「銅」の箱を2つ選び、残りは適当に「金」か「空」を割り当てます。全部「金」になったり、全部「空」になる場合もあります(と解釈して解いたのですが、それも問題では曖昧です)。箱は box[]
で、:gold, :copper, nil が入るということです(nil は「空」)。
ラベルの条件は label[]
に Proc オブジェクトで記述しています。「p ならば q」という論理包含(IMP)は、「p の否定と q との論理和」と必要十分なので、それを imp という Proc で表現しています。
箱の中身とラベルのマッチングを判断しないといけないので、それは judgement.()
で記述しています。すべての箱で judgement.()
が真になった場合に、その組み合わせを出力( output.()
)します。
Proc (ここでは lambda ですが)のクロージャの機能を使いまくっているコードになっています。