ludwig125のブログ

頑張りすぎずに頑張る父

go言語アルゴリズムやTipsなどメモ

内容

go言語で自分がよく使う書き方やTipsのまとめ

並行処理関係は以下

ludwig125.hatenablog.com

BenchmarkTestは以下

ludwig125.hatenablog.com

Tips

入力値

入力値を受け取る

文字列で受け取る

var s string
fmt.Scan(&s)

整数で受け取る

var N int
fmt.Scan(&N)

複数の入力をまとめて受け取ることもできる

var a, b, c int
fmt.Scan(&a, &b, &c)

空白区切りの入力値を受け取ってスライスにする

N文字の空白区切りの入力された数値をスライスにする

  • fmt.Scanは、改行もしくは空白を区切ってしまうので、N回ループする
l := make([]int, N)
for i := range l {
    fmt.Scan(&l[i])
}
20 24 59  ← 入力値
[20 24 59]

空白区切りの入力値を一行受け取ってスライスにする

参考: Goで標準入力から文字列や数値列を取得する - Qiita

package main

import (
    "bufio"
    "fmt"
    "os"                             
)

func main() {
    s := bufio.NewScanner(os.Stdin)
    s.Scan()
    input := s.Text()
    l := strings.Split(input, " ")
    fmt.Println(l)
}
3 42 25  ← 入力
[3 42 25]  ← 全部stringなので注意

全部intのスライスにしたい場合はこんな感じかな

  • この方法よりは上記のfmt.Scanを使ったほうがよっぼど簡単だと思う
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    s := bufio.NewScanner(os.Stdin)
    s.Scan()                                             
    input := s.Text()
    l := strings.Split(input, " ")

    toIntL := func(l []string) []int {
        var intL []int
        for _, v := range l {
            i, _ := strconv.Atoi(v)
            intL = append(intL, i)
        }
        return intL
    }
    fmt.Println(toIntL(l))
}

実行結果

431 12 64 ←入力値
[431 12 64]

整数の数列をソート

package main

import (
    "fmt"
    "sort"
)

func main() {
    a := []int{10, 3, 4, 1}
    
    A := a
    sort.Ints(A)
    fmt.Println(A)
    
    B := a
    fmt.Println(sort.IntSlice(B))  // 上と同じ出力

    C := a
    sort.Sort(sort.IntSlice(C))
    fmt.Println(C)    // 上と同じ出力

    D := a
    sort.Sort(sort.Reverse(sort.IntSlice(D)))
    fmt.Println(D)  // 逆順に出力
}

結果 [1 3 4 10] [1 3 4 10] [1 3 4 10] [10 4 3 1]

文字列を辞書順にソート

いったんスライスにしてソートしてからjoinで戻す方法

参考

package main

import (
    "fmt"
    "sort"
    "strings"
)

func main() {
    var s string
    fmt.Scan(&s)
    sl := strings.Split(s, "")
    sort.Strings(sl)
    sj := strings.Join(sl, "")

    fmt.Println(sj)                                  
}

実行

gasdbfgaa ← 入力
aaabdfggs ← 出力

逆順にソートしたいときは以下を使う

  • sort.Sort(sort.Reverse(sort.StringSlice(s)))

関数化したもの

複数条件で優先順位をつけたソート

安定ソートを数回するといい - 安定ソート(SliceStable)を使うことで、最初の並びをなるべくそのままにしつつ並べ替える

参考 - GoのSliceをSortする(sort.Sliceとsort.SliceStable) - Qiita

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    ID   int
    Name string
}

