Skip to content

Instantly share code, notes, and snippets.

@1206yaya
Last active September 24, 2025 07:31
Show Gist options
  • Select an option

  • Save 1206yaya/9dc552cf7ae4ce86118a091e304a3952 to your computer and use it in GitHub Desktop.

Select an option

Save 1206yaya/9dc552cf7ae4ce86118a091e304a3952 to your computer and use it in GitHub Desktop.
Go Cheat Sheet #go #cheat-sheet

ChatGPT - gpts: tool cheat sheet

VSCode

Debugingz

launch.json

  • 現在開いているコードをデバックする: Debug Current Go File
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Go Test",
      "type": "go",
      "request": "launch",
      "mode": "test",
      "program": "${fileDirname}",
      "args": ["-test.run", "Test", "-test.v"],
      "showLog": true,
      "env": {},
      "buildFlags": "-gcflags 'all=-N -l'",
      "logOutput": "rpc" // Delveのログ出力を最小限に抑える
    },
    {
      "name": "Debug Current Go File",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${file}",
      "env": {},
      // "args": ["${input:argument}"],
      "console": "integratedTerminal",
      // これがないと、ルートディレクトリで実行した時と、個別のファイルをデバックしたときの読み込みパスが同じにならない
      "cwd": "${workspaceFolder}"
    }
  ]
}

コア概念

依存関係は通常、構造体タイプではなく、インターフェースとして表現されます。これにより、コードはより柔軟になり、モックやスタブを使用してテストを行うことができます。

// CartRepository はカートのデータ操作を抽象化するインターフェース
type CartRepository interface {
    FindCart(id string) (*Cart, error)
    SaveCart(cart *Cart) error
}

// StockService は在庫確認を抽象化するインターフェース
type StockService interface {
    CheckStock(productID string, quantity int) error
}

// Cart はカート情報を保持する構造体
type Cart struct {
    ID       string
    Items    []CartItem
    Total    int
}

// CartService はカートの操作を行うサービス
type CartService struct {
    cartRepo CartRepository  // データ操作の依存
    stock    StockService    // 在庫確認の依存
}

// NewCartService は CartService を初期化します
func NewCartService(repo CartRepository, stock StockService) *CartService {
    return &CartService{
        cartRepo: repo,
        stock:   stock,
    }
}

// AddItem は商品をカートに追加します
func (s *CartService) AddItem(cartID string, productID string, quantity int) error {
    // インターフェースを通じて依存オブジェクトのメソッドを呼び出す
    cart, err := s.cartRepo.FindCart(cartID)
    if err != nil {
        return err
    }

    if err := s.stock.CheckStock(productID, quantity); err != nil {
        return err
    }

    // カートの更新処理...
    return s.cartRepo.SaveCart(cart)
}

構造体

go ではクラスがない代わりに構造体を使用します。なので Java とは異なり、インスタンスの生成と呼ばずに構造体の生成と呼びます。

Go vs Java 構造体チートシート

// 1. クラス vs 構造体
// Java: クラスベース
/*
public class User {
    private String name;  // privateフィールド
    public int age;      // publicフィールド

    // コンストラクタ
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// インスタンス化
User user = new User("山田", 25);
*/

// Go: 構造体ベース
type User struct {
    name string  // 小文字で始まる → private
    Age  int     // 大文字で始まる → public
}

// 初期化
user := User{name: "山田", Age: 25}   // 値として
userPtr := &User{name: "田中", Age: 30} // ポインタとして


// 2. メソッド定義の違い
// Java
/*
public class User {
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
*/

// Go: 構造体の外部でメソッドを定義
func (u User) GetName() string {      // 値レシーバー
    return u.name
}

func (u *User) SetName(name string) { // ポインタレシーバー
    u.name = name
}


// 3. 継承 vs 埋め込み
// Java: 継承
/*
public class Employee extends User {
    private String department;
}
*/

// Go: 埋め込み(継承ではない)
type Employee struct {
    User              // 構造体の埋め込み
    Department string
}


// 4. インターフェース
// Java: 明示的な実装
/*
public interface UserInterface {
    String getName();
    void setName(String name);
}

public class User implements UserInterface {
    // メソッドを明示的に実装
}
*/

// Go: 暗黙的な実装
type UserInterface interface {
    GetName() string
    SetName(string)
}
// 必要なメソッドを持っていれば自動的に実装したとみなされる


