チャネルを使ってデータ交換

これまで、チャネルを使った例をいくつか見てきたけど、今回はもうちょっと意味のあるデータを通信してみる。

ゴルーチンで起動される関数 sendColor は送信用のチャネル ch に、色名(文字列)と返信用のチャネルをまとめた Req 構造体を送信する。で、それを n 回繰り返したら chnil を送って終了する。
receiveColor はチャネル ch から Req が送られてきたら、その色名を出力してから返信用のチャネル Req.Reply0 を送信する。これは処理が完了したことを知らせるだけなので、0 でなくても int なら何でもいい。で、それを n がゼロになるまで繰り返す。この n は起動されたゴルーチンの数だ。

では、実行例。

^o^ > go run channel_color.go
blue
red
green
blue
red
green
blue
red
green
blue
red
green
blue
red
green
blue
red
green
red
green
red

Go言語で画像フォーマットのチェック

たまたま見つけたので、メモしておく。

 cf. Go言語で画像ファイルか確認してみる – Qiita

画像を扱うには image パッケージを利用する。が、具体的なフォーマットを扱うにはそれぞれに対応したパッケージの import が必要。直接には使わないけど image パッケージの関数で内部的に使われる。注意しなきゃいけないのは、普通に import すると、インポートしてるのに使ってないぞエラーが出ること。そこで下のコードのように、パッケージ名の前に _ をつけている。

それから、標準で対応しているのは、JPEG, PNG, GIF のみ。ほかに BMP と TIFF が外部のパッケージを導入すれば対応できる。こんなふうに。

^o^ > go get golang.org/x/image/bmp

^o^ > go get golang.org/x/image/tiff

で、実行例。

^o^ > go run imgcheck.go sample.jpg
jpeg

^o^ > go run imgcheck.go sample.png
png

^o^ > go run imgcheck.go sample.bmp
bmp

repeated_combination

Ruby の Array#repeated_combination は重複を許す組み合わせを返すメソッドだ(厳密にいうとEnumerator を返す)。
次のブログ記事で見つけた。

 cf. 続:Haskell/Clojureでrepeated_combinationを実装してみる – Programmer’s Note

これを Go でやってみよう、ということで Haskell のコードを参考に作ってみた。

ところが、実行結果が Array#repeated_combination と合わない。
上のコードの実行結果はこう。

^o^ > go run repeated_combination.go
[[1 1 1] [1 1 2] [1 1 3] [1 2 1] [1 2 2] [1 2 3] [1 3 1] [1 3 2] [1 3 3] [2 1 1] [2 1 2] [2 1 3] [2 2 1] [2 2 2] [2 2 3] [2 3 1] [2 3 2] [2 3 3] [3 1 1] [3 1 2] [3 1 3] [3 2 1] [3 2 2] [3 2 3] [3 3 1] [3 3 2] [3 3 3]]

対して Array#repeated_combination はこう。

irb(main):001:0> [1,2,3].repeated_combination(3).to_a
=> [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3], [2, 3, 3], [3, 3, 3]]

順列ではなく組み合わせなので、Array#repeated_combination では例えば [1,1,2] と [1,2,1] と [2,1,1] を同じものとしているのに対して、Go のコードでは別のものとして数え上げてしまっている。
そこで改良を加えたのがこれだ。

実行結果。

^o^ > go run repeated_combination2.go
[[1 1 1] [1 1 2] [1 1 3] [1 2 2] [1 2 3] [1 3 3] [2 2 2] [2 2 3] [2 3 3] [3 3 3]]

今度はうまくいった。

コインの両替

最近見かけたブログの記事。

 cf. Haskell/Clojureで数学パズル:コインの両替 – Programmer’s Note

同じような問題はずっと前に Haskell でやったことがあるんだけど、今回は Go でやってみる。はじめに思い付いたのはリンク記事の後半に出てくる再帰を使ったほうなんだけど、まずはリンク記事と同じ順で for を使ったやり方で。

ああ、ネストした for 文が美しくない!!

で、こっちが再帰を使ったほう。

だいぶマシだけど else if がなあ。改めて Haskell のパターンマッチの美しいことを思い知る。

ゴルーチンとチャネルでジェネレータ

ゴルーチンとチャネルを使ってジェネレータを作ることもできる。

makeGen 関数は無名関数を返す。この無名関数はクロージャになっていて、makeGen 内のゴルーチンからチャネルを使って受け取った値を返す。ゴルーチンは二分木の値を昇順に返すので、結果として無名関数も呼び出しごとに同じ順で値を返すことになる。
実行してみよう。