func main() {
    group := []Person{
        {ID: 2, Name: "A"},
        {ID: 1, Name: "A"},
        {ID: 3, Name: "A"},
        {ID: 2, Name: "C"},
        {ID: 1, Name: "B"},
        {ID: 1, Name: "C"},
        {ID: 3, Name: "C"},
        {ID: 3, Name: "B"},
        {ID: 2, Name: "B"},
    }

    // groupをgroup1, group2に代入してそれぞれの変化を見る

    group1 := group
    // 最初にID順にソート
    sort.SliceStable(group1, func(i, j int) bool { return group1[i].ID < group1[j].ID })
    // 次にName順にソート。この時、Nameが同じであれば上でソートされたID順の並びを崩さない(安定ソート)
    sort.SliceStable(group1, func(i, j int) bool { return group1[i].Name < group1[j].Name })
    fmt.Printf("IDで安定SortしてからNameで安定ソート:%+v\n", group1)

    group2 := group
    // 最初にName順にソート
    sort.SliceStable(group2, func(i, j int) bool { return group2[i].Name < group2[j].Name })
    // 次にID順にソート。この時、IDが同じであれば上でソートされたName順の並びを崩さない(安定ソート)
    sort.SliceStable(group2, func(i, j int) bool { return group2[i].ID < group2[j].ID })
    fmt.Printf("Nameで安定SortしてからIDで安定ソート:%+v\n", group2)
}

The Go Playground

実行結果

IDで安定SortしてからNameで安定ソート:[{ID:1 Name:A} {ID:2 Name:A} {ID:3 Name:A} {ID:1 Name:B} {ID:2 Name:B} {ID:3 Name:B} {ID:1 Name:C} {ID:2 Name:C} {ID:3 Name:C}]
Nameで安定SortしてからIDで安定ソート:[{ID:1 Name:A} {ID:1 Name:B} {ID:1 Name:C} {ID:2 Name:A} {ID:2 Name:B} {ID:2 Name:C} {ID:3 Name:A} {ID:3 Name:B} {ID:3 Name:C}]

数字の各桁の数値をすべて足す

方法1.
sum := 0
for n > 0 {
        sum += n % 10
        n /= 10
}

⇒ n が 12345なら sは15

方法2.
sum := 0
for i := n; i > 0; i /= 10 {
        sum += i % 10
}

ポインタ

変数のポインタ

package main

import (
    "fmt"
)

func main() {   
    t := 10
    fmt.Println(t)
    change := func(t *int) {
        *t = 11
    }
    change(&t)
    fmt.Println(t)
}

実行結果

10
11

グリッド

二次元配列を組み立て

package main

import (
    "fmt"
    "strings"
)

func main() {
    var h, w int
    fmt.Scan(&h, &w) // high, width

    var p [][]string
    for i := 0; i < h; i++ {
        var l string
        fmt.Scan(&l)
        p = append(p, strings.Split(l, ""))
    }                                                          
    fmt.Println(p)
    fmt.Println(p[0][1])

}

実行結果

3 5
12345
67891
23456 
[[1 2 3 4 5] [6 7 8 9 1] [2 3 4 5 6]] ← 出力
2 ← 出力

行列計算

n * m の二次元配列を入力から受け取り

 var n, m int
    fmt.Scan(&n)
    fmt.Scan(&m)

    a := make([][]int, n)
    for i := 0; i < n; i++ {
        a[i] = make([]int, m)
        for j := 0; j < m; j++ {
            fmt.Scan(&a[i][j])
        }
    }

n * mの行列Aと m * l の行列Bの積としてn * lの行列Cを作成

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_D

 var n, m, l int
    fmt.Scan(&n)
    fmt.Scan(&m)
    fmt.Scan(&l)

    a := make([][]int, n)
    for i := 0; i < n; i++ {
        a[i] = make([]int, m)
        for j := 0; j < m; j++ {
            fmt.Scan(&a[i][j])
        }
    }
    b := make([][]int, m)
    for i := 0; i < m; i++ {
        b[i] = make([]int, l)
        for j := 0; j < l; j++ {
            fmt.Scan(&b[i][j])
        }
    }
    c := make([][]int, n)
    for i := 0; i < n; i++ {
        c[i] = make([]int, l)
    }

    for k := 0; k < n; k++ {
        for j := 0; j < l; j++ {
            for i := 0; i < m; i++ {
                c[k][j] += a[k][i] * b[i][j]
            }
        }
    }
    fmt.Println(c)

構造体とメソッド

構造体

こんな感じにstructを宣言して初期化できる

package main

import "fmt"

type Person struct {
    name   string
    age    int
    weight float64
}

func main() {
    person1 := Person{name: "Taro", age: 21, weight: 60.12} // フィールド名を指定するとわかりやすい
    fmt.Println(person1)
    person2 := Person{"Akira", 22, 61.12} // 順番通りなら指定しなくてもいい
    fmt.Println(person2)
}