// 5. カプセル化の違い
// Java: アクセス修飾子による制御
/*
public class User {
    private String name;   // クラス内のみ
    protected int age;     // 継承クラスまで
    public String email;   // 全てのクラス
}
*/

// Go: 命名規則による制御
type User struct {
    name  string  // 小文字 → パッケージ内のみ
    Email string  // 大文字 → 全てのパッケージ
}


// 6. 使用例
func main() {
    // Goの構造体の使用
    user := User{
        name: "山田",
        Age:  25,
    }

    // メソッドの呼び出し
    name := user.GetName()

    // 埋め込み構造体の使用
    emp := Employee{
        User:       User{name: "田中", Age: 30},
        Department: "開発部",
    }

    // フィールドへのアクセス
    empName := emp.GetName()  // 埋め込まれたUserのメソッドを直接使用可能
}

// 主な違いのまとめ
// 1. Go: クラスがない → 代わりに構造体を使用
// 2. Go: コンストラクタがない → 代わりに初期化関数を作成
// 3. Go: 継承がない → 代わりに埋め込みを使用
// 4. Go: インターフェースは暗黙的に実装
// 5. Go: アクセス修飾子がない → 命名規則で制御
// 6. Go: メソッドは構造体の外部で定義

Go の特徴:

  1. シンプルな設計

    • クラスや継承がない
    • 暗黙的なインターフェース実装
    • 命名規則によるアクセス制御
  2. 明示的な動作

    • ポインタと値の使い分けが明確
    • メソッドのレシーバーで意図を表現
  3. 少ない言語機能

    • 継承の代わりに埋め込み
    • アクセス修飾子の代わりに命名規則
    • シンプルな構文

このような設計により、Go は:

  • コードの理解がしやすい
  • 保守が容易
  • パフォーマンスの予測が可能 という特徴を持っています。

構造体初期化とポインタの使い分け

基本的な構造体の定義

type User struct {
    ID        int
    Name      string
    Data      []byte
    CreatedAt time.Time
}

// コンストラクタとしてのNew関数
func NewUser(name string, age int) *User {
    return &User{
        ID:       generateID(),  // IDを生成する関数があると仮定
        Name:     name,
        Data:     nil,
        CreatedAt: time.Now(),
    }
}

type Point struct {
    X int
    Y int
}

ポインタを使う場合 vs 値を使う場合の比較

// ポインタとして使う場合 (*User)
// ユースケース:
// - 構造体の値を変更する必要がある
// - 大きなサイズの構造体を扱う (1KB以上、スライス/マップを含む)
// - データベースの行など、更新が必要なデータ
// - 並行処理で共有されるデータ
userPtr := &User{
    ID:   1,
    Name: "田中",
}

UpdateUser(userPtr) // 元の値が変更される

user := NewUser("田中")

// 値として使う場合 (Point)
// ユースケース:
// - 読み取り専用データ
// - 小さいサイズの構造体 (数百バイト以下)
// - 単純なデータ型のみを含む
// - 一時的な計算や変換用
point := Point{X: 10, Y: 20}
DisplayPoint(point) // コピーが渡される

メソッドの定義パターン

// ポインタレシーバー: 構造体を変更する場合
func (u *User) UpdateName(name string) {
    u.Name = name        // 元の構造体が変更される
}

// 値レシーバー: 構造体を変更しない場合
func (p Point) Distance() float64 {
    return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))  // 元の構造体は変更されない
}

関数での使い分け

func UpdateUser(u *User) {
    u.Name = "新しい名前"  // ポインタ:元の値が変更される
}

func DisplayPoint(p Point) {
    fmt.Printf("Point(%d, %d)\n", p.X, p.Y)  // 値:コピーが使われる
}

初期化パターン

func InitializationExamples() {
    // ポインタとして初期化
    user1 := &User{ID: 1, Name: "田中"}    // &による初期化
    user2 := new(User)                    // newによる初期化(フィールドは初期値)

    // 値として初期化
    point1 := Point{X: 10, Y: 20}         // 直接初期化
}

カスタムエラー型

カスタムエラー型は Go の error インターフェースを実装した独自のエラー型で、より詳細なエラー情報の提供や型安全なエラーハンドリングを可能にします。

基本的なカスタムエラーの実装

1. カスタムエラー型の定義

// 構造体としてカスタムエラーを定義
type ValidationError struct {
    Field   string
    Message string
}

// error インターフェースを実装
func (e *ValidationError) Error() string {
    return fmt.Sprintf("Validation error on field %s: %s", e.Field, e.Message)
}

