[Golang] Map

2021-11-29 hit count image

Golangで資料構造の1つであるマップ(Map)を定義して使う方法について説明します。

概要

今回のブログポストではGolangでマップ(Map)について調べてみて、使う方法について説明します。このブログポストで紹介するコードは次のリンクで確認できます。

マップ(Map)

マップ(Map)はキーとバリューの形でデータを保存する資料構造です。プログラミング言語によってディクショナリー(Dictionary)、バッシュテーブル(Hash table)、ハッシュマップ(Hash map)などと呼ぶこともあります。

Golangでは次のようにマップを定義します。

// map[KEY_TYPE]VALUE_TYPE
map[string]int

make関数を使って変数を宣言すします。

m := make(map[string]string)

これを確認するため、main.goファイルを生成して次のように修正します。

package main

import "fmt"

func main() {
  m := make(map[string]string)

  m["name"] = "John"
  m["country"] = "Korea"
  fmt.Println(m)

  m["city"] = "Seoul"
  fmt.Println(m)

  fmt.Printf("No key: %s / %T\n", m["language"], m["language"])
}

これを実行すると次のような結果が表示されます。

# go run main.go
map[country:Korea name:John]
map[city:Seoul country:Korea name:John]
No key:  / string

マップに存在しないキーにアクセスすると、値のタイプのデフォルトの値がリターンされます。上の例題ではバリューのタイプがstringなので、stringのデフォルトである``が取得できることが確認できます。

キーの存在有無

マップに存在してないキーにアクセスすると、バリュータイプのデフォルトの値がリターンされます。そしたら、次のようにバリューのデフォルトである値を保存した場合、それが値が割り当てないことでデフォルトの値がリターンされたことか、デフォルトの値が割り当てたのかどうすれば分かりますか?

package main

import "fmt"

func main() {
  m := make(map[string]string)

  m["name"] = ""
  fmt.Printf("name: %s / %T\n", m["name"], m["name"])
  fmt.Printf("country: %s / %T\n", m["country"], m["country"])
}

これのため、Golangではキーを使ってマップに保存された値を取得する時、当該キーがマップに存在するかどうかを一緒にリターンします。

v, ok := m["name"]

当該キーがマップに存在する場合、ok変数はtrueになり、存在しない場合はfalseになります。

これを確認するためmain.goファイルを次のように修正します。

package main

import "fmt"

func main() {
  m := make(map[string]string)

  m["name"] = ""
  fmt.Printf("name: %s / %T\n", m["name"], m["name"])
  fmt.Printf("country: %s / %T\n", m["country"], m["country"])

  v, ok := m["name"]
  fmt.Println(v, ok)

  v, ok = m["country"]
  fmt.Println(v, ok)
}

これを実行すると次のような結果が出力されます。

# go run main.go
name:  / string
country:  / string
 true
 false

これで私たちは当該キーが存在するかどうかを確認することができます。

要素の削除

Golangでは次のようにdelete関数を使ってマップの要素を削除することができます。

delete(MAP_VARIABLE, KEY)

これを確認するためmain.goファイルを次のように修正します。

package main

import "fmt"

func main() {
  m := make(map[int]int)

  m[1] = 0
  m[2] = 2
  m[3] = 3

  v, ok := m[1]
  fmt.Println(v, ok)

  delete(m, 1)

  v, ok = m[1]
  fmt.Println(v, ok)
}

これを実行すると次のような結果が表示されます。

# go run main.go
0 true
0 false

delete関数を使って当該キーの要素を削除する前の値は0で、delete関数を使って当該要素を削除した時も0が取得されることが分かります。

delete関数を使って当該要素を削除した後、0がリターンされる理由はマップにそのキーが存在しなくて、バシューのデフォルト(int)である0がリターンされました。このように値だけ確認すると0を割り当てたことか、デフォルトが出力されたか分かりません。

ここでマップの2つのリターン値(ok)をチェックして当該値が設定した値か、削除されてデフォルトがリターンされたかを確認する必要があります。

マップの巡回

マップの全てのキー・バリューにアクセスするためには次のようにfor文とrangeを使います。

m := make(map[int]int)

for key, value := range m {
  fmt.Println(key, value)
}

これを確認するためmain.goファイルを次のように修正します。

package main

import "fmt"

type Product struct {
  Name  string
  Price int
}

func main() {
  m := make(map[int]Product)

  m[19] = Product{Name: "TV", Price: 3000}
  m[16] = Product{Name: "Phone", Price: 1000}
  m[18] = Product{Name: "PC", Price: 500}
  m[17] = Product{Name: "Tablet", Price: 2000}

  for key, value := range m {
    fmt.Println(key, value)
  }
}

これを実行すると次のような結果が表示されます。

# go run main.go
18 {PC 500}
17 {Tablet 2000}
19 {TV 3000}
16 {Phone 1000}

Golangではハッシュマップ(Hash map)を使ってます。そのため、マップの順番が保証されません。

  • Hash map: unsorted map
  • Sorted map: sorted map

マップ、配列、リストの比較

マップと配列、リストをBig-O表記法を使って比較すると下記のようです。

機能配列、スライスリストマップ
追加O(N)O(1)O(1)
削除O(N)O(1)O(1)
アクセスO(1)O(N)O(1)

マップは追加、削除、アクセスが全てO(1)で、性能はいいですが、for文を使って巡回する時、順番が保証されないことと、たくさんのメモリを使う欠点があります。

Big-O表記法について詳しく調べたい方は、下記のリンクをクリックして以前のブログポストを確認してください。

完了

これでGolangでマップを使う方法について見て見ました。Golangで使える他の資料構造であるリスト、キュー、スタック、リングについては下記のリンクで以前のブログポストを確認してください。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。

Posts