^o^ > go run go_gen.go
0
1
2
3
4
5
6
7
8
9
<nil>

最後の <nil> は何だろう?

チャネルとrange

チャネルから送られてくるデータは、for ループの range で受けることもできる。

チャネルの場合、スライスやマップと違って返り値は多値ではない。あと、チャネルにデータを送る側では、送信が終わったらチャネルを close する必要がある。
次の例は、二分木にチャネルを使ってデータを送信する each 関数を追加したものだ。

for ループの range には二分木の each 関数を呼び出していて、その each 関数では、データを一つずつチャネルに送信し、最後にチャネルを close している。
実行結果はこう:

^o^ > go run go_range.go
0
1
2
3
4
5
6
7
8
9

ゴルーチンの同期

チャネルを使ってゴルーチンの同期をとることができる。
次のコードを見てほしい。

makeRoutine 関数は、文字列 code とチャネル in を引数にとる。まずチャネル out を作っておいて、無名関数をゴルーチンで起動、最後に out を返している。無名関数は無限ループになっていて、in から何か送信されてくるのを待って code を出力し、200 ミリ秒待ってから out にデータを送信する。
main 関数では、最初の(送信用)チャネル ch1 を作り、makeRoutine 関数に渡している。返ってきたチャネルは、次の makeRoutine 関数に渡され、返ってきたチャネルはさらに次の makeRoutine 関数に渡される。こうして5つの makeRoutine 関数の中のゴルーチンがチャネルを通じて数珠つなぎのようになる。これらのゴルーチンは、最初のチャネル ch1 にデータを送信することによって動作を開始し、最後のチャネル(main 関数から見ると ch6)にデータを送信して終わる。これを10回繰り返している。

実行してみよう。

^o^ > go run go_hey.go
hey! hey! hey! hey! hey! hey! hey! hey! hey! hey!

この実行例ではわからないけど、1文字ずつ時間を空けて hey! の文字列が10回出力されている。各ゴルーチンは1文字出力するだけなので、うまく同期して動作している様子がわかる。

sync.WaitGroup

ゴルーチンの終了待ちには、チャネルを使うほかに sync パッケージの WaitGroup を使う方法もある。
使い方はこうだ:

  1. sync.WaitGroup の変数を作る
  2. その変数に、終了待ちをするゴルーチンの数を設定する
  3. ゴルーチンを呼び出す。このとき、sync.WaitGroup の変数を渡す
  4. ゴルーチン側では、終了したら Done 関数を呼ぶ
  5. メインルーチン側で、Wait 関数を呼ぶ

実際に試してみよう。

^o^ > go run go_waitgroup.go
0 baz
0 bar
0 foo
1 baz
1 bar
1 foo
2 baz
2 bar
2 foo
3 baz
3 bar
3 foo
4 baz
4 foo
5 baz
5 foo
6 baz
7 baz

チャネルとゴルーチンの終了待ち

チャネルはゴルーチンの間で通信するためのデータだ。次のように生成する。

T はチャネルでやり取りするデータの型、bufsize はデータを格納するバッファのサイズで省略すると 0 になる。チャネルの型は chan T。
関数の引数や変数の型指定の時、chan の前に <- をつけると受信専用に、後に <- をつけると送信専用になる。 チャネルを使うと、ゴルーチンの終了待ちができるようになる。 次の例では test 関数をゴルーチンとして呼び出し、チャネルを渡している。test 関数は 0.5 秒間隔で name を出力し、終了するときにチャネルを通じて name を送ってくる。main 関数ではチャネルからデータが送られてくるのを待っている。

実行してみよう。

^o^ > go run go_channel.go
1 foo
1 baz
1 bar
2 foo
2 baz
2 bar
3 foo
3 baz
3 bar
4 foo
4 baz
4 bar
5 foo
5 baz
bar
6 foo
6 baz
foo
7 baz
8 baz
baz

数字とともに出力されているのが test 関数内で出力したもの、数字のないのがゴルーチンが終了した後に main 関数で出力したものだ。3つのゴルーチンが並行して動き、main 関数ではその終了を待っていることがわかる。

reverse

Go でスライスを逆順にしたかったんだけど、そういう関数は用意されてないようだ。じゃあどうするかというと for 文を使ってひとつずつ入れ替えてくしかないみたい。こんなふうに。

^o^ > go run reverse.go
[5 4 3 2 1]

文字列を逆順にするにはいったん rune 型のスライスにしてから。

^o^ > go run reverse_string.go
おえういあ