2. カスタムエラーの生成と返却

// エラーを返す関数の実装
func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "age cannot be negative",
        }
    }
    return nil
}

// 使用例
age := -5
if err := validateAge(age); err != nil {
    fmt.Println(err) // "Validation error on field age: age cannot be negative"
}

3. エラーの型チェックと型アサーション

// errors.As を使用した型チェック(推奨)
if err := validateAge(-5); err != nil {
    var validErr *ValidationError
    if errors.As(err, &validErr) {
        fmt.Printf("Field: %s, Message: %s\n", validErr.Field, validErr.Message)
    }
}

// 型アサーションを使用した方法
if err := validateAge(-5); err != nil {
    if validErr, ok := err.(*ValidationError); ok {
        fmt.Printf("Field: %s, Message: %s\n", validErr.Field, validErr.Message)
    }
}

4. エラーのラッピング

// エラーをラップする構造体
type DatabaseError struct {
    Err     error  // 元のエラー
    Query   string // 追加情報
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("Database error: %v (Query: %s)", e.Err, e.Query)
}

// エラーチェーンの作成
func (e *DatabaseError) Unwrap() error {
    return e.Err
}

// 使用例
func executeQuery(query string) error {
    err := someDBOperation()
    if err != nil {
        return &DatabaseError{
            Err:   err,
            Query: query,
        }
    }
    return nil
}

5. エラー定数とエラー比較

// エラー定数の定義
var (
    ErrNotFound = errors.New("record not found")
    ErrInvalid  = errors.New("invalid input")
)

// エラー比較の実装
func findRecord(id string) error {
    // ...
    if recordNotExists {
        return ErrNotFound
    }
    return nil
}

// errors.Is を使用したエラー比較
if err := findRecord("123"); err != nil {
    if errors.Is(err, ErrNotFound) {
        fmt.Println("Record was not found")
    }
}

カスタムエラーを使用するメリット

  1. 型安全性: エラーの型チェックが可能で、特定のエラーに対して適切な処理を行える
  2. 追加情報: エラーに関連する詳細情報を含められる
  3. エラーの階層化: エラーをラップして情報を追加しながらコールスタックを遡れる
  4. 可読性: エラーメッセージをフォーマットして、より明確な情報を提供できる
  5. テスト容易性: 特定のエラー型をテストで検証しやすい

ベストプラクティス

  1. エラー型はポインタレシーバを使用する、なぜなら...

    • nil を返せるようになる(値レシーバでは nil を返せない)
      • テストを書くとき、興味のない依存関係に nil を使用できて便利
    • エラーインスタンスの同一性を保証できる
    • メモリ効率が良い(大きな構造体の場合コピーを避けられる)
    // Good
    type DatabaseError struct {
        Message string
        Code    int
    }
    func (e *DatabaseError) Error() string { ... }  // ポインタレシーバ
    
    // Bad
    func (e DatabaseError) Error() string { ... }   // 値レシーバ
  2. errors.As/errors.Is とは...

    • errors.Is: エラーチェーンの中で特定のエラー値と等しいものがあるか確認
    • errors.As: エラーチェーンの中で特定の型のエラーがあるか確認
    // errors.Is の例: エラー値の比較
    var ErrNotFound = errors.New("not found")
    if errors.Is(err, ErrNotFound) {
        // ErrNotFound または そのラップされたエラーの場合の処理
    }
    
    // errors.As の例: エラー型の確認
    var dbErr *DatabaseError
    if errors.As(err, &dbErr) {
        // DatabaseError型 または そのラップされたエラーの場合の処理
        fmt.Printf("Database error code: %d\n", dbErr.Code)
    }
  3. 共通のエラーは定数として定義する、例えば...

    // パッケージレベルでよく使うエラーを定義
    var (
        // 認証関連
        ErrUnauthorized = errors.New("unauthorized access")
        ErrInvalidToken = errors.New("invalid token")
    
        // データ操作関連
        ErrNotFound     = errors.New("resource not found")
        ErrDuplicate    = errors.New("duplicate resource")
    
        // 入力検証関連
        ErrInvalidInput = errors.New("invalid input")
        ErrRequired     = errors.New("required field missing")
    )
    
    // 使用例
    func GetUser(id string) (*User, error) {
        if !isAuthenticated() {
            return nil, ErrUnauthorized
        }
        if !isValidID(id) {
            return nil, ErrInvalidInput
        }
        // ...
    }
  4. エラーメッセージは 5W1H を意識して具体的に書く

    // Good: 具体的な情報を含む
    return fmt.Errorf("failed to connect to database at %s: %w", host, err)
    
    // Bad: 抽象的すぎる
    return errors.New("database error")
  5. エラーのラッピングは階層構造を意識する

    // 階層的なエラーハンドリングの例
    func UpdateUser(id string, data UserData) error {
        user, err := findUser(id)
        if err != nil {
            return fmt.Errorf("failed to find user: %w", err)
        }
    
        if err := validateUserData(data); err != nil {
            return fmt.Errorf("invalid user data: %w", err)
        }
    
        if err := saveUser(user); err != nil {
            return fmt.Errorf("failed to save user: %w", err)
        }
    
        return nil
    }