実行結果

{Taro 21 60.12}
{Akira 22 61.12}

メソッド

goにはクラスはないが、構造体にメソッドを追加することでクラスメソッドのように使える

上で定義したPerson構造体の要素をすべてstringにしたスライスを返すメソッドを追加した例

func (p *Person) toString() []string {
    var str []string
    str = append(str, fmt.Sprintf("%s", p.name))
    str = append(str, fmt.Sprintf("%d", p.age))
    str = append(str, fmt.Sprintf("%f", p.weight))
    return str
}

func main() {
    person1 := Person{name: "Taro", age: 21, weight: 60.12}
    fmt.Println(person1.toString())
}

実行結果

[Taro 21 60.120000]

例2

Person構造体のスライスPersonsを定義して、要素をすべてinterfaceの二次元配列にして返す関数を使ってみた例

type Person struct {
    name   string
    age    int
    weight float64
}

type Persons []Person

func (p *Person) toString() []string {
    var str []string
    str = append(str, fmt.Sprintf("%s", p.name))
    str = append(str, fmt.Sprintf("%d", p.age))
    str = append(str, fmt.Sprintf("%f", p.weight))
    return str
}

func (p *Person) toInterfaceSlice() []interface{} {
    var is []interface{}
    is = append(is, p.name)
    is = append(is, p.age)
    is = append(is, p.weight)
    return is
}

func (ps *Persons) toInterfaceSlices() [][]interface{} {
    var iss [][]interface{}
    for _, p := range *ps {
        iss = append(iss, p.toInterfaceSlice())
    }
    return iss
}

func main() {
    person1 := Person{name: "Taro", age: 21, weight: 60.12}
    fmt.Printf("要素が全部stringのスライスに変換 %v\n", person1.toString())
    fmt.Printf("要素が全部interfaceのスライスに変換 %v\n", person1.toInterfaceSlice())

    persons := Persons{}
    persons = append(persons, person1)

    person2 := Person{name: "Akira", age: 22, weight: 61.12}
    persons = append(persons, person2)
    fmt.Printf("要素が全部interfaceの二次元スライスに変換 %v\n", persons.toInterfaceSlices())
}

実行結果

要素が全部stringのスライスに変換 [Taro 21 60.120000]
要素が全部interfaceのスライスに変換 [Taro 21 60.12]
要素が全部interfaceの二次元スライスに変換 [[Taro 21 60.12] [Akira 22 61.12]]

コード https://play.golang.org/p/FeJx-_wXv68

Enum

参考

4 iota enum examples · YourBasic Go

Go言語 - enumを定義する - 覚えたら書く

エラーハンドリング

参考

【go】golangのエラー処理メモ - ②. 例外はないがエラーハンドリングはできるよ(インスタンスや型でハンドリング) - tweeeetyのぶろぐ的めも

エラー・ハンドリングについて(追記あり) — プログラミング言語 Go | text.Baldanders.info

Golangのエラー処理とpkg/errors | SOTA

Go言語のエラーハンドリングについて - Qiita

Errors are values - The Go Blog ← 同じ関数を複数回呼ぶ時のエラーをまとめて扱う

reflect

こちらにまとめた ludwig125.hatenablog.com

アルゴリズム

全N個のものをM個単位で処理する

以下のようなlistを指定の件数ずつ分割して処理したいとき

package main

import (
    "fmt"
)

func main() {
    l := []int{1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10}

    for _, i := range l {
        fmt.Println(i)
    }

}
// 結果
/*
1
2
3
4
5
6
6
7
8
9
10
*/

以下のようにしてみた

package main

import (
    "fmt"
)

func main() {

    l := []int{1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10}

    chunkSize := 3
    for start := 0; start < len(l); start += chunkSize {
        
        end := start + chunkSize
        if end > len(l) {
            end = len(l)
        }
        fmt.Println(l[start:end])
        fmt.Println("---")
    }
}

// 結果
/* 
[1 2 3]
---
[4 5 6]
---
[6 7 8]
---
[9 10]
---
*/