permutation.eachを自前で用意する(rubyで魔方陣その4)

permutation.eachを自前で用意する

Counting And Listing All PermutationsのB. Heap's algorithmを元にされたGaucheで魔方陣に挑戦(順列を実装) - Gemmaの日記のu8.scmのアルゴリズムrubyに実装してみた。

def heapPermute(n,item,block=Proc.new)
  if n==1
    block.call(item)
  else
    0.upto(n-1){|i|
      heapPermute(n-1,item,block)
      n%2==0 ? swap(0,n-1,item) : swap(i,n-1,item)
    }
  end
end

def swap(a,b,item)
  tmp=item[a]
  item[a]=item[b]
  item[b]=tmp
end

item=(1..9).to_a
heapPermute(9,item){|i|
  x=i[0]+i[1]+i[2]
  p i if x==i[3]+i[4]+i[5]&&x==i[6]+i[7]+i[8]&&x==i[0]+i[3]+i[6]&&x==i[1]+i[4]+i[7]&&x==i[2]+i[5]+i[8]&&x==i[0]+i[4]+i[8]&&x==i[2]+i[4]+i[6]
}

コードがlisp臭くなっているのがすごく気に入らないんだが、Proc.callじゃなくてyieldで書きたいがその時どうやって再帰の方に引数を渡すのか分からないのでこのまま行ってみる。

$ time ruby e.rb
[8, 3, 4, 1, 5, 9, 6, 7, 2]
[8, 1, 6, 3, 5, 7, 4, 9, 2]
[6, 7, 2, 1, 5, 9, 8, 3, 4]
[6, 1, 8, 7, 5, 3, 2, 9, 4]
[4, 3, 8, 9, 5, 1, 2, 7, 6]
[4, 9, 2, 3, 5, 7, 8, 1, 6]
[2, 7, 6, 9, 5, 1, 4, 3, 8]
[2, 9, 4, 7, 5, 3, 6, 1, 8]

real	0m0.650s
user	0m0.636s
sys	0m0.008s

そして、Gaucheでかかれたu8.scmの方は

u$ time gosh u8.scm
#u8(2 9 4 7 5 3 6 1 8)
#u8(2 7 6 9 5 1 4 3 8)
#u8(4 3 8 9 5 1 2 7 6)
#u8(4 9 2 3 5 7 8 1 6)
#u8(6 7 2 1 5 9 8 3 4)
#u8(6 1 8 7 5 3 2 9 4)
#u8(8 3 4 1 5 9 6 7 2)
#u8(8 1 6 3 5 7 4 9 2)

real	0m0.847s
user	0m0.828s
sys	0m0.004s

(スーパーサイ○人になる前でも)差が生じるのは言語のせいとは言いたくないんだけど…言語の特性なるものがあるんだろうか。
というか、もっとschemeくさくコード書くと高速化するのでしょうか?
私の技術ではまだ無理そうだなぁ…

追記 2008/12/13 22:37:18:

id:Gemmaさんの方でvectorインプリメントされ直してあった(笑
そのコードの実行結果

$ time gosh k.scm
#(2 9 4 7 5 3 6 1 8)
#(2 7 6 9 5 1 4 3 8)
#(4 3 8 9 5 1 2 7 6)
#(4 9 2 3 5 7 8 1 6)
#(6 7 2 1 5 9 8 3 4)
#(6 1 8 7 5 3 2 9 4)
#(8 3 4 1 5 9 6 7 2)
#(8 1 6 3 5 7 4 9 2)

real	0m0.433s
user	0m0.432s
sys	0m0.008s

さすがなスピード(笑

ここまで

odd?使って遅くなったのはナイショ(笑


もうちょっとrubyっぽくかくとこうなるけどおそくなった。

class Permute
  def initialize(item) @item=item ;end
  def each(block=Proc.new) heapPermute(@item.size,block) ;end
  def heapPermute(n,block=Proc.new)
    if n==1
    block.call(@item)
    else
      0.upto(n-1){|i|
        heapPermute(n-1,block)
        n%2==0 ? swap(0,n-1) : swap(i,n-1)
      }
    end
  end
  def swap(a,b)
    tmp=@item[a]
    @item[a]=@item[b]
    @item[b]=tmp
  end
  protected :heapPermute, :swap
end

Permute.new((1..9).to_a).each{|i|
  x=i[0]+i[1]+i[2]
  p i if x==i[3]+i[4]+i[5]&&x==i[6]+i[7]+i[8]&&x==i[0]+i[3]+i[6]&&x==i[1]+i[4]+i[7]&&x==i[2]+i[5]+i[8]&&x==i[0]+i[4]+i[8]&&x==i[2]+i[4]+i[6]
}