代入

// 1. 基本的な変数宣言と代入
var name string = "Taro"     // 型を明示的に指定
age := 25                    // 型推論による省略形(関数内でのみ使用可)

// 2. 複数の変数を同時に宣言・代入
var x, y int = 1, 2         // 複数の同じ型
i, s := 100, "hello"        // 異なる型でも可能

// 3. ゼロ値による初期化
var (
    number int               // 0
    text string             // ""
    check bool              // false
)

// 4. 定数の定義
const Pi = 3.14
const (
    StatusOK = 200
    StatusNotFound = 404
)

// 5. スライスやマップの初期化
colors := []string{"red", "blue", "green"}
scores := make(map[string]int)

// 6. 関数からの複数の戻り値の受け取り
result, err := someFunction()
if val, exists := map["key"]; exists {
    // キーが存在する場合の処理
}

// 7. アンダースコアを使用した値の破棄
_, err = someFunction()     // 最初の戻り値を無視

// 8. ポインタの代入
ptr := &value              // アドレスを取得
*ptr = newValue           // ポインタを介した値の変更

iota(定数の自動採番)

iota は、Go 言語で連番の定数を簡単に定義するための機能です。const ブロック内でのみ使用可能で、新しい行ごとに値が 1 ずつ増加します。

基本的な iota の使用パターン

1. シンプルな連番

const (
    Sunday = iota     // 0
    Monday           // 1
    Tuesday          // 2
    Wednesday        // 3
)

const (
    First = 1 + iota  // 1
    Second           // 2
    Third            // 3
)

2. ビットフラグとしての使用

const (
    Read = 1 << iota  // 1 (1 << 0)
    Write            // 2 (1 << 1)
    Execute          // 4 (1 << 2)
)

const (
    KB = 1 << (10 * iota)  // 1 << (10 * 0) = 1
    MB                     // 1 << (10 * 1) = 1024
    GB                     // 1 << (10 * 2) = 1048576
)

3. switch 文での使用

type BookStatus int

const (
    Available BookStatus = iota  // 0
    Swapped                     // 1
)

func (o BookStatus) String() string {
    return [...]string{"AVAILABLE", "SWAPPED"}[o]
}

func main() {
    status := Available
    fmt.Println(status) // "AVAILABLE" が出力される
    // なぜなら:
    // Available は 0 なので [0] = "AVAILABLE" が返される
    
    status = Swapped
    fmt.Println(status) // "SWAPPED" が出力される
    // なぜなら:
    // Swapped は 1 なので [1] = "SWAPPED" が返される
}
type Status int

const (
    Pending Status = iota
    Active
    Inactive
    Deleted
)

// String()メソッドを実装することで、出力時に文字列表現を得られる
func (s Status) String() string {
    return [...]string{"Pending", "Active", "Inactive", "Deleted"}[s]
}

func handleStatus(s Status) {
    switch s {
    case Pending:
        fmt.Println("処理待ち")
    case Active:
        fmt.Println("有効")
    case Inactive:
        fmt.Println("無効")
    case Deleted:
        fmt.Println("削除済み")
    default:
        fmt.Println("不明なステータス")
    }
}

// 使用例
func main() {
    status := Active
    handleStatus(status)  // 出力: 有効
    fmt.Println(status)   // 出力: Active(String()メソッドが呼ばれる)
}

4. 特殊なパターン

// スキップする場合
const (
    A = iota  // 0
    _         // 1 (スキップ)
    B         // 2
)

// 複数の値を同時に定義
const (
    A, B = iota, iota + 1  // 0, 1
    C, D                   // 1, 2
)

よくある使用例

// HTTPステータスコード
const (
    StatusOK = 200 + iota
    StatusCreated
    StatusAccepted
)

