ChatGPT - gpts: tool cheat sheet
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 の特徴:
-
シンプルな設計
- クラスや継承がない
- 暗黙的なインターフェース実装
- 命名規則によるアクセス制御
-
明示的な動作
- ポインタと値の使い分けが明確
- メソッドのレシーバーで意図を表現
-
少ない言語機能
- 継承の代わりに埋め込み
- アクセス修飾子の代わりに命名規則
- シンプルな構文
このような設計により、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
}// ポインタとして使う場合 (*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 インターフェースを実装した独自のエラー型で、より詳細なエラー情報の提供や型安全なエラーハンドリングを可能にします。
// 構造体としてカスタムエラーを定義
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)
}// エラーを返す関数の実装
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"
}// 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)
}
}// エラーをラップする構造体
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
}// エラー定数の定義
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")
}
}- 型安全性: エラーの型チェックが可能で、特定のエラーに対して適切な処理を行える
- 追加情報: エラーに関連する詳細情報を含められる
- エラーの階層化: エラーをラップして情報を追加しながらコールスタックを遡れる
- 可読性: エラーメッセージをフォーマットして、より明確な情報を提供できる
- テスト容易性: 特定のエラー型をテストで検証しやすい
-
エラー型はポインタレシーバを使用する、なぜなら...
- nil を返せるようになる(値レシーバでは nil を返せない)
- テストを書くとき、興味のない依存関係に nil を使用できて便利
- エラーインスタンスの同一性を保証できる
- メモリ効率が良い(大きな構造体の場合コピーを避けられる)
// Good type DatabaseError struct { Message string Code int } func (e *DatabaseError) Error() string { ... } // ポインタレシーバ // Bad func (e DatabaseError) Error() string { ... } // 値レシーバ
- nil を返せるようになる(値レシーバでは nil を返せない)
-
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) }
-
共通のエラーは定数として定義する、例えば...
// パッケージレベルでよく使うエラーを定義 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 } // ... }
-
エラーメッセージは 5W1H を意識して具体的に書く
// Good: 具体的な情報を含む return fmt.Errorf("failed to connect to database at %s: %w", host, err) // Bad: 抽象的すぎる return errors.New("database error")
-
エラーのラッピングは階層構造を意識する
// 階層的なエラーハンドリングの例 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 は、Go 言語で連番の定数を簡単に定義するための機能です。const ブロック内でのみ使用可能で、新しい行ごとに値が 1 ずつ増加します。
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
const (
First = 1 + iota // 1
Second // 2
Third // 3
)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
)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()メソッドが呼ばれる)
}// スキップする場合
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
)スライスは配列の要素を柔軟に操作するためのラッパーのようなものであり、動的なサイズ変更が可能なデータ構造です。 一方、配列は固定サイズでメモリ効率が良く、主に固定サイズのデータが必要な場面で使用されます。
// 既存の配列からスライスを作成
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]// 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]となるsrc := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // dstは[1, 2, 3]となる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]となるs := []int{2, 3, 4}
s = append([]int{1}, s...) // s は[1, 2, 3, 4]となるs :make([]int, 0, 5) // 長さ0、容量5のスライスを作成
// []s := make([]int, 3, 5) // 長さ3、容量5のスライスを作成
// [0, 0, 0]
// 長さの取得
length := len(s) // 3
// 容量の取得
capacity := cap(s) // 5s := []int{1, 2, 3, 4, 5}
// 2番目の要素(値3)を削除
s = append(s[:2], s[3:]...) // sは[1, 2, 4, 5]となる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]となるs := []int{1, 2, 3}
s = append(s[:2], s[2:]...) // sは[1, 2, 3]と変わらない
s = append(s[:2], s[3:]...) // 2番目の要素を削除し、[1, 2]となる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。以下に、スライスの複製に関するチートシートを、コメントで結果が分かる形式でまとめました。
s1 := []int{1, 2, 3}
s2 := s1 // s2は[1, 2, 3]となる(s1の浅いコピー)
s2[0] = 10 // s1とs2はどちらも[10, 2, 3]となる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]となるs1 := []int{1, 2, 3}
s2 := make([]int, len(s1), cap(s1)+5)
copy(s2, s1) // s2は[1, 2, 3]となる(容量はs1より大きい)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]]となる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 次元配列は、配列の配列です。各要素はさらに配列となっており、行列のような構造を持ちます。Go では、2 次元配列のサイズは固定されており、スライスとは異なり動的なサイズ変更はできません。
// サイズが決まっている2次元配列を作成
var arr [3][4]int // 3x4の2次元配列を作成(すべての要素は0)
// 初期値を設定して2次元配列を作成
arr := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}// 要素の取得と更新
arr := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
value := arr[1][2] // 6を取得
arr[0][1] = 10 // arr[0][1]に10を設定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)
}
}arr := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
rows := len(arr) // 行の数(2)
cols := len(arr[0]) // 列の数(3)// 配列は固定サイズであるため、完全にコピーするには手動でループする必要があります
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次元スライスを作成
matrix := [][]int{}
// 直接値を設定して作成
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}appendはスライスの末尾に要素を追加し、容量が足りない場合は新しい配列を確保して返します。そのため、結果を元のスライスに再代入する必要があります。
// 新しい行を追加
newRow := []int{10, 11, 12}
matrix = append(matrix, newRow)
// matrixは[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]となる// 1行目(インデックス0)に新しい要素を追加
matrix[0] = append(matrix[0], 4)
// matrixは[[1, 2, 3, 4], [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]]となる// 2行目(インデックス1)を削除
matrix = append(matrix[:1], matrix[2:]...)
// matrixは[[1, 99, 2, 3, 4], [7, 8, 9]]となる// 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次元スライスを作成
matrix := [][]int{}
// 直接値を設定して作成
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}// 新しい行を追加
newRow := []int{10, 11, 12}
matrix = append(matrix, newRow)
// matrixは[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]となる// 1行目(インデックス0)に新しい要素を追加
matrix[0] = append(matrix[0], 4)
// matrixは[[1, 2, 3, 4], [4, 5, 6], [7, 8, 9]]となる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]]となる// 2行目(インデックス1)を削除
matrix = append(matrix[:1], matrix[2:]...)
// matrixは[[1, 99, 2, 3, 4], [7, 8, 9]]となる// 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 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//----------------------------------------
// 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]- マップのキーは一意でなければならない。同じキーを再度追加すると、そのキーの値が上書きされます。
- マップは順序を保証しない。イテレーションの順序はランダムです。
- マップのゼロ値は
nil。nilマップに対して要素を追加しようとするとパニックが発生しますので、必ず初期化する必要があります。
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.Ints、sort.Strings、sort.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
//----------------------------------------
// 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"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")