Go 言語の辞書の基本

GolangGolangBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

こんにちは、Gophersの皆さん。この新しい章へようこそ。

この章では、マップの基本的な使い方を学び、Goにおけるnilの存在について分析します。

この章を終えると、マップを日常の開発タスクに適用できるようになります。

学習要点

  • マップの宣言
  • マップの初期化
  • マップ要素の追加、削除、更新、および検索
  • マップ要素が存在するかどうかの確認
  • マップの反復処理

辞書の紹介

では、辞書とは何でしょうか?

コンピュータサイエンスにおいて、辞書はキーと値のペアから構成される特殊なデータ構造です。

キーと値のペア:要素のペアで、一方の要素をキーと呼び、もう一方を値と呼ぶ。

ではなぜ辞書を使う必要があるのでしょうか?

辞書は要素の追加、削除、更新、および検索などの操作において高い効率を持っているため、皆さんにとってこのデータ構造は非常に便利であると思います。

画像の説明

マップの宣言

新しいデータ構造の場合、最初のステップはそれをどのように宣言するかを学ぶことです。

mapの場合、それを定義する方法は次のとおりです。

var variableName map[keyType]valueType

例えば:

var a map[string]int

ただし、mapは参照型であるため、上記のコードのように宣言すると問題が発生します。

~/projectディレクトリにmap.goファイルを作成しましょう。

touch ~/project/map.go

map.goに次のコードを記述します。

package main

func main() {
    // キー型がstringで値型がintのマップmを宣言する
    var m map[string]int
    // データエントリ "labex"->1を追加する
    m["labex"] = 1 // プログラムがクラッシュする
}

プログラムを実行します。

go run map.go

プログラムがクラッシュし、エラーが表示されることがわかります。

panic: assignment to entry in nil map

これは、nil mapに要素を割り当てることは誤った動作であることを意味します。

これは、スライス、マップ、チャネル、ポインターなどのデータ構造は使用前に初期化する必要があるためです。

そしてnilはマップの初期デフォルト値であり、定義時に変数にメモリが割り当てられないことを意味します。

初期値nil

前節では、nilマップに要素を割り当てることは誤った動作であることを述べました。

この機会に、Goにおけるnilの真の意味を探ってみましょう。

nilの本質は、事前に宣言された識別子です。

基本データ型の場合、それらの初期値は異なります。

  • ブール値
  • 数値
  • 文字列

しかし、スライス、辞書、ポインタ、チャネル、関数の場合、それらの初期値はnilです。これは、私たちが慣れ親しんだ初期デフォルト値ではありません。

これは、nilが割り当てられたインスタンス化オブジェクトに反映されます。印刷することはできますが、使用することはできません。

また、nilに関しては、いくつか注意する点があります。

異なる型のnilの比較不可

package main

import "fmt"

func main() {
    var m map[int]string
    var p *int
    fmt.Printf("%v", m == p)
}

プログラムの出力は次のとおりです。

invalid operation: m == p (mismatched types map[int]string and *int)

つまり、int型ポインタのnilとマップのnilを比較することはできません。

それらは比較可能ではありません。

nilはキーワードではない

package main

import "fmt"

func main() {
    var nilValue = "= =$"
    fmt.Println(nilValue)
}

プログラムの出力は次のとおりです。

= =$

nilという名前の変数を定義することができ、エラーなくコンパイルすることができます。ただし、実際の開発ではこのようなことを行わないことを強くお勧めします。

nilの比較不可

package main

import "fmt"

func main() {
    fmt.Println(nil == nil)
}

プログラムの出力は次のとおりです。

invalid operation: nil == nil (operator == not defined on nil)

makeキーワードを使った宣言

nilの意味を理解した後、辞書のメモリ割り当ての問題を解決しましょう。

ここでは、makeキーワードを使ってメモリを割り当てます。

make関数は、初期化された指定された型の値を返り値として生成します。

package main

import "fmt"

func main() {
    var m map[string]int     // 辞書を宣言する
    m = make(map[string]int) // 辞書にメモリを割り当てる
    m["labex"] = 1       // 辞書にデータを追加する
    fmt.Println(m)
}

プログラムの出力は次のとおりです。

map[labex:1]

上記のコードは、makeキーワードを使って宣言された辞書にメモリをどのように割り当てるかを示しています。

このプロセスを簡略化することもできます。

map.goに次のコードを記述します。

package main

import "fmt"

func main() {
    m := make(map[string]int) // 辞書を宣言して初期化する
    m["labex"] = 666      // 辞書にデータを追加する
    fmt.Println(m)
}

プログラムの出力は次のとおりです。

map[labex:666]

上記のコードは、makeキーワードを使って辞書をどのように宣言するかを示しています。

空のマップを手動で初期化する

前節では、makeキーワードを使ってマップを初期化する方法を示しました。では、もう一つの方法を見てみましょう:リテラル構文を使って空のマップを手動で初期化する方法です。

