アルゴリズム ビジュアル大辞典のアルゴリズムをPython, Goで実装する [線形探索、二分探索]
これらの続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
やること
こちらの書籍に記載されるアルゴリズムをPython, Goで実装してみます。
(Goはまだまだホントに初学者です)
- 作者:渡部 有隆,ニコライ・ミレンコフ
- 発売日: 2020/03/20
- メディア: Kindle版
リバース
配列など指定された範囲の要素を逆順にすること。
ちょうど真ん中の要素は逆順にならないのでそのまま固定する。
真ん中を軸として対応する左右2つの要素を交換(スワップ)する。
計算量は配列のサイズの半分O(N/2) つまりO(N)である。
N個の要素をもつ配列に対して、左右それぞれの要素をi, j とすると、
i = 0, 1, 2, 3... N/2-1
j = N - (i + 1) = N - i - 1
となる。
実装例
アルゴリズム ビジュアル大辞典のアルゴリズムをPython, Goで実装する [線形探索、二分探索]
こちらの続きです。
やること
こちらの書籍に記載されるアルゴリズムをPython, Goで実装してみます。
(Goはまだまだホントに初学者です)
- 作者:渡部 有隆,ニコライ・ミレンコフ
- 発売日: 2020/03/20
- メディア: Kindle版
線形探索
キーの値が見つかるまで配列の最初から順に見ていく。最悪の場合は配列内の最後の要素まで見ていく。
計算量=O(N)となり、配列内の要素分だけ計算量も増える。
二分探索
配列の中央に分割するステップを繰り返しながらキーの値を見つける。
最初の中央値よりキーの値が大きければ、中央値以上、そうでなければ中央値以下の範囲を見ていく。
計算量=O(logN)となり、線形探索と比べ劇的に計算量が減る。
計算ステップはlog2Nだが定数は無視する。
実装例
Pythonでは簡単に連番となる配列を作ることができるけど、Goでは正直楽な作り方がわからなかったので、適当な配列としています(涙)。(ループで追加していけばできるんだろうけど面倒でした)
したがって、Pythonでは1000〜1億個もの配列を用意して線形探索・二分探索のそれぞれの計算速度の違いも計測しました。
Goではしていません笑。
import time def LinearSearch(array, key): for i in range(len(array)): if array[i] == key: return i, array[i] return None, None def BinarySearch(array, key): left = 0 right = len(array) while left < right: mid = round((left + right) / 2) if array[mid] == key: return mid, array[mid] elif array[mid] < key: left = mid + 1 else: right = mid return None, None if __name__ == '__main__': for i in [1000, 10000, 100000, 1000000, 10000000, 100000000]: r = list(range(1, i, 1)) print(f"array len: {i}") start = time.perf_counter() idx, val = LinearSearch(r, 671) print("index:{0} value: {1}".format(idx2, val2)) print("Linear Seach") end = time.perf_counter() print(start - end, "\n") start2 = time.perf_counter() idx2, val2 = BinarySearch(r, 671) print("Binary Seach") print("index:{0} value: {1}".format(idx2, val2)) end2 = time.perf_counter() print(start2 - end2, "\n") """ array len: 1000 index:670 value: 671 Linear Seach -6.370099999999934e-05 Binary Seach index:670 value: 671 -3.961000000000242e-05 array len: 10000 index:670 value: 671 Linear Seach -5.716499999999791e-05 Binary Seach index:670 value: 671 -1.4183000000000945e-05 array len: 100000 index:670 value: 671 Linear Seach -6.525899999999807e-05 Binary Seach index:670 value: 671 -1.769600000000454e-05 array len: 1000000 index:670 value: 671 Linear Seach -6.603499999999207e-05 Binary Seach index:670 value: 671 -2.3250000000002435e-05 array len: 10000000 index:670 value: 671 Linear Seach -5.8237000000016526e-05 Binary Seach index:670 value: 671 -2.2823999999976863e-05 array len: 100000000 index:670 value: 671 Linear Seach -5.810900000025043e-05 Binary Seach index:670 value: 671 -2.6684000000276598e-05 """
package main import ( "fmt" ) func LinearSearch(array []int, key int) (int, int) { for i := 0; i < len(array); i++ { if array[i] == key { return i, array[i] } } return 0, 0 } func BinarySearch(array []int, key int) (int, int) { left := 0 right := len(array) for left < right { mid := (left + right) / 2 if array[mid] == key { return mid, array[mid] } else if array[mid] < key { left = mid + 1 } else { right = mid } } return 0, 0 } func main() { A := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println(LinearSearch(A, 6)) fmt.Println(BinarySearch(A, 6)) }
アルゴリズム ビジュアル大辞典のアルゴリズムをPython, Goで実装する [スワップソート、合計、最小・最大値の値とインデックス取得]
何をやるか
先日販売されたこちらの書籍より、基礎的なアルゴリズムを実装してみたいと思い立ったので、Python, Goで実装にトライしてみます。
- 作者:渡部 有隆,ニコライ・ミレンコフ
- 発売日: 2020/03/20
- メディア: Kindle版
スワップソート
概要
データの入れ替え(スワップ)を繰り返しながらデータの順序を変えるアルゴリズム。
変数の中身を交換するスワップを行うためには、入れ替え対象となる2つの変数のほかに、どちらかの値を退避させるための箱(変数)も必要となる。
実装例
A = 8 B = 5 C = 2 def swap(x, y): t = x x = y y = t return x, y if __name__ == '__main__': if A > B: A, B = swap(A, B) if B > C: B, C = swap(B, C) if A > B: A, B = swap(A, B) print(A, B, C) # 2, 5, 8
package main import ( "fmt" ) var ( A int = 8 B int = 5 C int = 2 ) func swap(x, y *int) { t := *x *x = *y *y = t } func main() { if A > B { swap(&A, &B) } if B > C { swap(&B, &C) } if A > B { swap(&A, &B) } fmt.Println(A, B, C) // 2, 5, 8 }
メモ
Goの場合、ポインタで操作しながら変数の実体を入れ替えられるが、Pythonの場合、原則参照渡しであるものの、イミュータブルな型については値渡しのような振る舞いになるため、関数内にてSwapした値を返すことにした。
合計(サム)
概要
配列などの複数のデータに対して、全ての値を参照して足し合わせた合計値を得るアルゴリズム。
実装例
A = [1,2,3,4,5,6,7] sum = 0 if __name__ == '__main__': for i in A: sum += i print(sum) # 28
package main import ( "fmt" ) func main() { A := []int{1, 2, 3, 4, 5, 6, 7} total := 0 for i := 0; i < len(A); i++ { total = total + A[i] } fmt.Println(total) // 28
最小(最大)要素の値
概要
配列など複数のデータやデータの集合に対して、全ての値を参照して最小の値(または最大の値)を得るアルゴリズム。
以下の例は最小値を得るもので、配列から1つずつ値を取り出して、初期値との大小関係を比較しながら最小値を格納する変数を更新していく。
実装例
A = [10,23,24,23,21,6,17] minv = A[0] if __name__ == '__main__': print(minv) for i in A: if i < minv: minv = i print(minv) # 6
package main import ( "fmt" ) func main() { A := []int{10, 23, 24, 23, 21, 6, 17} minV := A[0] for i := 0; i < len(A); i++ { if A[i] < minV { minV = A[i] } } fmt.Println(minV) // 6 }
メモ
初期値は何でもいいが、INFを設定したり、対象となる集合値のうち最初の値を設定しておけばいい。
最小値のインデックス取得
概要
今度は値そのものではなく最小(最大)値のインデックス取得をするアルゴリズム。
言語によって書き方は様々だと思うが、関数化して書いてみた。
実装例
def minimum(array): mini = 0 for i in range(len(array)): if array[mini] > array[i]: mini = i return mini, array[mini] A = [9,3,5,7,11,20,12,8] print("index:{} value: {}".format(minimum(A)[0], minimum(A)[1])) # index: 1 value: 3
package main import ( "fmt" ) func minimum(array []int) (idx int, value int) { mini := 0 for i := 0; i < len(array); i++ { if array[mini] > array[i] { mini = i } } return mini, array[mini] } func main() { A := []int{9, 3, 5, 7, 11, 20, 12, 8} idx, val := minimum(A) fmt.Printf("index: %d value: %d", idx, val) // index: 1 value: 3 }
A Tour of Goのざっくりまとめ⑥ <Methods and Interfaces編>
前回までの続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
Methods
Goにクラスは存在しないため、代わりにメソッドが定義できる。
メソッドを定義する際、レシーバを引数に取る。
下記の例では、add関数は引数にVertex型のレシーバを受け取る。
このvからadd関数を呼び出せるため、オブジェクト指向的に見える。
import "fmt" type Vertex struct { X, Y int } // 通常の記法 func add(v Vertex) int{ return v.X + v.Y } // メソッド func (v Vertex) add() int { return v.X + v.Y } func main() { v := Vertex{30, 40} fmt.Println(v.add()) // 70 }
ポインタレシーバ
レシーバがポインタの場合、それはポインタレシーバである。
レシーバ自身のもつ値を変更する際はこちらを使うため、通常の変数レシーバよりも使われることが多いらしい。
下記の例では、Scale関数はVertexのポインタを引数にとるポインタレシーバである。
もしアスタリスクを無くして、変数レシーバにした場合、元のレシーバが指す変数の値自体は変わらない。
type Vertex struct { X, Y int } /* ポインタを引数にとるポインタレシーバ */ func (v *Vertex) Scale(f int) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{30, 40} v.Scale(2) fmt.Println(v) //{60 80} }
ここで、通常ポインタを受け取るためには、アドレスを渡さないといけなかった。
上の例だと、vはただの変数であってアドレスではない。
なのに自動的にポインタレシーバが呼び出され、ポインタのもつ実体も変わった。
理由として、ポインタレシーバを持つ場合、Goは利便性を考慮して、自動的にv.Scale(2)を(&v).Scale(2)と解釈してくれるから。
したがって、わざわざ下記のように書かなくてもいいというわけである。
p := &Vertex{50, 50} p.Scale(2) fmt.Println(*p) //{100 100}
ポインタレシーバを使う理由
- レシーバが指す実体の値を変更するため
- メソッドの呼び出しごとに変数を複製することを避けるため
2つ目は、大きな構造体などを扱う場合にメリットがあるらしい。
Interfaces
メソッドの集まりをinterface型の変数へ持たせることができる。
...とはいえよくわかりません。原文の解説を見てみます。
An interface is a collection of method signatures that a Type can implement (using methods). Hence interface defines (not declares) the behavior of the object (of the type Type).
...やっぱりよくわかりません。
インターフェース上ではメソッドシグネチャを定義します。(※メソッドシグネチャについては下記リンクが参考になりました。)
インターフェースは、メソッド名と引数、返り値から構成されるメソッドシグネチャを提供するものです。
import ( "fmt" "math" ) type geometry interface { area() float64 perim() float64 } type rect struct { height, width float64 } type circle struct { radius float64 } func (r rect) area() float64 { return r.width * r.height } func (r rect) perim() float64 { return 2*r.width + 2*r.height } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perim() float64 { return 2 * math.Pi * c.radius } func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } func main() { r := rect{4, 5} c := circle{8} measure(r) measure(c) }
まず、areaとperimのメソッドをもつインターフェースgeometryを定義する。
続いて、構造体rectとcircleを定義する。
これらを引数とするarea, perimメソッドはインターフェース上に定義されている。
変数がインターフェース型をもつ場合、インターフェース上の各メソッドを呼び出せる。
インターフェース上で宣言したメソッドはすべて実装しないとエラーになる。
空のインターフェース
空のインターフェースは、任意の型の値を保持できます。 (全ての型は、少なくともゼロ個のメソッドを実装しています。)
空のインターフェースは、未知の型の値を扱うコードで使用されます。 例えば、 fmt.Print は interface{} 型の任意の数の引数を受け取ります。
https://go-tour-jp.appspot.com/methods/14
インターフェースがメソッドを持っていない場合、空のインターフェースと呼ばれる。
これを使えば、異なる型のデータを呼び出してプリントすることもできる。
type MyString string type Rect struct { width, height float64 } func explain(i interface{}) { fmt.Printf("value given to explain this function is of type %T with %v\n", i, i) } func main() { ms := MyString("My massage is here!") rect := Rect{30, 40} explain(ms) // value given to explain this function is of type main.MyString with My massage is here! explain(rect) // value given to explain this function is of type main.Rect with {30 40} }
explain関数は空のインターフェースを引数に取るため、string型のMyStringやstruct型のRectも受け取ることができる。
Type Assertion
func main() { var i interface{} = "Hello world" s := i.(string) fmt.Printf("type is %T value is %v\n", s, s) // type is string value is Hello world s2, ok := i.(string) fmt.Println(s2, ok) // Hello world true s3, ok := i.(int) fmt.Println(s3, ok) // 0 false s4 := i.(float64) fmt.Println(s4) // panic: interface conversion: interface {} is string, not float64 }
型アサーションにより、インターフェースのある値を取り出すことができる。
t := i.(T)
インターフェースiにある具体的な型Tを保持する。
そしてTの値をtに代入している。
iがTを保持していれば、tはその値が代入されるが、保持していない場合、返り値にokを加えてテストをしないとpanic errorとなる。
Type switches
複数の型アサーションができる構文。
import "fmt" func do(i interface{}) { switch v := i.(type) { case string: fmt.Printf("Expected String type is %T / value is %v\n", v, v) case int: fmt.Printf("Expected Int type is %T / value is %v\n", v, v) default: fmt.Printf("type is %T / value is %v\n", v, v) } } func main() { do(1) // Expected Int type is int / value is 1 do("OK") // Expected String type is string / value is OK do(true) // type is bool / value is true }
A Tour of Goのざっくりまとめ⑤ <More types: Range, Maps, 関数編>
前回までの続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
Range
配列やスライスなどを単純にfor-loopする場合、ちょっと面倒。
Rangeを使えば簡単に反復処理ができる。
import "fmt" func main() { a := []int{1, 2, 3, 4, 5} for _, value := range a { fmt.Println(value) // 1 2 3 4 5 }
スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。 1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。
https://go-tour-jp.appspot.com/moretypes/16
インデックスを無視したい場合は上記の例のようにアンダーバーを書く。
インデックスだけほしい場合は返す変数を1つにする。(, valueを書かない)
Maps
マップはキーと値とを関連付けする。
ゼロ値はnilで、キーをもっていない。追加もできない。
Pythonの辞書型のイメージと近い気がする。
func main() { v := map[int]string{1: "Mike", 2: "Tony", 3: "Kary"} fmt.Println(v) // map[1:Mike 2:Tony 3:Kary] fmt.Println(v[2]) // Tony
上述の例だとint型のインデックス(キー)にstring型の値なのでスライスでもできるが、
mapsならインデックスにint型以外も指定できる。
mapsのいろんな操作
func main() { v := map[int]string{1: "Mike", 2: "Tony", 3: "Kary"} /* 要素の追加 */ v[4] = "Bob" fmt.Println(v) // map[1:Mike 2:Tony 3:Kary 4:Bob] /* 要素が存在するかチェック */ element, ok := v[2] fmt.Println(element, ok) // Tony true element2, ok2 := v[5] fmt.Println(element2, ok2) // false /* 要素の削除 */ delete(v, 1) fmt.Println(v) // map[2:Tony 3:Kary 4:Bob] }
空のmapを作る方法
空のmapはnilを返す。(ゼロ値=nil)
空のmapに要素を追加しようとすると、エラーが発生する。
スライスと同様の理由で、map自体はどんなデータも保持していなく参照しかしていない、またnilで何も存在していない状態だから、とのこと。
func main() { var m map[string]int fmt.Println(m == nil) // true m["add"] = 200 fmt.Println(m) // ERROR: panic: assignment to entry in nil map }
では、空のmapをどう作るかというと、make関数をつかう。
func main() { m := make(map[string]bool) m["ok"] = true fmt.Println(m) // map[ok:true] }
この場合、変数mはstring型のキーとbool型の値を保持する空のmapである。
また、スライスと同様に、map自体と比較できるのはnilである。
関数
func add(x, y int) int { return x + y } func add_two(x, y int) (int, int) { return x + y, x - y } func add_name(x, y int) (result int) { result = x * y // := を使うとエラー return // Nakid return } func main() { r := add(30, 30) fmt.Println(r) // 60 r2, r3 := add_two(30, 10) fmt.Println(r2, r3) // 40 20 r4 := add_name(50, 50) fmt.Println(r4) // 2500 }
関数内に関数をつくる
芸がない事例ですが、こんな感じ。
func main() { f := func(x int) { fmt.Println("Function is called", x) } f(100) // Function is called 100 /* 他の変数に一旦置かずに、関数を書き、続けて引数を入力こともできる*/ func(x int) { fmt.Println("Function is called", x) }(2000) // Function is called 2000 }
クロージャー
クロージャは、それ自身の外部から変数を参照する関数値です。 この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。
https://go-tour-jp.appspot.com/moretypes/25
簡単に言うと、関数を返す関数みたいな感じでしょうか。
/* int型を返す関数funcを返すincrementGenerator関数 */ func incrementGenerator() func() int { x := 0 return func() int { x++ return x } } func main() { increment := incrementGenerator() fmt.Println(increment()) // 1 fmt.Println(increment()) // 2 fmt.Println(increment()) // 3 }
上記の例だと、毎回incrementGeneratorを呼ぶごとに、それまでに読んだ回数分だけxが既に加算されている。
円の面積を求めるクロージャー
func circleCalc(pi float64) func(radius float64) float64 { return func(radius float64) float64 { return pi * radius * radius } } func main() { circle1 := circleCalc(3) fmt.Println(circle1(2)) // 12 circle2 := circleCalc(3.14) fmt.Println(circle2(2)) // 12.56 }
フィボナッチ数列の実装
最後にあるエクササイズの一例。
// fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { a := 0 b := 1 return func() int { a, b = b, a+b return a } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
参考
A Tour of Goのざっくりまとめ④ <More types: 配列(Arrays), Slice, make編>
前回までの続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
配列
[n]T 型は、型 T の n 個の変数の配列( array )を表します。
func main() { var a [2]int a[0] = 300 a[1] = 500 fmt.Println(a) // [300 500] // 定めたインデックス数以上の数を配列に配置できない a[2] = 1000 // invalid array index 2 (out of bounds for 2-element array) } /* 最初に値を入れる場合 */ func main() { var b [2]int = [2]int{500, 1000} // or c := [3]string{"a", "b", "c"} fmt.Println(b) // [500, 1000] fmt.Println(c) // [a, b, c] }
- 同じデータ型を格納する
- デフォルト値はデータ型に応じて変わる(int=0, string="", etc..)
- 配列は最初に定めたサイズを変えることができない。つまり固定長である。
- 可変長にしたい場合、後述のSliceを使う。
Automatic array length declaration
[n]Tにおけるnを定めなくとも、配列数を推論してくれるもの。
この場合、[...]Tと書く。
func main(){ i := [...]int{ 1,2,3,4, } s := [...]string{ "japan", "korea", "thailand", "usa", } fmt.Println(i) // [1 2 3 4] fmt.Println(s) // [japan korea thailand usa] }
配列同士の比較
配列同士の比較では、データ型、配列数、要素の順序が合致していないと同じとみなされない。
func main(){ a := [3]int{1,2,3} b := [3]string{"1", "2", "3"} c := [3]int{2,1,3} fmt.Println(a==b) // (mismatched types [3]int and [3]string) fmt.Println(a==c) // false }
Multi-demantional array
後述のスライスにも書いていますが、配列内配列も下記のように作れる。
ただし、配列内はすべて同じデータ型である必要がある。
[n][m]T型は、m個の要素をもつ配列がn個あることを表します。
func main(){ a := [2][3]int{ [3]int{1,2,3}, [3]int{4,5,6}, } fmt.Println(a) // [[1 2 3] [4 5 6]] /* short syntaxとしてこのようにも書ける */ a := [3][2]int{{1,2},{3,4},{5,6}} fmt.Println(a) // [[1 2] [3 4] [5 6]] }
多次元配列の要素を1つずつFor loopで取り出す場合の一例
func main(){ a := [3][2]int{{1,2},{3,4},{5,6}} for _, outsideArray := range a{ for _, insideArray := range outsideArray{ fmt.Println(insideArray) // 1,2,3,4,5,6 } } }
Slice
型 []T は 型 T のスライスを表します。
- スライスは配列への参照のようなもの
- 同じデータ型である必要はあるものの、配列内要素数の制約がない。(先述のように、異なるデータ型でもいいのはstruct)
- 可変長配列であるスライスでは、[]の中に配列数を指定しない
- 後から配列に要素を追加することもできる。また、配列内配列をつくることもできる
- インデックス指定で値を取り出す場合、最後の要素は含まれない (ex: 1:4なら1~3まで)
- スライスの要素を変更すると元の対応する配列要素も変更される
- スライスのゼロ値はnil (配列との違い。配列は定めた要素数分の初期値を返す)
また、おそらく、Pythonのように、[-1]といった取り出し方はできない模様。
スライスが配列への参照であること
スライスは配列を参照しているので、スライス自体がデータを持つのではなく、配列にあるデータをもっている。
var s []intのような、配列内にデータがない場合は、スライスは配列を参照していないため、nilが返される。
また、スライス内のある要素を書き換えた場合、その更新は元の配列へも反映される。
func main() { sl := []int{1, 2, 3, 4, 5, 6} fmt.Println(sl) // [1 2 3 4 5 6] fmt.Println(sl[1]) // 2 fmt.Println(sl[1:3]) // [2 3] fmt.Println(sl[:3]) // [1 2 3] fmt.Println(sl[3:]) // [4 5 6] /* 値を変更 */ sl[2] = 1000 fmt.Println(sl) // [1 2 1000 4 5 6] /* 値を追加 */ sl = append(sl, 5000) fmt.Println(sl) // [1 2 1000 4 5 6 5000] /* 配列に配列を追加 */ var double = [][]int{ []int{1, 2, 3}, []int{4, 5, 6}, []int{7, 8, 9}, } fmt.Println(double) // [[1 2 3] [4 5 6] [7 8 9]] }
スライス同士の比較
スライスはnilとしか比較できない。要素同士を比較したい場合、ループを回して要素を取り出すか、他のパッケージを利用するしかない。
make
built-inのmake関数をつかってスライスをつくることもできる。
The make function allocates a zeroed array and returns a slice that refers to that array
https://tour.golang.org/moretypes/13
ということで、配列と配列の容量を決めることができる。
スライスが返されるため、生成時に定めた配列を拡大することができる。
一方で、容量以上の配列を作成しようとするとエラーになる。(下記例の最後の文)
func main() { /* 2番目が配列数、3番目が配列の容量(capacity) */ s1 := make([]int, 3) fmt.Printf("len=%d cap=%d val=%v\n", len(s1), cap(s1), s1) // len=3 cap=3 val=[0 0 0] s2 := make([]int, 3, 5) fmt.Printf("len=%d cap=%d val=%v\n", len(s2), cap(s2), s2) // len=3 cap=5 val=[0 0 0] s3 = append(s3, 0, 0) fmt.Printf("len=%d cap=%d val=%v\n", len(s3), cap(s3), s3) // len=5 cap=5 val=[0 0 0 0 0] s4 = append(s4, 0, 0, 1, 2) // Sliceなのでなので定めた容量以上を追加することもOK fmt.Printf("len=%d cap=%d val=%v\n", len(s4), cap(s4), s4) // len=9 cap=10 val=[0 0 0 0 0 0 0 1 2] // s4 := make([]int, 5, 3) // error: len larger than cap in make([]int)
参考
A Tour of Goのざっくりまとめ③ <More types: pointers, structs編>
こちらの記事の続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
今回のMore Types編は長いので分割して書いていきます。
Pointers
ポインタは値のメモリアドレスを指す。
メモリには1バイトごとに番号が振られている。
例えばint型の100を指すSomething変数の場合、
&SomethingとすることでSomething変数のアドレスへアクセスできる。
このアドレスを値として受け取るのがポインタ型であり、
Pointer変数であれば、*Pointerと、先頭にアスタリスクをつける。
func main() { a := 42 b := a fmt.Printf("%T %v\n", a, a) // int 42 fmt.Printf("%T %v\n", b, b) // int 42 c := 42 d := &c fmt.Printf("%T %v\n", c, c) // int 42 fmt.Printf("%T %v\n", c, c) // *int 0xc000014108% アドレス }
アドレスをもつポインタ型を通して、直接そのアドレスが示す値(変数)にアクセスできる。
*d = 500 fmt.Printf("%T %v\n", c, c) // int 500 // cのアドレスを格納するポインター型dのポインタを通して変数cの値に直接アクセスした fmt.Printf("%T %v\n", *d, *d) // int 500 // ポインタ型dには500が格納されている
C言語などはやっていないので、正直ポインタやアドレスは馴染みがなくまだまだ学習する必要があると思います...
Struct
...(Structのページ、解説が少なすぎないか...orz)
Structは他プログラム言語でいうclassと比較して解説されることが多い。
主な理由として、OOPのような表現ができるからだと考えられる。
package main import "fmt" // Vertexというstruct typeを定義 type Vertex struct { X, Y int } func main() { v := Vertex{3, 19} fmt.Println(v) // {3, 19} // Struct typeの与えられたvからX, Yを取り出すイメージ(OOP的) fmt.Println(v.X, v.Y) // 3 19 }
Struct内では異なる型で、異なる値(Field or Propertyと呼ばれる)を入れることができる。
異なるFieldからつくられた集合体(Scheme)を定義する際にStructは使われる。
SchemeとClassはほぼ同義で、SchemeからObjectをつくることができるというイメージ。
Struct Typeは構造体を示すSchemeでしかないため、枠組みや設計図のようなイメージ。
上述コードの例だと、変数vはVertexというStruct型をもつStructであり、Vertexはint型のX, Yというフィールドを持っている構造体という感じ。
ちなみにフィールドの名前は小文字だと外部からアクセス(参照)できない。
また、下記のように構造体内で示されているフィールドに値を与えない場合、初期値が設定されている。
(Int型は0, bool型はfalse, string型は空白)
type Vertex struct { X int T bool S string } func main() { var v Vertex fmt.Println(v) // {0 false } }
Anonymous struct
上述の例だと、Vertexを定義する際にvという変数に一旦渡していたが、定義と値渡しを同時に行うAnonymous structというやり方もある。
ただし下記のケースでいうと、eを再活用することはできない。eというStructを再利用しない場合には便利な方法でもある。
package main import "fmt" func main() { e := struct { FirstName, LastName string Phone int Address string }{ FirstName: "Sarah", LastName: "Conner", Phone: 1234567, Address: "Earth", } fmt.Println(e) // {Sarah Conner 1234567 Earth} fmt.Println(e.address) // Earth }
メソッド
Struct型を引数にとることで、メソッドをつかうこともできる。
type Vertex struct { X, Y int } // 引数であるStruct内フィールドX,Yを足す関数Sum func (v Vertex) Sum() int { return v.X + v.Y } func main() { v := Vertex{10, 20} fmt.Println(v.Sum()) // 30 }
Pointers to Struct
structのフィールドは、structのポインタを通してアクセスすることも可能。
Structにポインタを渡す書き方は下記の通り。
func main() { p := People{"Tom", 38} fmt.Println(p) // {Tom 38} pp := &p // Struct ppにポインタを渡す (*pp).Name = "Mike" fmt.Println(p) // {Mike 38} }
これでpのアドレスをppに渡し、ppポインタを通してName変数の実体を書き換えている。
値渡しでなく参照渡しのため、pp=pのコピーになっていない? (理解が間違っていたらすみません
上記の例だと、ポインタppのName変数の実体を指定する際に、(*pp).Nameと書いている。
これは、(*pp).Nameと*(pp.Name) を混同しないためらしいが、括弧はなくてもいいらしい。
// 別の例 type Vertex struct { X, Y int } func Vertex1000(v Vertex) { v.X = 1000 } func Vertex1000_2(v *Vertex) { v.X = 1000 } func main() { v := Vertex{1, 2} Vertex1000(v) fmt.Println(v) // {1, 2} (値は変わらない) v2 := &Vertex{1, 2} Vertex1000_2(v2) fmt.Println(v2) // &{1000, 2} (値が変わる) }
Anonymous fields
Struct型にあるフィールドの名前は必ずしも定義しなくても良い。
一部のフィールドだけ定義されていない場合でもOK
データ型が記述してあれば、それが同時にフィールド名にもなる。
type Anonymous struct { int bool string } func main() { a := Anonymous{4, true, "OK"} fmt.Println(a) // {4 true OK} }
Nested Struct
Struct型にあるフィールドはどんなデータ型でもいいわけだから、当然Struct型でも良い。
これをNested Structという。Nested StructでAnonymous fieldsを定義しても良い。
type Job struct { companyName string salary int } type Family struct { Papa string Job Job // struct型を定義 (Nested Struct) Mama string Son string } func main() { family := Family{ Papa: "Tom", Mama: "Jessica", Son: "Mike", Job: Job{"A company", 300}, } fmt.Println(family) // {Tom {A company 300} Jesica Mike} fmt.Printf("Papa's salary is %v", family.Job.salary) // 300 }
Struct Literals
Structを列挙できる。
type Job struct { X, Y int } var ( j1 = Job{1, 2} j2 = Job{3, 4} j3 = &Job{5, 6} j4 = Job{} ) func main() { fmt.Println(j1, j2, j3, j4) }