鍋あり谷あり

テーマを決めずに適当に書いています。

書いてみた

昨日の続き。
http://www.kmonos.net/wlog/53.php#_1604050908 の、トーナメントひいき問題。
紙とペンで考えてもわからないので、とりあえずプログラムを書いてみた。
まずは、総当たり方式(←バカ)。
ここで haskell で書くのが筋のように思えるが、やっぱり ruby

require 'set'

L = {
  'A' => [ '', 'o', 'x', 'x', 'o', 'x', 'o', 'x' ],
  'B' => [ 'x', '', 'o', 'x', 'x', 'o', 'x', 'x' ],
  'C' => [ 'o', 'x', '', 'o', 'o', 'o', 'o', 'x' ],
  'D' => [ 'o', 'o', 'x', '', 'o', 'o', 'o', 'o' ],
  'E' => [ 'x', 'o', 'x', 'x', '', 'x', 'x', 'x' ],
  'F' => [ 'o', 'x', 'x', 'x', 'o', '', 'x', 'x' ],
  'G' => [ 'x', 'o', 'x', 'x', 'o', 'o', '', 'o' ],
  'H' => [ 'o', 'o', 'o', 'x', 'o', 'o', 'x', '' ],
}

PLAYERS = L.keys

table=Hash.new{ Set.new }

def each_combi( a, n )
  return if a.size<n || n<0
  if a.size==n
    yield a
  else
    tail = a[1..-1]
    each_combi( tail, n-1 ){ |x| yield( [a[0]]+x ) }
    each_combi( tail, n   ){ |x| yield( x ) }
  end
end

each_combi(PLAYERS, 2 ){ |x,y|
  xwin=L[x][ y[0]-?A ]=='o'
  ywin=L[y][ x[0]-?A ]=='o'
  if xwin==ywin
    raise "which will win? : #{x},#{y}"
  elsif xwin
    table[ x ] += y
  elsif ywin
    table[ y ] += x
  else
    raise "unexpected."
  end
}

Table = table

def target_lost( c, target )
  return ( if c.flatten.include? target
    target != find_winner( c )
  else
    false
  end )
end


def each_tment( players, target )
  if players.size==1 then
    yield players
  else
    tail = players[ 1..-1 ]
    each_combi( tail, players.size/2 ){ |c1|
      c0 = players-c1
      each_tment(c0, target){ |x0|
        next if target_lost( x0, target ) 
        each_tment(c1, target){ |x1|
          next if target_lost( x1, target ) 
          yield [x0,x1]
        }
      }
    }
  end
end

def find_winner( x )
  case x
  when Array
    case x.size
    when 1
      find_winner( x[0] )
    else
      w = x.map{ |i| find_winner( i ) }
      (0..1).each{ |m|
        return w[m] if Table[w[m]].member?( w[1-m] )
      }
      raise "#{w.join(',')} : not found in Table"
    end
  else
    x
  end
end

each_tment(PLAYERS, 'A'){ |x|
  winner = find_winner( x )
  if winner=='A'
    puts( '%s => %s' % [ x.inspect, winner ] )
    exit
  end
}

総当たりと書いたが、「target_lost」を呼んでいるところで枝刈りをしている。
8人ぐらいでは枝刈りをしなくても一緒か、かえって遅いくらいなんだが、16人になると、計算が終わるか否かの決定的な要因になる。

乱数で問題を作ってみると、解がある場合については、16人でもだいたい10秒以下で計算が終わる。
解がない場合は、10分待っても終わらないのでそのまま寝たら、朝には終わっていた。時間は計ってない。

まだ実装していないが、枝刈りのできる既知のポイントがもう一つある。
それは。残り試合が n のとき、ターゲットとの距離(ターゲット自身は距離ゼロ。ターゲットに負ける人は距離1。距離n未満でなく、距離n-1の人に負ける人は距離n、と、定義する)が n+1 以上の人がいたら調査しない、というものだ。
これで、全勝の人がいたりした場合に諦めることができるようになる。

いや、それでもまだ足りない。
16人プレイヤーがいるときに、ターゲットが3人にしか勝てないケースが刈れていない。

と、枝刈りをあと2種類ぐらい実装すれば、いい線まで行けるんじゃないかと思っているんだが、数学を背景にしたきれいな実装ではないので、計算量を見積もるのは困難である。

それと。
id:Cryolite:20050908:p3 も、自分で実装してみるつもり。枝刈りも入れて。まだよく読んでないが。
#pruning って何? と思って辞書をあたると、枝刈りだった。あう。