20210507のGoに関する記事は3件です。

Python3年目のエンジニアがGo言語に入門してみたら詰まったところ。

私は普段PythonとTypeScriptばかり書いています。 GWで時間があったのでかねてからやってみたいと思っていたGo言語を触ってみたので 詰まったところをまとめておきます。 PythonとGoだと一番大きな違いは型付けだと思いますが、 普段からPythonでも型定義をしながら書いているのでそこは問題に感じませんでした。 私が理解に時間がかかったのは以下の項目です。 - Json - インターフェース - パッケージの扱い パッケージの扱い Goのパッケージはファイル単位ではありません。 package パッケージ名 のようにファイルの先頭で指定したパッケージ名ごとに分かれます。 同じパッケージ内であれば特に明示しなくてもファイル間で参照し合えます。 また、ファイルの外部から参照する際には全ての命名を大文字で始めなければなりません。 構造体のプロパティも例外ではないので全て大文字で始まります。 type SomeStruct struct { propertyName string // <-こうじゃなくて PropertyName string // <-こう } Json 構造体をJsonに変換するときに、タグを付けなければならないという所でも若干詰まりました。 type SomeStruct struct { PropertyName string `json:"propertyName` <-最後のこれ } こうするとJson化するときにキー名を小文字で始めることができます。 上の構造体のプロパティ名が大文字で始まってしまう仕様と関係していそうです。 また、このタグはJson以外のパースにも使われますし、 バリデーションを行うためにRequiredのフラグを立てたり、最大、最小値を指定することにも使われます。 インターフェース Go言語のインターフェースは他言語のインターフェースと使い方が異なります。 JavaやTypeScriptであればクラスを定義する際にimplement句などで インターフェースを実装している事を明示しますが、 Go言語のインターフェースは使い方が異なります。 インターフェースで定義されているメソッドを持つ型であれば、 特に明示しなくてもそのインターフェースを実装していることになります。 type Reader interface { Read() string } type Human struct {} // ここでHumanにEaterを実装していることになる。 func (h Human*) Read() { return "..." } ではどういう時に便利なのかというと、構造体を初期化する関数の返り値をその構造体自身ではなく インターフェースを指定することでより高度にカプセル化できます。 また、テスト用にモックを作る時にも役立ちます。 Goのインターフェースの使い方は特殊な感じがしますが、 下記のエムスリーさんのテックブログが大変分かりやすかったので参考にさせていただきました。 https://www.m3tech.blog/entry/go 未解決問題 なんでEnum型ないんだろう。。。 最後に Goを勉強する上で、実際に手を動かす前には クラスの継承がなかったり型を常に定義したりといった所に目がいっていました。 しかし、実際にコードを書いてみるとそれらはそこまで大したハードルにはならず、 意外なところにつまづきポイントがありました。 新しい言語を学習するとプログラミングに関する理解も深まって良いですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語におけるバリデーションモジュールの返却値の覚え書き

Go/Echoで、APIのパラメータのバリデーションどうしようと調べて見つかったモジュールが「gopkg.in/go-playground/validator」。 そのリファレンスの返却値部分のメモです。 使用モジュール 最新のメジャーバージョンはv10のようなのですが、gomodのプロキシ周りの設定?が怪しいのかリンクエラーが出まくってしまったため、本例ではv9を使っています。 (本当はgithub.comだけどgopkg.in使えとエラーが出てるがVSCodeだと補完できずエラーになる) v9リファレンス https://pkg.go.dev/gopkg.in/go-playground/validator.v9 概要 チェックしたい構造体にバリデーションルールを書いたタグを付与する validatorインスタンスのStructメソッドに、チェックしたい構造体のインスタンスを渡す 戻り値(error)がリストに入ったinterface型で返ってくるので、validator.ValidationErrors型にアサーションして使用する 例えばAPIの返却値としてエラー内容を構造化してクライアントに返すためには、3のvalidator.ValidationErrors下のメソッドを使う必要があります。 サンプル コピペでPlaygroundで動きます。 package main import ( "fmt" "gopkg.in/go-playground/validator.v9" ) type paramUpdateUserInfo struct { ID int `json:"id" validate:"required,gte=0"` Name string `json:"name" validate:"required,min=10,max=100"` Memo string `json:"memo"` } func main() { param := paramUpdateUserInfo{ ID: 1, Name: "あ", } if err := validator.New().Struct(param); err != nil { for _, desc := range err.(validator.ValidationErrors) { fmt.Println("Field: " + desc.Field()) fmt.Println("StructField: " + desc.StructField()) fmt.Println("Namespace: " + desc.Namespace()) fmt.Println("StructNamespace: " + desc.StructNamespace()) fmt.Println("Kind: " + desc.Kind().String()) // reflect fmt.Println("Type: " + desc.Type().String()) // reflect fmt.Println("Value: " + desc.Value().(string)) // interface fmt.Println("Tag: " + desc.Tag()) fmt.Println("ActualTag: " + desc.ActualTag()) fmt.Println("Param: " + desc.Param()) } } } または https://play.golang.org/p/ibPUaf7fE_B メソッド 公式リファレンス https://pkg.go.dev/gopkg.in/go-playground/validator.v9#FieldError サンプルの前提 構造体名: paramUpdateUserInfo validateタグ: validate:"required,min=10,max=100" エラーがあるフィールド名: Name エラー内容: 文字数不足エラー メソッド 文字列変換した場合の返却値 説明 Field Name エラーが発生したフィールド名。タグがある場合はその名前が優先される。 StructField Name エラーが発生したフィールド名(構造体の定義名)。 Namespace paramUpdateUserInfo.Name エラーが発生したフィールド名にネームスペースを付与したもの。タグがある場合はその名前が優先される。 StructNamespace paramUpdateUserInfo.Name エラーが発生したフィールド名にネームスペースを付与したもの。 Kind string reflectのKind型が返却される。Stringメソッドを使うことで、型を示す文字列を取得可能。 Type string reflectのType型が返却される。Stringメソッドを使うことで、型を示す文字列を取得可能。 Value あ エラーが発生した値のinterface値。型アサーションが必要。動的にKindを渡したいところですが、interfaceにreflectは渡せないらしくエラーになります。。(循環参照的な?) Tag min validateタグで設定され、失敗した検証内容。エイリアスが設定されていた場合、実際の検証内容ではなくエイリアスが返却される。例えばエイリアスが"iscolor": "hexcolor|rgb|rgba|hsl|hsla"の場合、返却値はiscolor。 ActualTag min validateタグで設定され、失敗した検証内容。エイリアスが設定されていた場合、エイリアス名ではなく実際の検証内容が返却される。例えばエイリアスが"iscolor": "hexcolor|rgb|rgba|hsl|hsla"の場合、返却値はhexcolor|rgb|rgba|hsl|hsla。 Param 10 validateタグで設定され、失敗した検証のパラメータ。しきい値など。 例えばフロントで「名前は10文字以上で設定してください」というエラーメッセージを作りたいとして、 欲しい情報は StructField (= Name) Tag (= min) Param (= 10) 辺りでしょうか? 半角数字のみで~とかそういうのはカスタムバリデーションで設定することになると思いますが、 この記事ではとりあえずここまで。 参考 https://qiita.com/RunEagler/items/ad79fc860c3689797ccc https://qiita.com/ayatothos/items/064ce18f4260b2503249
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】Sorting algorithms (ソートアルゴリズム)

Goでプログラミングの基礎を学ぶシリーズ スクールでは教えてくれないプログラミングの基礎、データ構造とアルゴリズムをGoで学んでいくシリーズです。 そのデータ構造がどのようなものであるかは、理解を助けてくれるサイトを紹介しつつ、簡単な説明に留めさせていただきます。(ご自身でも調べてみてください!) 筆者自身、Exerciseに取り組みながら理解を深めていったので、コードの解説を中心に記事を書いていきたいと思います。 タイトル #0 はじめに (環境構築と勉強方法) #1 Pointer, Array, String (ポインタと配列と文字列と) #2 File operations (ファイル操作) #3 Linked List (連結リスト) #4 Stack & Queue (スタックとキュー) #5 Search algorithms (探索アルゴリズム) #6 Tree (木構造) #7 Sorting algorithms (ソートアルゴリズム) ☜ here #8 String pattern matching algorithms (文字列探索アルゴリズム) 今回は#7 Sorting algorithms (ソートアルゴリズム)です。 Sortとは データを一定の規則に従って並び替えることです。 昇順や降順など日常生活でも馴染みがありますね。 Sort には多くのアルゴリズムが存在し、時間計算量が比較されます。 以下の記事はソートの様子が可視化されているので、直感的にわかりやすいと思います。 ここでは中でも代表的な Sort のアルゴリズムの Go での実装例をみていきます。 各ソートがどのような流れでソートしていくかは、上のサイトをご参考ください。 Insertion sort (挿入ソート) リストのソート済みの部分に、新たな要素を適切な位置に挿入することを繰り返してソートを行うアルゴリズムです。 平均計算時間: $O(n^2)$ , 最悪計算時間: $O(n^2)$ と遅いのですが、アルゴリズムは単純で実装しやすいと思います。 func InsertionSort(numbers []int) []int { for i := 0; i < len(numbers); i++ { next := numbers[i] // ソートされていない部分の先頭 fmt.Printf("next: %d\n", next) var j int // ソートされている部分で、 next が挿入されるところまで要素をずらしていく for j = i - 1; j >= 0 && next < numbers[j]; j-- { numbers[j+1] = numbers[j] fmt.Printf("j = %d, sorting... %v\n", j, numbers) } numbers[j+1] = next // next を挿入 fmt.Printf("after insert next: %v\n\n", numbers) } return numbers } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} InsertionSort(numbers) } 所々にログを仕込んでみました。 ログを見ると、ソートの流れがよくわかるかと思います。 ☟ 出力結果 next: 7 after insert next: [7 65 8 32 4 21 10] next: 65 after insert next: [7 65 8 32 4 21 10] next: 8 j = 1, sorting... [7 65 65 32 4 21 10] after insert next: [7 8 65 32 4 21 10] next: 32 j = 2, sorting... [7 8 65 65 4 21 10] after insert next: [7 8 32 65 4 21 10] next: 4 j = 3, sorting... [7 8 32 65 65 21 10] j = 2, sorting... [7 8 32 32 65 21 10] j = 1, sorting... [7 8 8 32 65 21 10] j = 0, sorting... [7 7 8 32 65 21 10] after insert next: [4 7 8 32 65 21 10] next: 21 j = 4, sorting... [4 7 8 32 65 65 10] j = 3, sorting... [4 7 8 32 32 65 10] after insert next: [4 7 8 21 32 65 10] next: 10 j = 5, sorting... [4 7 8 21 32 65 65] j = 4, sorting... [4 7 8 21 32 32 65] j = 3, sorting... [4 7 8 21 21 32 65] after insert next: [4 7 8 10 21 32 65] Selection sort (選択ソート) リストのソートされていない部分から、最小値(or最大値)を持つ要素を探して、その値をソートされていない部分の先頭に移動(交換)することを繰り返してソートを行うアルゴリズムです。 平均計算時間: $O(n^2)$ , 最悪計算時間: $O(n^2)$ と遅いのですが、アルゴリズムは単純で実装しやすいと思います。 func SelectionSort(numbers []int) []int { for i := 0; i < len(numbers); i++ { min := i // ソートされていない部分での最小値の index を探す for j := i + 1; j < len(numbers); j++ { if numbers[min] > numbers[j] { min = j } } // ソートされていない部分の先頭と、ソートされていない部分での最小値を入れ替える numbers[i], numbers[min] = numbers[min], numbers[i] fmt.Printf("i = %d, %v\n", i, numbers) } return numbers } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} SelectionSort(numbers) } ☟ 出力結果 i = 0, [4 65 8 32 7 21 10] i = 1, [4 7 8 32 65 21 10] i = 2, [4 7 8 32 65 21 10] i = 3, [4 7 8 10 65 21 32] i = 4, [4 7 8 10 21 65 32] i = 5, [4 7 8 10 21 32 65] i = 6, [4 7 8 10 21 32 65] Bubble sort (バブルソート) 隣り合う要素の大小を比較しながらソートしていくアルゴリズムです。 平均計算時間: $O(n^2)$ , 最悪計算時間: $O(n^2)$ と遅いのですが、アルゴリズムは単純で実装しやすいと思います。 func BubbleSort(numbers []int) []int { for i := 0; i < len(numbers)-1; i++ { for j := 0; j < len(numbers)-i-1; j++ { if numbers[j] > numbers[j+1] { numbers[j], numbers[j+1] = numbers[j+1], numbers[j] } } fmt.Printf("i = %d, %v\n", i, numbers) } return numbers } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} BubbleSort(numbers) } ☟ 出力結果 i = 0, [7 8 32 4 21 10 65] i = 1, [7 8 4 21 10 32 65] i = 2, [7 4 8 10 21 32 65] i = 3, [4 7 8 10 21 32 65] i = 4, [4 7 8 10 21 32 65] i = 5, [4 7 8 10 21 32 65] Heap sort (ヒープソート) ヒープとは、データ構造の一種で、木構造(ツリー構造)のうち、親要素が子要素より常に大きい(あるいは小さい)という条件を満たすもの。 引用: IT用語辞典 e-Words Heap sort は Binary Heap (二分ヒープ木) を用いて行うソートのアルゴリズムです。 ヒープの構造を利用するだけで、Tree を実装する必要はありません。 平均計算時間: $O(n\log{n})$ , 最悪計算時間: $O(n\log{n})$ であり、上の3つよりも速いソートアルゴリズムになります。 func HeapSort(numbers []int) []int { // heap を形成する fmt.Printf("Build Heap\n") for i := len(numbers)/2 - 1; i >= 0; i-- { heapify(numbers, len(numbers), i) } // heap から要素を一つずつ取り出す (ソートしていく) fmt.Printf("\nHeap Sort") for i := len(numbers) - 1; i > 0; i-- { numbers[i], numbers[0] = numbers[0], numbers[i] fmt.Printf("\ni = %d, sorting... %v\n", i, numbers) heapify(numbers, i, 0) } return numbers } // i 番目の要素を root とする Tree をヒープ化する. // len は heap size, root は numbers の index func heapify(numbers []int, len, root int) { largest := root // 最大の要素の index を i (root) としておく left := 2*root + 1 right := 2*root + 2 if left < len && numbers[left] > numbers[largest] { // left child が root より大きい場合 largest = left } if right < len && numbers[right] > numbers[largest] { // right child が root より大きい場合 largest = right } if largest != root { // 最大の要素が root ではないとき numbers[root], numbers[largest] = numbers[largest], numbers[root] // root の要素と最大の要素を入れ替える fmt.Printf("(%d %d %d) heapify... %v\n", root, left, right, numbers) heapify(numbers, len, largest) } } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} HeapSort(numbers) } ☟ 出力結果 Build Heap (2 5 6) heapify... [7 65 21 32 4 8 10] (0 1 2) heapify... [65 7 21 32 4 8 10] (1 3 4) heapify... [65 32 21 7 4 8 10] Heap Sort i = 6, sorting... [10 32 21 7 4 8 65] (0 1 2) heapify... [32 10 21 7 4 8 65] i = 5, sorting... [8 10 21 7 4 32 65] (0 1 2) heapify... [21 10 8 7 4 32 65] i = 4, sorting... [4 10 8 7 21 32 65] (0 1 2) heapify... [10 4 8 7 21 32 65] (1 3 4) heapify... [10 7 8 4 21 32 65] i = 3, sorting... [4 7 8 10 21 32 65] (0 1 2) heapify... [8 7 4 10 21 32 65] i = 2, sorting... [4 7 8 10 21 32 65] (0 1 2) heapify... [7 4 8 10 21 32 65] i = 1, sorting... [4 7 8 10 21 32 65] 流れを図にすると以下のようになります。 まずはランダムな配列のヒープ化を行います。 heapify では root, left, right から最大の値を root と入れ替え、入れ替えが行われた index について再帰的に heapify を実行するという流れになります。 ヒープ化ができたらソートをしていきます。 ヒープ化により root が最大値になっているので、root と配列の最後尾を入れ替え、入れ替えが行われた後に再度ヒープ化( heapify )を実行します。 それの繰り返しにより、ソートされていきます。 Quick sort (クイックソート) ピボットという基準となる値をランダムに選択し、リストの先頭からピボットよりも大きいか小さいかで両側のグループに分割し、それを繰り返してソートを行うアルゴリズムです。 平均計算時間: $O(n\log{n})$ , 最悪計算時間: $O(n^2)$ であり、Wikipediaによると、 他のソート法と比べて、一般的に最も高速だといわれているが対象のデータの並びやデータの数によっては必ずしも速いわけではない ようです。 func QuickSort(numbers []int) []int { if len(numbers) < 2 { return numbers } fmt.Printf("\nsorting... %v\n", numbers) left, right := 0, len(numbers)-1 pivot := numbers[right] for i := 0; i < right; i++ { if numbers[i] < pivot { numbers[i], numbers[left] = numbers[left], numbers[i] left++ fmt.Printf("p = %d, left = %d, %v\n", pivot, numbers[left], numbers) } } numbers[left], numbers[right] = numbers[right], numbers[left] fmt.Printf("p = %d, left = %d, %v\n", pivot, numbers[left], numbers) QuickSort(numbers[:left]) QuickSort(numbers[left+1:]) return numbers } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} QuickSort(numbers) } ☟ 出力結果 sorting... [7 65 8 32 4 21 10] p = 10, left = 65, [7 65 8 32 4 21 10] p = 10, left = 65, [7 8 65 32 4 21 10] p = 10, left = 32, [7 8 4 32 65 21 10] p = 10, left = 10, [7 8 4 10 65 21 32] sorting... [7 8 4] p = 4, left = 4, [4 8 7] sorting... [8 7] p = 7, left = 7, [7 8] sorting... [65 21 32] p = 32, left = 65, [21 65 32] p = 32, left = 32, [21 32 65] ソートの流れは最初にリンクした参考サイトの通りです。 Merge sort (マージソート) ソートされていないリストを2つのリストに分割して、それぞれをソートした後、それらをマージしてソート済みのひとつのリストを作るアルゴリズムです。 平均計算時間: $O(n\log{n})$ , 最悪計算時間: $O(n\log{n})$ func MergeSort(numbers []int) []int { if len(numbers) < 2 { return numbers } middle := len(numbers) / 2 return merge(MergeSort(numbers[:middle]), MergeSort(numbers[middle:])) } func merge(left, right []int) []int { var merged []int fmt.Printf("\nmerge %v %v\n", left, right) // 左のリストと右のリストの要素を比較し、小さい方を merged に追加する for i := 0; len(left) > 0 && len(right) > 0; i++ { if left[0] < right[0] { merged = append(merged, left[0]) left = left[1:] } else { merged = append(merged, right[0]) right = right[1:] } fmt.Printf("i = %d, %v\n", i, merged) } // リストに残っている要素を merged に追加 for _, l := range left { merged = append(merged, l) } for _, r := range right { merged = append(merged, r) } fmt.Printf("sorted %v\n", merged) return merged } func main() { numbers := []int{7, 65, 8, 32, 4, 21, 10} MergeSort(numbers) } ☟ 出力結果 merge [65] [8] i = 0, [8] sorted [8 65] merge [7] [8 65] i = 0, [7] sorted [7 8 65] merge [32] [4] i = 0, [4] sorted [4 32] merge [21] [10] i = 0, [10] sorted [10 21] merge [4 32] [10 21] i = 0, [4] i = 1, [4 10] i = 2, [4 10 21] sorted [4 10 21 32] merge [7 8 65] [4 10 21 32] i = 0, [4] i = 1, [4 7] i = 2, [4 7 8] i = 3, [4 7 8 10] i = 4, [4 7 8 10 21] i = 5, [4 7 8 10 21 32] sorted [4 7 8 10 21 32 65] ソートの流れは最初にリンクした参考サイトの通りです。 ? Exercise 1~500 の数字で構成される、要素数 500 のランダムな配列を Insertion sort と Quick sort でソートしましょう。 その実行速度を比較してみましょう。 ☟ 解答例 package main import ( "fmt" "math/rand" "time" ) func initList(n int) []int { var numbers []int rand.Seed(time.Now().UnixNano()) for i := 0; i < n; i++ { numbers = append(numbers, rand.Intn(500)) } return numbers } func InsertionSort(numbers []int) []int { for i := 0; i < len(numbers); i++ { next := numbers[i] var j int for j = i - 1; j >= 0 && next < numbers[j]; j-- { numbers[j+1] = numbers[j] } numbers[j+1] = next } return numbers } func QuickSort(numbers []int) []int { if len(numbers) < 2 { return numbers } left, right := 0, len(numbers)-1 pivot := numbers[right] for i := 0; i < right; i++ { if numbers[i] < pivot { numbers[i], numbers[left] = numbers[left], numbers[i] left++ } } numbers[left], numbers[right] = numbers[right], numbers[left] QuickSort(numbers[:left]) QuickSort(numbers[left+1:]) return numbers } func main() { numbers := initList(500) start := time.Now() InsertionSort(numbers) runtime := time.Since(start) fmt.Printf("Insertion sort ▶︎ runtime: %v\n", runtime) numbers = initList(500) start = time.Now() QuickSort(numbers) runtime = time.Since(start) fmt.Printf("Quick sort ▶︎ runtime: %v\n", runtime) } ☟ 出力結果 Insertion sort ▶︎ runtime: 44.611µs Quick sort ▶︎ runtime: 23.602µs おわりに Exerciseの解答例はあくまで例なので、もっといい書き方あるよ!という方、ぜひコメントをお寄せください! 説明についても、筆者自身が初心者であるため、ご指摘や補足は大歓迎でございます。 株式会社Link Sportsでは、あらゆるスポーツを楽しむ人たちに送る、チームマネジメントアプリを開発しています。 未経験でも経験豊富なエンジニアの方でも、スポーツ好きなら活躍できる環境があります! 絶賛エンジニア募集中です! Wantedly ▶︎ https://www.wantedly.com/projects/324177 Green ▶︎ https://www.green-japan.com/job/82003 次回は、データ構造とアルゴリズム#8 String pattern matching algorithms (文字列探索アルゴリズム)です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む