// ログレベル
const (
    DEBUG = iota
    INFO
    WARN
    ERROR
)

// アクセス権限
const (
    None = 0
    Read = 1 << iota  // 1
    Write             // 2
    Execute           // 4
    All = Read | Write | Execute
)

Slice

スライスは配列の要素を柔軟に操作するためのラッパーのようなものであり、動的なサイズ変更が可能なデータ構造です。 一方、配列は固定サイズでメモリ効率が良く、主に固定サイズのデータが必要な場面で使用されます。

基本的なスライスの操作

1. スライスの作成

// 既存の配列からスライスを作成
arr := [5]int{1, 2, 3, 4, 5} // arr: [1, 2, 3, 4, 5]
s := arr[1:4]  // sは[2, 3, 4]となる

// make関数を使用してスライスを作成
s := make([]int, 5)  // 長さ5のスライスを作成 [0, 0, 0, 0, 0]

arr := [...]int{1, 2, 3, 4, 5} // arr: [1, 2, 3, 4, 5]

2. スライスの一部を取得

// S[i:] はスライスのi番目から最後までを取得
s := []int{1, 2, 3, 4, 5}
subSlice := s[2:]  // subSliceは[3, 4, 5]となる

// S[:i] はスライスの最初からi番目までを取得
subSlice := s[:3]  // subSliceは[1, 2, 3]となる

// S[i:j] はスライスのi番目からj番目までを取得
subSlice := s[1:4]  // subSliceは[2, 3, 4]となる

3. スライスのコピー

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)  // dstは[1, 2, 3]となる

4. スライスの追加

s := []int{1, 2, 3}
s = append(s, 4)  // sは[1, 2, 3, 4]となる

// 複数要素を末尾追加
s = append(s, 5, 6)  // sは[1, 2, 3, 4, 5, 6]となる
4.1 先頭に追加
s := []int{2, 3, 4}
s = append([]int{1}, s...) // s は[1, 2, 3, 4]となる

5. スライスの容量と長さ

s :make([]int, 0, 5) // 長さ0、容量5のスライスを作成
// []
s := make([]int, 3, 5)  // 長さ3、容量5のスライスを作成
// [0, 0, 0]

// 長さの取得
length := len(s)  // 3

// 容量の取得
capacity := cap(s)  // 5

6. スライスの要素を削除

s := []int{1, 2, 3, 4, 5}

// 2番目の要素(値3)を削除
s = append(s[:2], s[3:]...)  // sは[1, 2, 4, 5]となる

応用的な操作

1. スライスの逆順

s := []int{1, 2, 3, 4, 5}
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
    s[i], s[j] = s[j], s[i]
}
// sは[5, 4, 3, 2, 1]となる

2. スライスのコピーとリサイズ

s := []int{1, 2, 3}
s = append(s[:2], s[2:]...)  // sは[1, 2, 3]と変わらない

s = append(s[:2], s[3:]...)  // 2番目の要素を削除し、[1, 2]となる

3. スライス内の要素の検索

s := []int{1, 2, 3, 4, 5}
target := 3
found := -1
for i, v := range s {
    if v == target {
        found = i
        break
    }
}
// foundは2(3が見つかったインデックス)となる。見つからなければ-1。

以下に、スライスの複製に関するチートシートを、コメントで結果が分かる形式でまとめました。

スライスの複製 (Shallow Copy vs. Deep Copy)

1. 浅いコピー(Shallow Copy)

s1 := []int{1, 2, 3}
s2 := s1  // s2は[1, 2, 3]となる(s1の浅いコピー)

s2[0] = 10  // s1とs2はどちらも[10, 2, 3]となる

2. 深いコピー(Deep Copy)

s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1)  // s2は[1, 2, 3]となる(s1の深いコピー)

s2[0] = 10  // s1は[1, 2, 3]のままで、s2は[10, 2, 3]となる

スライスの複製の詳細

1. 複製後のサイズと容量

s1 := []int{1, 2, 3}
s2 := make([]int, len(s1), cap(s1)+5)
copy(s2, s1)  // s2は[1, 2, 3]となる(容量はs1より大きい)

2. 二次元スライスの深いコピー

s1 := [][]int{
    {1, 2, 3},
    {4, 5, 6},
}

s2 := make([][]int, len(s1))
for i := range s1 {
    s2[i] = make([]int, len(s1[i]))
    copy(s2[i], s1[i])  // s2の各スライスはs1の各スライスの深いコピーとなる
}
// s2は[[1, 2, 3], [4, 5, 6]]となる

