あみだくじを作ってみる。
言語を練習するとき、面白い問題があると便利ですね。どう書く?orgではそんな問題が沢山掲載されています。そこに乗っていた二つの問題を解いてみたのでメモしておきます。コードが稚拙なので投稿するのは憚られますが・・・。
まず1問目の問題を解きます。これは、与えられたあみだくじを解くだけの問題です。基本的な解法としてはあみだくじの行数分ループさせ、最初に与えられた1行をあみだくじに従って入れ替える事が考えられますが、この方法は既に投稿されているので、別の方法をとってみることにします。
class Walker LINE_MOVE_NUM = 2 attr_reader :character attr_accessor :x, :y def initialize(character,x,y) @character = character @x = x @y = y end def go_to_next(line) @y += 1 if line[@x+1,1] == "-" @x += LINE_MOVE_NUM elsif line[@x-1,1] == "-" @x -= LINE_MOVE_NUM end end end amidakuji = <<END_OF_AMIDAKUJI A B C D E | | |-| | |-| | |-| | |-| |-| |-| |-| | |-| | | | END_OF_AMIDAKUJI result = "" walkers = Array.new amidakuji.each_line do |line| if walkers.empty? line.split(//).each_with_index do |c,i| print "#{c}" walkers << Walker.new(c,i,0) unless c == " " result << c end else walkers.each do |c| c.go_to_next(line) result[c.x,1] = c.character end print("#{line.chomp} : #{result}") end end puts result
最初の一行を、あみだくじを進める人(Walker)オブジェクトと見なし、go_to_nextメソッドで次の行を見に行く仕組みです。エレガントでもなんでもないですが、Rubyでクラスを使う練習にはなりましたね。確認結果は次のようになります。
A B C D E | | |-| | : A B D C E |-| | |-| : B A D E C | |-| |-| : B D A C E |-| |-| | : D B C A E |-| | | | : B D C A E B D C A E
あみだくじの横の数字は、一行ごとに、そのときwalker(s)がいた座標を表します。もっと複雑なあみだくじも試してみます。
A B C D E F G H | | |-| | |-| | : A B D C E G F H |-| | |-| | |-| : B A D E C G H F | |-| |-| |-| | : B D A C E H G F |-| |-| |-| |-| : D B C A H E F G |-| | | | |-| | : B D C A H F E G B D C A H F E G
ちゃんと答えが出ています。ただし、このコードでは横棒が連なっている場合(|-|-|)は対処できずヘンな結果になります。
とりあえず解く事はできたので、2問目に進みます。これは、あみだくじの答え(結果?)からあみだくじ自体を生成するという問題です。生成されたあみだくじの一行目は、答えを昇順にソートしたものになります。ここではオブジェクトをつかって云々やると大変なので、ソートという特性を使ったコードを書きました。
def generate_amidakuji_from_result(arg) #配列をディープコピー sorted_list = Marshal.load(Marshal.dump(arg)).sort.to_s list = Marshal.load(Marshal.dump(arg)) amidakuji = Array.new i = 0 #リストがソート済みの結果になるまでループ until(sorted_list == list.to_s) j = 0 #列の最後まで走査 while(list[j+1] != nil) if(list[j].to_i > list[j+1].to_i) #入れ替え list[j],list[j+1] = list[j+1],list[j] add = "|-|" j+=1 else add = "|" end j+=1 #列の最後ならば縦棒で閉じる。 add << " |" if j < list.size && (list[j] == nil || list[j+1] == nil) (amidakuji[i] ||= "") << add << " " end amidakuji[i].strip! i += 1 end amidakuji end #list = [3,1,5,5,1,2,4,5,5,7,3,1,6,2,4] #list = [3,5,2,4,0,1] list = ARGV puts list.sort.join(" ") puts generate_amidakuji_from_result(list).reverse puts list.join(" ")
このコードには汚い点があります。until式で「リストがソート済みの結果になるまでループ」していることです。つまり、最初に受け取った配列をまずソートしてから文字列に直し、処理中の配列も文字列に変換し、毎ループごとに見比べるからです。これはパフォーマンス的にも問題があるし、なんかダサイですね。これ以外の方法を思いついたら教えてください。
結果は以下のようになります。rubyでこのコードを走らせるときは、引数にスペース区切りでリストを与えてください。問題の例を使うならば、コード中のコメントを外して使ってください。また、このコードでは1桁以上の数字はサポートできません。これはあみだくじの体裁のためです。
$ ruby amida2.rb 3 5 2 4 0 1 0 1 2 3 4 5 | |-| | | | |-| |-| |-| | |-| |-| | |-| |-| |-| | |-| |-| | 3 5 2 4 0 1
なんとか生成できたようです。ところで、どう書く?に投稿されているRubyコード(現時点)では同じ値を含むリストを渡されると正しく処理ができないようです。(問題文にないから当然ですが)このコードはそういったリストも処理できます。
$ ruby amida2.rb 3 3 3 3 3 3 2 2 3 3 3 3 3 3 |-| | | | | | | |-| | | | | | | |-| | | | | | | |-| | | | | | | |-| | | | | | | |-| 3 3 3 3 3 3 2 1 1 1 2 2 3 3 4 4 5 5 5 5 6 7 | | |-| |-| | | | | | | | | | | | | |-| |-| | | | | | | | | | | | | |-| |-| |-| | | | | | | | | | | |-| |-| |-| | | | | | | | | |-| |-| |-| |-| | | | | | | | | |-| |-| |-| |-| |-| | | |-| |-| |-| |-| |-| |-| | | |-| |-| |-| |-| |-| |-| | | | | |-| |-| | | |-| |-| | |-| |-| | |-| | | | | |-| | |-| | 3 1 5 5 1 2 4 5 5 7 3 1 6 2 4
1問目のWalkerをつかってあみだくじを解くと、正しくルーティングされている事がわかります。
1 1 1 2 2 3 3 4 4 5 5 5 5 6 7 | | |-| |-| | | | | | | | | | : 1 1 2 1 3 2 3 4 4 5 5 5 5 6 7 | | | |-| |-| | | | | | | | | : 1 1 2 3 1 3 2 4 4 5 5 5 5 6 7 | | | | |-| |-| |-| | | | | | : 1 1 2 3 3 1 4 2 5 4 5 5 5 6 7 | | | | | |-| |-| |-| | | | | : 1 1 2 3 3 4 1 5 2 5 4 5 5 6 7 | | | | |-| |-| |-| |-| | | | : 1 1 2 3 4 3 5 1 5 2 5 4 5 6 7 | | | | | |-| |-| |-| |-| |-| : 1 1 2 3 4 5 3 5 1 5 2 5 4 7 6 | | |-| |-| |-| |-| |-| |-| | : 1 1 3 2 5 4 5 3 5 1 5 2 7 4 6 | |-| |-| |-| |-| |-| |-| | | : 1 3 1 5 2 5 4 5 3 5 1 7 2 4 6 | | |-| |-| | | |-| |-| | |-| : 1 3 5 1 5 2 4 5 5 3 7 1 2 6 4 |-| | |-| | | | | |-| | |-| | : 3 1 5 5 1 2 4 5 5 7 3 1 6 2 4 3 1 5 5 1 2 4 5 5 7 3 1 6 2 4
また、次のようにStringクラスにto_iメソッドを追加すれば、文字列リストを渡しても文字列コードでソートしてくれます。*1
class String def to_i self end end
$ ruby amida2.rb C D A E F D B A B C D D E F | |-| | | | | | | |-| | | | | | | |-| | | | | | | |-| | |-| | |-| |-| | |-| | |-| | C D A E F D B $ ruby amida2.rb O W A T A ^ o ^ A A O T W ^ ^ o | |-| |-| | | | |-| |-| | | | | | |-| |-| | |-| O W A T A ^ o ^
以上。