A Tour of Go の Exercise: Slices 解答例

はじめに

Go 言語基礎文法最速マスターにて出題されている演習課題「Exercise: Slices(スライス)」の答えに辿り着くまでの手順と解答例の解説を記事にしてみました。pic.Show 関数、NewNRGBA 関数などについても簡単に説明しています。

課題

課題の目的は main 関数の pic.Show 関数で画像が出力されるように Pic 関数を実装することです。

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
	pic.Show(Pic)
}

解答例

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for x := range s {
		s[x] = make([]uint8, dx)
		for y := range s[x] {
			s[x][y] = uint8((x + y) / 2)
		}
	}
	return s
}

func main() {
	pic.Show(Pic)
}

解説

初めに、課題のソースコードを読み解きます。

3 行目、golang.org/x/tour/pic パッケージをインポートしています。この中で pic.Show 関数が定義されています。

import "golang.org/x/tour/pic"

5 〜 6 行目、Pic 関数が定義されています。ここに pic.Show 関数で画像が出力されるように処理を実装します。

func Pic(dx, dy int) [][]uint8 {
}

8 〜 10 行目、main 関数で pic.Show 関数が呼び出されています。

func main() {
	pic.Show(Pic)
}

さて、実装を進める前に pic.Show 関数の振る舞いについて理解する必要があります。

pic.Show 関数は実行すると引数 f の値を基に画像を出力します。引数 fPic 関数の Function value(関数値)ですね。

func Sh​​ow(f func(dx、dy int)[] [] uint8)

Pic 関数の引数 dx, dy は何を受け取るのでしょうか。

答えは Show 関数の ソースコード にあります。

func Show(f func(dx, dy int) [][]uint8) {
	const (
		dx = 256
		dy = 256
	)
	data := f(dx, dy)
	m := image.NewNRGBA(image.Rect(0, 0, dx, dy))
	for y := 0; y < dy; y++ {
		for x := 0; x < dx; x++ {
			v := data[y][x]
			i := y*m.Stride + x*4
			m.Pix[i] = v
			m.Pix[i+1] = v
			m.Pix[i+2] = 255
			m.Pix[i+3] = 255
		}
	}
	ShowImage(m)
}

Pic 関数の実引数には int 型の 256 で初期化した定数 dx, dy が渡されます。この値は 0 で完全な青、255 で完全な白を意味しています。

その後、image パッケージの NewNRGBA 関数と Pic 関数の戻り値を組み合わせた画像を ShowImage 関数で出力しています。

もうちょい掘り下げて説明すると、変数 m には NRGBA 構造体のポインタが格納されていて、フィールドの m.Pix には R(Red), G(Green), B(Blue), A(Alpha)の順序で画像のピクセルが保持されています。つまり、画像の R, G に Pic 関数の戻り値を格納して出力している仕掛けになっています。

では、pic.Show 関数の正体がわかったところで実装を進めます。

やることは、長さ dy のスライスに各要素が uint8(8bit の unsigned int)型で長さ dx のスライスを割り当てたものを返すように実装することです。

まずは、make 関数で長さ dy[][]uint8 型スライスを作成します。

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	return s
}

変数 s の各要素に長さ dx[]uint8 型スライスを割り当てたいので for-range で各要素を取り出します。

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for x := range s {
	}
	return s
}

各要素に長さ dx[]uint8 型スライスを割り当てまして。

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for x := range s {
		s[x] = make([]uint8, dx)
	}
	return s
}

さらに for-range で各要素に対して (x + y) / 2x * y, x ^ y を算出した値を格納して実装は完了です。^ は冪乗のことです。

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for x := range s {
		s[x] = make([]uint8, dx)
		for y := range s[x] {
			s[x][y] = uint8((x + y) / 2)
		}
	}
	return s
}

はい、できあがり!

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for x := range s {
		s[x] = make([]uint8, dx)
		for y := range s[x] {
			s[x][y] = uint8((x + y) / 2)
		}
	}
	return s
}

func main() {
	pic.Show(Pic)
}

(x + y) / 2 の実行結果は下記のようになります。

x * y の実行結果は下記のようになります。

x ^ y の実行結果は下記のようになります。

以上です。

おわりに

個人的には x ^ y の画像が好きです。