3. スライスの一部を複製

s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 2)
copy(s2, s1[1:3])  // s2は[2, 3]となる(s1の一部を深いコピー)

まとめ

  • 浅いコピー: スライスのポインタだけがコピーされ、元のデータは共有されるため、どちらかを変更するともう一方も変更されます。
  • 深いコピー: copy関数を使用して、新しいスライスに元のデータを全てコピーします。これにより、元のスライスと新しいスライスは独立します。

その他のテクニック

  • スライスのゼロ値: nilスライスは長さ 0 かつ容量 0 であり、要素を持たないが、append可能です。 以下に、2 次元配列の基本操作に関するチートシートを作成しました。これを先ほどの「Slice」と同じ階層に配置してください。

2 次元配列

2 次元配列は、配列の配列です。各要素はさらに配列となっており、行列のような構造を持ちます。Go では、2 次元配列のサイズは固定されており、スライスとは異なり動的なサイズ変更はできません。

基本的な 2 次元配列の操作

1. 2 次元配列の作成

// サイズが決まっている2次元配列を作成
var arr [3][4]int  // 3x4の2次元配列を作成(すべての要素は0)

// 初期値を設定して2次元配列を作成
arr := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

2. 2 次元配列の要素にアクセス

// 要素の取得と更新
arr := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

value := arr[1][2]  // 6を取得
arr[0][1] = 10      // arr[0][1]に10を設定

3. 2 次元配列のループ処理

arr := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

// すべての要素にアクセスするための二重ループ
for i := 0; i < len(arr); i++ {
    for j := 0; j < len(arr[i]); j++ {
        fmt.Println(arr[i][j])
    }
}

// rangeを使ったループ
for i, row := range arr {
    for j, val := range row {
        fmt.Printf("arr[%d][%d] = %d\n", i, j, val)
    }
}

4. 2 次元配列の長さを取得

arr := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

rows := len(arr)       // 行の数(2)
cols := len(arr[0])    // 列の数(3)

5. 2 次元配列のコピー

// 配列は固定サイズであるため、完全にコピーするには手動でループする必要があります
src := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

var dst [2][3]int
for i := range src {
    for j := range src[i] {
        dst[i][j] = src[i][j]
    }
}

以下に、Golang で 2 次元スライスに値を追加するためのチートシートを作成しました。コメントで簡単な説明を加えています。

2 次元スライス

2 次元スライスは、スライスのスライスとして実装されます。各スライスは異なる長さを持つことができ、動的に要素を追加することが可能です。

2 次元スライスの基本操作

1. 2 次元スライスの作成

// 空の2次元スライスを作成
matrix := [][]int{}

// 直接値を設定して作成
matrix := [][]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

2. 2 次元スライスに行を追加

appendはスライスの末尾に要素を追加し、容量が足りない場合は新しい配列を確保して返します。そのため、結果を元のスライスに再代入する必要があります。

// 新しい行を追加
newRow := []int{10, 11, 12}
matrix = append(matrix, newRow)
// matrixは[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]となる

3. 2 次元スライスの特定の行に要素を追加

// 1行目(インデックス0)に新しい要素を追加
matrix[0] = append(matrix[0], 4)
// matrixは[[1, 2, 3, 4], [4, 5, 6], [7, 8, 9]]となる

4. 2 次元スライスの特定の場所に要素を挿入

// 1行目(インデックス0)の2番目の位置(インデックス1)に値を挿入
index := 1
matrix[0] = append(matrix[0][:index+1], matrix[0][index:]...)
matrix[0][index] = 99
// matrixは[[1, 99, 2, 3, 4], [4, 5, 6], [7, 8, 9]]となる

5. 2 次元スライスの行を削除

// 2行目(インデックス1)を削除
matrix = append(matrix[:1], matrix[2:]...)
// matrixは[[1, 99, 2, 3, 4], [7, 8, 9]]となる

6. 2 次元スライスの特定の行から要素を削除

// 1行目(インデックス0)の3番目の要素(インデックス2)を削除
matrix[0] = append(matrix[0][:2], matrix[0][3:]...)
// matrixは[[1, 99, 3, 4], [7, 8, 9]]となる

補足

  • スライスは可変長であり、appendを使って動的に要素を追加できます。
  • 2 次元スライスはスライスのスライスであり、各行が異なる長さを持つことが可能です。
  • 挿入や削除の操作では、新しいスライスを作成して既存のスライスを再構築することが多いです。