この方法は簡潔で、明示的にメモリを割り当てることなく使用できる空のマップを作成できます。

map.goに次のコードを記述します。

package main

import "fmt"

func main() {
    // リテラル構文を使って空のマップを初期化する
    m := map[string]int{}

    // マップに要素を追加する
    m["labex"] = 777
    m["labs"] = 11

    fmt.Println(m) // 追加された要素付きのマップを出力する
}

構文map[keyType]valueType{}は、使用できるようになった空のマップを作成します。初期化後、構文map[key] = valueを使ってマップに要素を追加できます。

上記のコードを実行すると、プログラムは次のように出力されます。

map[labex:777 labs:11]

手動初期化の利点:

  • makeキーワードを使わずに空のマップを作成する迅速な方法を提供します。
  • 空のマップから始め、動的に要素を追加する必要がある場合に便利です。

辞書を実際に初期化する

空の辞書を初期化できるので、いくつかの初期値を与えることもできます。

map.goに次のコードを記述します。

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
    }
    m["labby"] = 666
    fmt.Println(m)
}

プログラムの出力は次のとおりです。

map[labby:666 labs:11 labex:777]

Goで辞書を初期化する際には、最後の要素を含め、各要素の後にコンマを追加する必要があることに注意してください。

辞書要素の追加と更新

辞書に要素を追加するのは非常に簡単で、上のコード例に示されています。

構文は次の通りです。

dictionaryInstance[keyToAdd] = valueToAdd

例えば:

m := map[string]int{}
m["labby"] = 666

注:Goでは、map内の各keyは一意でなければなりません。

map.goに次のコードを記述します。

package main

import "fmt"

func main() {
    m := map[string]int{}
    m["labby"] = 666
    m["labby"] = 777
    fmt.Println(m)
}

プログラムの出力は次のとおりです。

map[labby:777]

出力の値が777に変更されたことがわかりました。つまり、同じkeyに異なる値を書き込むと、対応するvalueが新しい値に更新されます。

これが辞書を更新または変更する方法です。

辞書要素の削除

辞書から要素を削除するにはどうすればよいでしょうか。

組み込みのdelete関数を使う必要があります。

map.goに次のコードを記述します。

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
        "labby": 666,
    }
    fmt.Println(m)
    delete(m, "labs")
    fmt.Println(m)
}

プログラムの出力は次のとおりです。

map[labby:666 labs:11 labex:777]
map[labby:666 labex:777]

delete関数は2つの引数を取ります。最初の引数は操作対象の辞書で、2番目の引数は削除するキーです。

もちろん、delete関数には他にも用途がありますが、この実験では辞書におけるその使い方についてのみ議論します。

辞書要素の検索

存在しない辞書要素を検索するとどうなりますか。

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
}

プログラムの出力は次のとおりです。

The value of labs is: 0

辞書に要素が存在しない場合、それを照会すると値型の初期値が返されることがわかりました。

値が0である辞書内のキーはどうでしょうか。

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
    fmt.Print("The value of labex is: ")
    fmt.Println(m["labex"])
}

プログラムの出力は次のとおりです。

The value of labs is: 0
The value of labex is: 0

辞書において、存在しない値と存在するが初期値の値の表現が同じであることがわかりました。

これは大きな混乱の原因になります。どう解決すればよいでしょうか。

実は、Goの開発者たちはこの問題を既に考えていました。辞書内の要素を照会する際、1つまたは2つの変数戻り値があります。

つまり:

labs, ok := m["labs"]

map.goを変更します。

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    labs, ok := m["labs"]
    fmt.Print("The value of labs is: ")
    fmt.Print(labs)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok)
    labex, ok2 := m["labex"]
    fmt.Print("The value of labex is: ")
    fmt.Print(labex)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok2)
}

プログラムの出力は次のとおりです。

The value of labs is: 0 Does it exist? false
The value of labex is: 0 Does it exist? true

これで、照会の2番目の戻り値を使って、返された結果が存在する初期値か、存在しない初期値かを判断することができます。

辞書の反復処理

特定のシナリオでは、辞書全体を反復処理し、各キー-値ペアを照会して処理する必要があります。これをどのように実現できるでしょうか。

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "labex": 777,
        "labs":   11,
        "labby":     666,
    }
    for key, value := range m {
        fmt.Println(key, value)
    }
}

プログラムの出力は次のとおりです。

labby 666
labs 11
labex 777

出力からわかるように、辞書を反復処理する方法は、スライスや配列を反復処理する方法と非常に似ています。

まとめ

この実験では、辞書の基本的な使い方について学びました。これには、以下が含まれます。

  • 辞書の宣言と宣言の問題への対処法
  • 初期値nilの特性
  • 辞書の初期化方法
  • 辞書内の要素の追加、削除、更新、および検索
  • 辞書要素の存在を検出する方法
  • 辞書の反復処理