Croudのスタックを2本に拡張
クラウド・コンピューティング時代のオレ様プログラミング言語「Croud」にはスタックが1本しかなかった。これでは制限がきつすぎるし、かといってヒープとかじゃつまらないと思い、もう1本、裏スタックを加えようと思って、次のようにStackクラスを作ることを考えた。
何となくArrayを継承して、外側からはArrayに見えて、内部的には2本の配列を切り替えるようなものを作ればいいんだろうと思って、あれこれがんばってみた。
class Stack < Array def initialize @stc1 = [] @stc2 = [] @current = 1 end def switch if @current == 1 @stc = @stc2 @current = 2 elsif @current == 2 @stc = @stc1 @current = 1 end end super() ?? end
なにか、決定的にアホな間違いをしている気がする……。ArrayとStackでは内部的なデータ表現が配列1本と2本で全然違う。だからこれって継承とか、そういう問題じゃないのか。継承したいのはメソッド群だけなんだけど。
もっと素直に次のように書いてみた。
class Stack attr_accessor :stc def initialize @stc1 = [] @stc2 = [] @current = 1 @stc = @stc1 end def switch if @current == 1 @stc = @stc2 @current = 2 elsif @current == 2 @stc = @stc1 @current = 1 end end end
これで動作自体は期待通り。
irb(main):001:0> require "stack.rb" => true irb(main):002:0> s = Stack.new => [] irb(main):003:0> (1..10).each do |i| s.stc << i end => 1..10 irb(main):004:0> s.stc => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] irb(main):005:0> s.switch => 2 irb(main):006:0> ("a".."z").each do |i| s.stc << i end => "a".."z" irb(main):007:0> s.stc => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] irb(main):008:0> s.switch => 1 irb(main):009:0> s.stc => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] =end
switという命令を追加して、この命令が来たら2本のスタックを入れ替えるようにした。
s = Stack.new : : when :drop s.stc.pop : : when :swit s.switch :
フィボナッチ数列の、最初のほうの20個だけ表示するCroud(の中間言語)で書いたコードは次の通り。2本目の裏スタックに置いたカウンターをループの停止条件に使うようにしている。
push,1 push,2 swit push,20 setl,1 swit dup roll add putnum swit push,1 sub nzjmpl,1
うまくいった!
$ ./crd fib2.cr 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 $
s.stc.popという表現が山のようにでてきて汚いコードになってしまったけど、とりあえずできたし、ちゃんとフィボナッチが停止して感激した。
しかし、最初にfib2.crを書いたときには、ジャンプの戻り先を間違えて一段階スタック切り替えを忘れてしまった。そういうバグがあると、Rubyのエラーになりがちで、一瞬処理系のほうが壊れたかと思ってしまう。やっぱりエラー対策とか、例外処理みたいなことって必要なものなのかなと思ったりした。
スタックを入れ替えるだけじゃなくて、スタックの一番上同士を入れ替えるような命令とか、「X」の形に入れ替えるような命令があると、なんだか楽しそうな気もする。
裏スタックに「Hello, world」とゼロターミネートした文字列を置いて、それを表示させるサブルーチンを呼ぶというようなことも、ひょっとしてできるかも。とか考え出すと、なるほど単純なジャンプだけじゃなくて戻り先を何とかうまく処理する仕組みもほしいよな、とかなってくる。
そういえば、やる気がなかったgets、getnum命令も実装してみた。
when :gets s.stc << $stdin.gets.chomp! when :getnum s.stc << $stdin.gets.chomp!.to_i
これで、入力された数字の回数だけフィボナッチを繰り返すコードは次のように書ける。
getnum swit push,1 push,2 swit setl,1 swit dup roll add putnum swit push,1 sub nzjmpl,1
実行結果は、
$ ./crd fib3.cr 10 3 5 8 13 21 34 55 89 144 233 $
となる。