2 次元スライス

2 次元スライスは、スライスのスライスとして実装されます。各スライスは異なる長さを持つことができ、動的に要素を追加することが可能です。

2 次元スライスの基本操作

1. 2 次元スライスの作成

// 空の2次元スライスを作成
matrix := [][]int{}

// 直接値を設定して作成
matrix := [][]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

2. 2 次元スライスに行を追加

// 新しい行を追加
newRow := []int{10, 11, 12}
matrix = append(matrix, newRow)
// matrixは[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]となる

3. 2 次元スライスの特定の行に要素を追加

// 1行目(インデックス0)に新しい要素を追加
matrix[0] = append(matrix[0], 4)
// matrixは[[1, 2, 3, 4], [4, 5, 6], [7, 8, 9]]となる

4. 2 次元スライスの特定の場所に要素を挿入

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

// 挿入したい位置のインデックス
index := 1

// 挿入する値
newValue := 99

// 挿入位置の前の部分をコピー
before := matrix[0][:index]  // beforeは[1]

// 挿入位置の後の部分をコピー
after := matrix[0][index:]   // afterは[2, 3]

// 新しいスライスを作成し、前の部分、新しい値、後の部分を結合
newRow := append(before, newValue)  // newRowは[1, 99]
newRow = append(newRow, after...)   // newRowは[1, 99, 2, 3]

// 元のスライスに新しい行を設定
matrix[0] = newRow

// 結果: matrixは[[1, 99, 2, 3], [4, 5, 6], [7, 8, 9]]となる

👆  同じ  👇

// 1行目(インデックス0)の2番目の位置(インデックス1)に値を挿入
index := 1
matrix[0] = append(matrix[0][:index+1], matrix[0][index:]...)
matrix[0][index] = 99
// matrixは[[1, 99, 2, 3, 4], [4, 5, 6], [7, 8, 9]]となる

5. 2 次元スライスの行を削除

// 2行目(インデックス1)を削除
matrix = append(matrix[:1], matrix[2:]...)
// matrixは[[1, 99, 2, 3, 4], [7, 8, 9]]となる

6. 2 次元スライスの特定の行から要素を削除

// 1行目(インデックス0)の3番目の要素(インデックス2)を削除
matrix[0] = append(matrix[0][:2], matrix[0][3:]...)
// matrixは[[1, 99, 3, 4], [7, 8, 9]]となる

補足

  • スライスは可変長であり、appendを使って動的に要素を追加できます。
  • 2 次元スライスはスライスのスライスであり、各行が異なる長さを持つことが可能です。
  • 挿入や削除の操作では、新しいスライスを作成して既存のスライスを再構築することが多いです。

このチートシートを参考にして、Golang の 2 次元スライスの操作を習得してください。

Map

マップの宣言と初期化

//----------------------------------------
// Map declaration and initialization
//----------------------------------------

// マップを宣言(初期化なし)
var m map[string]int

// マップを初期化
m = make(map[string]int)

// 宣言と初期化を同時に行う
m := make(map[string]int)

// リテラルで初期化
m := map[string]int{
    "apple":  120,
    "banana": 150,
}

値の設定と取得

//----------------------------------------
// Set and get values in a map
//----------------------------------------

// 値を設定
m["apple"] = 130

// 値を取得
price := m["apple"]
fmt.Println("Apple price:", price) // 出力: Apple price: 130

キーの存在確認

//----------------------------------------
// Check if a key exists in the map
//----------------------------------------

// キーの存在確認
price, exists := m["banana"]
if exists {
    fmt.Println("Banana price:", price)
} else {
    fmt.Println("Banana is not found")
}

値の削除

//----------------------------------------
// Delete a key-value pair from the map
//----------------------------------------

// 値の削除
delete(m, "apple")

マップの長さを取得

//----------------------------------------
// Get the number of key-value pairs in the map
//----------------------------------------

length := len(m)
fmt.Println("Number of items in the map:", length)

マップのイテレーション

//----------------------------------------
// Iterate over a map
//----------------------------------------

for fruit, price := range m {
    fmt.Printf("%s costs %d yen\n", fruit, price)
}

キーの一意性(重複不可)

//----------------------------------------
// Map keys must be unique
//----------------------------------------

// キーが重複すると、値が上書きされる
m["banana"] = 150
m["banana"] = 180 // 既存の"banana"の値が上書きされる

