続・深さ優先探索
先日の記事でバックトラック法が上手く書けない、と焦っていましたが、デザインをきちんとやったらすんなり書けました。今回は動作に対してもそれなりに納得できるものになりましたので、一応転記しておきます。
やっていることは単純で、こんな組合せを作ります。
- 制約条件
- :q1 は :s1, :s2 のいずれかを選択する。
- :q2 は :s2, :s3 のいずれかを選択する。
- :q1, :q2 は必ず選択されなくてはならない。
- :s1, :s2, :s3 は1回だけ選択可能。
- 結果
- [{:q2=>:s2}, {:q1=>:s1}]
- [{:q2=>:s3}, {:q1=>:s1}]
- [{:q2=>:s3}, {:q1=>:s2}]
irbで実行するとこんな感じです。
irb(main):139:0> constraint = CMB::CmbConstraint.sample => {:q2=>[:s2, :s3], :q1=>[:s1, :s2]} irb(main):140:0> CMB::CmbCreator.create constraint => [[{:q2=>:s2}, {:q1=>:s1}], [{:q2=>:s3}, {:q1=>:s1}], [{:q2=>:s3}, {:q1=>:s2}]]
以下、コード。Railsで使うつもりでしたので、クラス名にCmbという接頭辞を付けています。ダサイです。バックトラック法を用いていますので、Haskilなどの関数型言語で記述するともう少しマシになるかもしれません。
module Combination # hash of 'key => array' # { q1 => [s11, s12, ...], q2 => [s21, s22, ...] } class CmbConstraint < Hash def self.sample hash = {:q1=>[:s1, :s2], :q2=>[:s2, :s3]} self.create hash end def self.create(hash) ret = self.new hash.each do |key, ary| ret[key] = ary.dup end ret end # 破壊的 def delete_pair!(q, s) delete(q) each do |key, ary| ary.delete(s) end self end def delete_pair(q, s) ret = self.class.create(self) ret.delete_pair!(q, s) end end class CmbCell < Hash def self.make(q, s) cell = self.new if cell.empty? cell[q] = s else raise "CmbCell has an only member" end cell end end class CmbRow < Array # 破壊的 def add_cell!(cell) self << cell end # 非破壊的 def add_cell(cell) ret = self.dup ret.add_cell!(cell) end end class CmbTable < Array def self.make(*rows) table = self.new rows.each do |row| table << row end end # Arrayにて定義済だが、再定義が必須 def +(other) ret = self.dup other.each do |item| ret << item end ret end end class CmbCreator def self.create(constraint, row = CMB::CmbRow.new) creator = self.new constraint = CMB::Cmbconstraint.create(constraint) unless constraint.kind_of? CMB::Cmbconstraint creator.create(constraint, row) end # constraint: CmbConstraint # row: CmbRow # retrun: CmbTable # Array of CmbRows: [ r1, r2, ... ] def create(constraint, row = CMB::CmbRow.new) return CmbTable.make(row) if constraint.empty? result = CmbTable.new q = constraint.keys.first for s in constraint[q] cell = CmbCell.make(q, s) t_constraint = constraint.delete_pair(q, s) t_row = row.add_cell(cell) t_table = create(t_constraint, t_row) result += t_table end result end end end CMB = Combination