WindowsでGoのクロスコンパイル環境構築(Go Ver 1.5専用)
さすがはWindows!ひと手間かけたおいしさだぜ!!
経過をメモっているので、最後だけ読めば事足りるよ!!
参考にしたのはこのあたり
Go のクロスコンパイル環境構築 - Qiita
Windows7 64bit版でGo言語のクロスコンパイルを試す - taknb2nchのメモ
どうして試行錯誤時にずっと見てたページって再度ググると全然出てこないんだろう?
なんか英語のall.bashを実行してとかそういうページをずっと見ながらやってたんだけど、
見つからないのでリンクを貼れない。公式っぽいページ。
前提
なのでcgoと呼ばれるCで作られたと思わしきgoは使えません。
前作業
Go1.5をインストロールします。
golang downloadってbingに入力すれば、msiみたいなのを入手できます。
適当にC:\Go
とかに入れます。たぶんデフォルトでそうなってたから。
環境変数としては、GOROOT=C:\Go
がセットされている状態になっていることを確認してください。
Goのコンパイヨ
クロヌコンパイヨに使用するコマンド自体はGOOS=[OS名] GOARCH=[ARCH名] go ~
と簡単なのですが、
この使用したいGOOSとGOARCHの組み合わせ毎にGOそのものをコンパイヨしておく必要があります。
以後の記述が面倒なので、GOOS=darwin
, GOARCH=amd64
に限定して進めます。
コンパイヨはGOROOT\src
フォルダーに移動して、以下のコマンドを実行!!
GOOS=darwin GOARCH=amd64 make.bash
すると、たとえbashがある環境でもWindowsではmake.bat使えよって怒られるので、 コマンドプヨンプヨを開いて実行します。
SET GOOS=darwin SET GOARCH=amd64 make.bat
はい。3行です。1行では実行できません。
上記コマンドを実行すると、BootStrap用のGOがないよと怒られます。
なんか設定がないと、%UserProfile%\Go1.4
をBootStrapのrootと認識するようです。
GOROOT_BOOTSTRAPの設定
これは今からコンパイヨしようとしているGoをコンパイヨするためのGoなので、
もう1個Goの塊が必要?なのです。
SET GOROOT_BOOTSTRAP=%GOROOT%
としても動くのかは確認していません。
1.4を落としてくるのも面倒なので、C:\Go
を丸々コピーしてC:\GoBoot
を作ります。
そして、GOOROOT_BOOTSTRAP
を設定して実行!!
SET GOOS=darwin SET GOARCH=amd64 XCOPY /S /E /F /G /H /R /K /Y C:\Go C:\GoBoot SET GOROOT_BOOTSTRAP=C:\GoBoot make.bat
GOOS
とかは設定済みであれば再度設定する必要はありません。
これでできるかと思いきや、gccがないからcgoが作れないよ!!
と怒られます。
CGOを作らない
作れないなら作らなきゃいいじゃない!!ということで、
CGO_ENABLED
を0に設定します。
SET GOOS=darwin SET GOARCH=amd64 XCOPY /S /E /F /G /H /R /K /Y C:\Go C:\GoBoot SET GOROOT_BOOTSTRAP=C:\GoBoot SET CGO_ENABLED=0 make.bat
あ、同じく一度やったやつは再実行しなくて大丈夫です。
これでコンパイヨに成功するはず!!
少なくともうちではしました。
これで
SET GOOS=darwin SET GOARCH=amd64 go build hello.go
とかが可能になります。
ちなみにマックは高いので持ってません。
おまけ
なぜか頑張ってコマンヨプヨンプヨで実行したけど、 よくよく考えればgoをやっているみんなはgit for Windowsとか入れているはずなので、 git bash使って以下の処理でいいような気がする。
cp -r C:\Go C:\GoBoot GOOS=darwin GOARCH=amd64 GOROOT_BOOTSTRAP=C:\GoBoot CGO_ENABLED=0 make.bat
試してないのでうまく動かないかもしれないけど。
goコマンド実行時も、SET GOOS=???
とか実行してしまうと元に戻さないといけないので、
bashで1行で書いたほうが良さげです。
あ、GoBootはいらなくなったら消す方がいいと思う。 お好みで。
型安全なリストを作りたい Part. 1
最終的な目標としては、行列の計算がしたい。
1 2 3 1 4 1*1+2*2+3*3 1*4+2*5+3*6 14 32 ( ) * ( 2 5 ) = ( ) = ( ) 4 5 6 3 6 4*1+5*2+6*3 4*4+5*5+6*6 32 77
こんな感じのやつ。
2行3列の行列は3行2列の行列との積を求めることができて、結果は2行2列の行列になる。
もう少し一般化すると、m行n列 * n行p列 = m行p列
で、
1つ目の行列の列数と2つ目の行列の行数が一致していないといけない。
これを普通にコンパイラが判定してくれるようにできればいいなという話。
ただ、全通りつくるとかはどう考えても現実的ではないので、 サイズ付きの行列を作ろうというのが主題です。
ちなみに、shapelessというライブラリにはSizedListみたいな型があるそうなので、
仕事とかですぐに必要であればそちらのソースを読んだりするべきですが、
今回は思考そのものを楽しむことが目的なので特に原典もなく間違いも気にせず進みます。
1. サイズを型で表現する。
いきなり難しいことは考えずに、とりあえずサイズ付きの集合型を考えることから始めます。
EmptyList
, OneItemList
, TwoItemsList
のようなのがList[0]
, List[1]
ってできればいいのですね。
まずは単純に数値0を表す型を作ってみます。
sealed trait Size sealed trait Zero extends Size
ここまでは決め事なのでこれでいいとして、問題はこの先です。
One
, Two
なんて続けていったらいつまで経っても終わりません。
そこで、ちょっと前に書いたScalaの型推論って難しいのチャーチ数を使ってみます。
final case class Succ[S <: Size]() extends Size
これで1がSucc[Zero]
, 2がSucc[Succ[Zero]]
なんてふうに表現できるようになりませんか?
どんどん長くなっていくけどこの際気にしない。
2. サイズを型引数にもつリストを定義
これが正しく動作するのかを試すために、Listを定義してみます。
名前は標準ライブラリとかぶらないように適当につけます。
trait SizedList[S <: Size, +A] { def :+:[B >: A](b: B): SizedList[Succ[S], B] = new :+:(b, this) } case object SizedNil extends SizedList[Zero, Nothing] case class :+:[S <: Size, A](head: A, tail: SizedList[S, A]) extends SizedList[Succ[S], A] object SizedList { def empty[A]: SizedList[Zero, A] = SizedNil }
replで動作確認。。。
scala> val a = SizedList.empty[Int] a: SizedList[Zero,Int] = SizedNil scala> val b = 2 :+: SizedList.empty b: SizedList[Succ[Zero],Int] = :+:(2,SizedNil) scala> val c = 9 :+: 1 :+: 2 :+: 4 :+: 7 :+: SizedList.empty c: SizedList[Succ[Succ[Succ[Succ[Succ[Zero]]]]],Int] = :+:(9,:+:(1,:+:(2,:+:(4,:+:(7,SizedNil)))))
とりあえず問題なく動いてはいそうです。
まだ値を取り出すことすらできませんが。
次回予告(いつ!?)
次回はこれにhead
やtail
を足してみましょう。
まだ漠然としか考えていませんが、値がOrdered
じゃないとソートできないとかそういうのと同じ機構を使えばできそうです。
reverse
が定義できればきっとそれなりのことができるはずです。
Scala勉強会で発表してきた。
初スライド!!!
for
の使い方について。
http://kazzna.jp/slide/scala_for/index.html
Haskellのモナド系ブログが大体doを理解するならStateだってなってたので便乗です。
勉強会でも口頭で言ったけれど、 flatMapの定義をするなら Monad則 を覚えないと危険です!!
あと、カッコの書き方について教わったのであとであとでスライドをなおす予定。
次回のスライドを作成中。
Scalaの型推論って難しい
ラムダ計算とかの例によくある チャーチ数 をScalaで試してみる。
原点を表す Zero
、次を表す Succ
を定義しておけば足し算とかできるってやつ。
まずは試し書き
import Conrtol.Applicative zero f a = a succ n f a = f $ n f a one = succ zero two = succ one add a b = liftA2 (.) a b toInt n = n (\a -> a + 1) 0 toAs n = n (\a -> 'a' : a) [] a = add (succ two) one main = do putStrLn $ show $ toInt a // => 4 pusStrLn $ show $ toAs a // => "aaaa"
わざと型は明記していない。haskellなどのきちんと型推論してくれる言語であればこれで動く。
もしくは動的型の言語でも。
def zero(f): def x(a): return a return x def succ(n): def x(f): def y(a): return f(n(f)(a)) return y return x one = succ(zero) two = succ(one) def add(n): def x(m): def y(f): def z(a): return n(f)(m(f)(a)) return z return y return x def to_int(n): return n(lambda x: x + 1)(0) def to_as(n): return n(lambda x: "a" + x)("") a = add(succ(two))(one) to_int(a) # => 4 to_as(a) # => 'aaaa'
Scalaの場合
これがScalaだとまあ大変で。。。
def zero[A](f: A => A)(a: A) = a
この時点で上記の例ではなんでもよかった(使ってない) f
の型を固定。
固定しない場合型指定が大変になる(後述)。
なお、戻り値の型は書くべきだと思うけれども、今回は型推論具合を知りたいから書かなくても動作する限り省略。
def succ[A](n: (A => A) => A => A)(f: A => A)(a: A) = f(n(f)(a)) def one[A]: (A => A) => A => A = succ(zero) def two[A]: (A => A) => A => A = succ(one) import scalaz._ import Scalaz._ // Applicativeの導入(必須ではないけど・・・) def add[A](x: (A => A) => A => A)(y: (A => A) => A => A) = (x |@| y) { case (a, b) => a compose b } def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0) def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty) // def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!! def a[A]= add[A](succ(two))(one) println(toInt(a)) // => 4 println(toAs(a)) // => List(a, a, a, a)
型が必須な上に、 val
が型引数を受け付けてくれないからギリギリまで def
じゃないといけない。
できる限りHaskellでやった時の型に近づけると以下のような感じ?
def zero[F, A](f: F)(a: A) = a def succ[A, B, C](n: (B => C) => A => B)(f: B => C)(a: A) = f(n(f)(a)) def one[A, B]: (A => B) => A => B = succ(zero) def two[A]: (A => A) => A => A = succ(one) def add[A, B, C, D](x: A => B => C)(y: A => D => B) = (x |@| y) { case (a, b) => a compose b } def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0) def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty) // def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!! def a[A] = add[(A => A), A, A, A](succ(two))(one) println(toInt(a)) // => 4 println(toAs(a)) // => List(a, a, a, a)
途中から A => A =>...
みたいなのを書くことを考えれば、最初の方が綺麗な気がする。。。
最後に蛇足だけど引数の順序をScalaっぽくしてみる。
def zero[A, F](a: A)(f: F) = a def succ[A, B, C](n: A => (B => C) => B)(a: A)(f: B => C) = f(n(a)(f)) def one[A, B]: A => (A => B) => B = succ(zero) // def one[A, B] = succ[A, A, B](zero) _ // <- これでも通るのは通るみたい def two[A]: A => (A => A) => A = succ(one) // def two[A] = succ[A, A, A](one) _ def flip[A, B, C](f: A => B => C) = (b: B) => ((a: A) => f(a)(b)) // これどっかにないのかな??? // scalaz.Function2Optsならあるけど・・・ uncurriedみたいなのが必要だよなぁ。 def add[A, B, C, D](x: A => B => C)(y: C => B => D) = flip((flip(x) |@| flip(y)) { case (a, b) => a andThen b }) def toInt(n: Int => (Int => Int) => Int) = n(0)(a => a + 1) def toAs(n: List[Char] => (List[Char] => List[Char]) => List[Char]) = n(List.empty[Char])(a => 'a' :: a) // def a[A]: A => (A => A) => A = add(succ(two))(one) // => 型エラー!! def a[A] = add[A, (A => A), A, A](succ(two))(one) println(toInt(a)) // => 4 println(toAs(a)) // => List(a, a, a, a)
何度 A =>
と書いただろう・・・???
atomのMarkdown Previewのフォント設定
atomエディタのMarkdownプレビューのCSS設定がいつの間にか変わっていたのでメモ。
目的は日本語のフォントにしたい。デフォルトだと中国系だからね
検索するといくつか出てくるけど、 setting
-> Open Config Folder
で、
styles.less
を修正する。
/* * Your Stylesheet * * This stylesheet is loaded when Atom starts up and is reloaded automatically * when it is changed. * * If you are unfamiliar with LESS, you can read more about it here: * http://www.lesscss.org */ .tree-view { font-family: "Migu 1C", sans-serif; } // Markdown Preview .markdown-preview { font-family: "Migu 1C", sans-serif; h1, h2, h3, h4, h5, h6 { font-family: "Migu 1C", sans-serif; } code, blockquote, atom-text-editor { font-family: "Migu 1M", "Meiryo", monospace; } }
.tree-view
はどこのことかわからないのでとりあえず書いただけだけど、
重要なのは .markdown-preview
って方。
ここに font-family
で好きなフォントを指定。
これで基本的には変わるんだけど、コード部分とかのバッククォート部分が変わらない。
なのでmarkdown-previewのスタイルを確認。
code
とか blockquote
とかが怪しいと思い追加してみるも変わらなかった。
結果的には atom-text-editor
ってのがコード部分らしい。
まあ、とりあえず全部書いておいた。
Python入門のためtoolz入れてみた
pythonは文法が単純で覚えやすいらしい。
class MyClass: def __init__(self, init_value): self.value = init_value def my_method(self, value): return self.value + value instance = MyClass(5) print instance.my_method(3) # => 8 print MyClass.my_method(instance, 3) # => 8
あと、なんか特別な意味を持つメソッドは全部 __???__
みたいに _
2つで囲まれてるらしい。
ということで入門。
細かいことを気にせずtoolzというライブラリを入れる。
・・・ために準備
pyenvのインストール
pythonはシステムに食い込んでいるらしいので、別のpythonを気軽にいじれるツールを導入。
> git checkout https://github.com/yyuu/pyenv.git ~/.pyenv > echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile > echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile > echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
if文とかは適当に。
> git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv > echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile
ローカルpythonのインストール
好きなバージョンのpythonをインストールできる。 今回はやむにやまれぬ事情で2系
> pyenv install 2.7.9 > pyenv virtualenv 2.7.9 my-env-name-2.7.9
上がpythonのインストールで、下が依存ライブラリが入る環境のインストール。 プロジェクト毎に依存関係を分けるためのやつ。
で、あとは自分のプロジェクトフォルダを作って、バージョン指定
> mkdir /my/project/root/dir > cd /my/project/root/dir > pyenv local my-env-name-2.7.9
最後の指定を行うことで、このディレクトリでのpythonが自動的に指定したバージョンになる。
pythonを使ってみる。
何はともあれtoolzをインストール。
> pip install toolz > pip freeze > freeze
これで my-env-name-2.7.9
にだけtoolzが入ったはず。
python
コマンドでpythonインタープリターを起動し試してみる。
>>> import toolz >>> toolz.map(lambda x: x + 1, [1, 3, 5, 7, 9]) <itertools.imap object at 0x7f3cfdf1af90> >>> for a in toolz.map(lambda x: x + 1, [1, 3, 5, 7, 9]): ... print a ... 2 4 6 8 10 >>> exit()
ひとつ上のディレクトリーに移動して、同じく実行。
>>> import toolz Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named toolz >>> exit()
いい感じ。
あと、ファイルにコードを書くときのshebangはenvを使わないといけないみたいなので注意。
#! /usr/bin/env python # coding: utf-8
toolzを使ってみる。
toolzは難しいプログラミングを簡単にしてくれるためのツールで、swiftzみたいなものらしい。
さっき使ったmapなど、遅延実行してくれるのででっかいファイルの処理するのに使用した。
>>> infile = open('inflie.txt', 'r') >>> lines = toolz.map(some_function, infile) >>> outfile1 = open('outfile1.txt', 'w') >>> outfile2 = open('outfile2.txt', 'w') >>> for line in lines: ... if some_condition(line): ... outfile1.write(line) ... else: ... outfile2.write(line) ... >>> outfile2.close() >>> outfile1.close() >>> infile.close()
まあtryとかは適時使用するとして、こんな感じでやった時に、for式のlineを取り出した段階で初めて some_function
が実行される。
toolz.map
で帰ってくる itertools.imap object
ってやつは1回しか評価できない。やっててこれでハマった。
list(map(f, [1, 2, 3]))
とかすればlist化できるけど、これだと普通の組み込みのmapでいい気がするし、どうするのがベストなんだろう?
で、ここで問題はコードに出てきた some_function
。
map
の第1引数は引数を1個とって戻り値も1個の関数なので、複雑な処理をしようと思うと長い複雑な関数が必要になる。
関数合成
それは困るので、toolzを使って関数の合成。
>>> a = lambda x: x + 1 >>> b = lambda x: x * 3 >>> c = lambda x: x - 2 >>> x = toolz.compose(a, b, c) >>> x(3) # == a(b(c(3))) 4
これで複数の関数をつなげて実行できるんだけど、どうも使いづらい。 だって、aから並べたらaから実行してほしいじゃない?
これに近い関数にpipeがある。
>>> toolz.pipe(2, a, b, c) # == c(b(a(2))) 7
でもこれは可変長引数の関係からか評価する値を先に書かなくちゃダメ。 仕方がないので新しい関数を定義してみた。
def chainf(*f): from toolz import pipe return lambda x: pipe(x, *f)
>>> x = chainf(a, b, c) >>> x(2) # == c(b(a(2))) 7
これ、元からあるなら誰か教えて。。。
複数の関数がつなげられるようになったので、次の問題に挑戦。
部分適用
map
は引数1の戻り値1なので、二つの引数を受け取るような関数が使えない。
これを解決するためには、部分適用を使用する。 そうすればテストするときは関数単位でテストできるし、合成もできるので便利なはず。
def with_contains(values, target): if target in values: return (target, True) else: return (target, False)
この例だとif文いらないけど、まあもう少し複雑な前提で。
これを toolz.map
で使って各値をtupleにしたいと。
まあ、lambdaとか使ってvaluesを固定してしまえばいいんだけど、toolzには便利な curry
があるので使おう。
>>> w = toolz.curry(with_contains) # w(values)(target) == with_contains(values, target) >>> x = w([1, 2, 3]) >>> x(2) (2, True) >>> x(4) (4, False)
curry
は複数引数の関数を1引数で1戻り値(関数が帰ってくる)の関数に変えた関数を返してくれる。
>>> uc = lambda a, b, c, d: a + b + c + d >>> cd = curry(uc) # uc(a, b, c, d) == cd(a)(b)(c)(d)
これで1引数まで持っていければ、小さなテスト済みの関数の組み合わせで最初の map
が実行できる。
もちろんoutputのファイルに書き込む部分も関数化できるんだけど、テスト方法がよくわからないからループしてログに書くだけにした。
toolz.do
とかを使えば処理自体は難しくないんだけど、ファイル出力のテストってどうやるのかがわからない。
テストの書き方勉強会とかないかなぁ。
ハノイの塔をScalaで解く
ハノイの塔自体は再帰を使えば簡単に解けることは有名で、Googleれば大体すぐ出てくる。
def hanoi(height: Int, from: String, via: String, to: String): Unit = { if (height <= 0) { () } else { // 1. スタート位置にある一番下以外のものをすべて関係のない場所に動かして hanoi(height - 1, from, to, via) // 2. スタート位置にある一番下を目的地に動かして println(s"Move disk ${height} from ${from} to ${to}.") // 3. `1`で関係のないところによけておいたものを全て目的地に移動させる。 hanoi(height - 1, via, from, to) } } > hanoi(3, "left", "middle", "right") Move disk 1 from left to right. Move disk 2 from left to middle. Move disk 1 from right to middle. Move disk 3 from left to right. Move disk 1 from middle to left. Move disk 2 from middle to right. Move disk 1 from left to right.
ただ、このままだと末尾再帰でないという理由でいつかはスタックが足りなくなるはず。 それより前に計算量が大きすぎて帰ってこなくなりそうな気もするけど。。。
ということで末尾再帰にしたい!たぶん。
関数らしくするために、値を返そう。
case class HanoiStep(height: Int, from: String, via: String, to: String) def hanoi1(height: Int, from: String, via: String, to: String) = (HanoiStep, String, HanoiStep) = { val pre = HanoiStep(height - 1, from, to, via) val move = s"Move disk ${height} from ${from} to ${to}." val post = HanoiStep(height - 1, via, from, to) (pre, move, post) }
Tree構造!!
自分でprintすべきものがrootにあって、左に自分より前にprintすべきものの塊、右に自分より後のがある。
左の枝、自分自身、右の枝の順で走査していけば結果になるはず!!
・・・Treeの走査って再帰でどうやるのん??? わからないから後で考えるとして、一番左の要素だけ展開すれば末尾再帰できるはずだからそれをやろう。
import scala.annotation.tailrec sealed trait HanoiStep case class MultiSteps(height: Int, from: String, via: String, to: String) extends HanoiStep case class SingleStep(id: Int, from: String, to: String) extends HanoiStep def expand(ms: MultiSteps): List[HanoiStep] = ms.height match { case n if n <= 0 => List.empty case 1 => List(SingleStep(1, ms.from, ms.to)) case _ => { val pre = MultiSteps(ms.height - 1, ms.from, ms.to, ms.via) val ss = SingleStep(ms.height, ms.from, ms.to) val post = MultiSteps(ms.height - 1, ms.via, ms.from, ms.to) List(pre, ss, post) } } @tailrec def expandHead(ss: List[HanoiStep]): List[HanoiStep] = ss match { case ((h: MultiSteps) :: t) => expandHead(expand(h) ++ t) case _ => ss } > val a = MultiSteps(5, "left", "middle", "right") a: MultiSteps = MultiSteps(5,left,middle,right) > val b = expand(a) b: List[HanoiStep] = List(MultiSteps(4,left,right,middle), SingleStep(5,left,right), MultiSteps(4,middle,left,right)) > val c = expandHead(b) c: List[HanoiStep] = List(SingleStep(1,left,right), SingleStep(2,left,middle), MultiSteps(1,right,left,middle), ....
これをとりあえずStreamでくるめば、それっぽくは動きそう。
def unfold[A, B](a: A)(f: A => Option[(B, A)]): Stream[B] = f(a) match { case Some((b, a)) => Stream.cons(b, unfold(a)(f)) case _ => Stream.empty } def hanoi(height: Int, from: String, via: String, to: String): Stream[String] = { import scala.annotation.tailrec sealed trait HanoiStep case class MultiSteps(height: Int, from: String, via: String, to: String) extends HanoiStep case class SingleStep(id: Int, from: String, to: String) extends HanoiStep def f1(ms: MultiSteps): List[HanoiStep] = ms.height match { case n if n <= 0 => List.empty case 1 => List(SingleStep(1, ms.from, ms.to)) case _ => { val pre = MultiSteps(ms.height - 1, ms.from, ms.to, ms.via) val ss = SingleStep(ms.height, ms.from, ms.to) val post = MultiSteps(ms.height - 1, ms.via, ms.from, ms.to) List(pre, ss, post) } } @tailrec def f2(ss: List[HanoiStep]): List[HanoiStep] = ss match { case ((h: MultiSteps) :: t) => f2(f1(h) ++ t) case _ => ss } val init = f1(MultiSteps(height, from, via, to)) unfold(init)(a => f2(a) match { case (h: SingleStep) :: t => Some((s"Move disk ${h.id} from ${h.from} to ${h.to}.", t)) case _ => None }) } > val a = hanoi(3, "left", "middle", "right") a: Stream[String] = Stream(Move disk 1 from left to right., ?) > a.foreach(println) Move disk 1 from left to right. Move disk 2 from left to middle. Move disk 1 from right to middle. Move disk 3 from left to right. Move disk 1 from middle to left. Move disk 2 from middle to right. Move disk 1 from left to right. > val b = hanoi(4, "left", "middle", "right").zipWithIndex b: scala.collection.immutable.Stream[(String, Int)] = Stream((Move disk 1 from left to middle.,0), ?) > val c = b.map { case (a, b) => s"${b + 1}: ${a}" } c: scala.collection.immutable.Stream[String] = Stream(1: Move disk 1 from left to middle., ?) > c.foreach(println) 1: Move disk 1 from left to middle. 2: Move disk 2 from left to right. 3: Move disk 1 from middle to right. 4: Move disk 3 from left to middle. 5: Move disk 1 from right to left. 6: Move disk 2 from right to middle. 7: Move disk 1 from left to middle. 8: Move disk 4 from left to right. 9: Move disk 1 from middle to right. 10: Move disk 2 from middle to left. 11: Move disk 1 from right to left. 12: Move disk 3 from middle to right. 13: Move disk 1 from left to middle. 14: Move disk 2 from left to right. 15: Move disk 1 from middle to right.
とりあえず動いた。
ツリーを走査する簡単なやり方とか、もしくは継続?かなんかのテクニックを使えばもっとシンプルになるんだろうなぁ。
続きはまた次回。