fmt.Println("Updated Banana price:", m["banana"]) // 出力: Updated Banana price: 180

ネストされたマップ

//----------------------------------------
// Nested maps
//----------------------------------------

productPrices := map[string]map[string]int{
    "fruits": {
        "apple":  100,
        "banana": 200,
    },
    "vegetables": {
        "carrot": 80,
        "onion":  50,
    },
}

// ネストされたマップの値の取得
applePrice := productPrices["fruits"]["apple"]
fmt.Println("Apple price from nested map:", applePrice) // 出力: Apple price from nested map: 100

マップのコピー(シャローコピー)

//----------------------------------------
// Shallow copy of a map
//----------------------------------------

newMap := make(map[string]int)
for k, v := range m {
    newMap[k] = v
}

// newMapはmのコピーだが、完全なディープコピーではない

マップと構造体の組み合わせ

//----------------------------------------
// Using maps with structs
//----------------------------------------

type Product struct {
    Name  string
    Price int
}

products := make(map[string]Product)
products["apple"] = Product{Name: "Apple", Price: 120}

// 構造体を使ったマップの値の取得
apple := products["apple"]
fmt.Println("Product:", apple.Name, "Price:", apple.Price) // 出力: Product: Apple Price: 120

マップの JSON 操作

//----------------------------------------
// Working with maps in JSON
//----------------------------------------

import (
    "encoding/json"
    "fmt"
)

// マップをJSONエンコード
jsonData, err := json.Marshal(m)
if err != nil {
    fmt.Println(err)
}
fmt.Println("JSON data:", string(jsonData)) // 出力: {"banana":180}

// JSONからマップをデコード
jsonStr := `{"apple":130,"banana":150}`
var decodedMap map[string]int
err = json.Unmarshal([]byte(jsonStr), &decodedMap)
if err != nil {
    fmt.Println(err)
}
fmt.Println("Decoded map:", decodedMap) // 出力: map[apple:130 banana:150]

注意点

  • マップのキーは一意でなければならない。同じキーを再度追加すると、そのキーの値が上書きされます。
  • マップは順序を保証しない。イテレーションの順序はランダムです。
  • マップのゼロ値はnilnilマップに対して要素を追加しようとするとパニックが発生しますので、必ず初期化する必要があります。

Sort

Slice

slice := []int{5, 2, 6, 3, 1, 4}

// 昇順ソート
sort.Slice(slice, func(i, j int) bool {
    return slice[i] < slice[j]
})
fmt.Println(slice) // [1 2 3 4 5 6]

// 降順ソート
sort.Slice(slice, func(i, j int) bool {
    return slice[i] > slice[j]
})
fmt.Println(slice) // [6 5 4 3 2 1]

シンプルに昇順ソートするには、sort.Intssort.Stringssort.Float64sを使う。

//----------------------------------------
// Sort slice
//----------------------------------------
nums := []int{42, -10, 0, 8}
sort.Ints(nums)
fmt.Println(nums) // [-10 0 8 42]

strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs)
fmt.Println(strs) // ["apple", "banana", "cherry"]

floats := []float64{3.14, 1.59, 2.65}
sort.Float64s(floats)
fmt.Println(floats) // [1.59, 2.65, 3.14]

逆順、カスタムソート

sort.Reverse(slice)

nums := []int{42, -10, 0, 8}

// IntSliceを使ってソート
sort.Sort(sort.IntSlice(nums)) // 昇順にソートされる
fmt.Println(nums) // [-10 0 8 42]

// 逆順にソート
sort.Sort(sort.Reverse(sort.IntSlice(nums)))
fmt.Println(nums) // [42 8 0 -10]

sort.Slice

strings の逆順(反転)ソート

//----------------------------------------
// Reverse string
//----------------------------------------
func reverseString(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

str := "hello"
reversed := reverseString(str)
fmt.Println(reversed) // "olleh"

ファイル操作

go.mod が存在するプロジェクトルートディレクトリを取得する

func getRootDir() (string, error) {
	currentDir, err := os.Getwd()
	if err != nil {
		return "", err
	}

	for {
		if _, err := os.Stat(filepath.Join(currentDir, "go.mod")); err == nil {
			return currentDir, nil
		}

		parentDir := filepath.Dir(currentDir)
		if parentDir == currentDir {
			return "", fmt.Errorf("go.mod not found")
		}
		currentDir = parentDir
	}
}

Usage:

targetFile := filepath.Join(getRootDir(), "file.txt")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment