【Go言語入門】5.マップについてさくっと学ぶ

今回はマップについてキャッチアップしていきます。
配列と同様API周りでも不可欠な概念になってくるので、丁寧になぞっていきます。

前回の記事

マップはキーと値のペアを一要素として持っておくことができます。他の言語では連想配列ディクショナリといったりします。

初期化

一つの変数がキーと値のペアを複数保持できるので、「ユーザ情報」を例に挙げると視覚的にも概念を理解しやすいと思います。

package main

import "fmt"

var user map[string]int

func main() {
  user = make(map[string]int)
  user["level"] = 1
  user["age"] = 20
  fmt.Println(user)
}

// >>> map[age:20 level:1]

この場合、userの型情報としてはmap[string]intと定義されているので、キーはstring値はintのペアを保持することができます。

ちなみにマップのゼロ値はnilです。

また、スライスでも登場しましたが、make関数はスライスマップチャネルにのみ割り当て可能で、初期化した(ゼロ値ではない)値を返します。

package main

import "fmt"

var user map[string]int

func main() {
  // user := make(map[string]int)
  // user = make(map[string]int)
  // user["level"] = 1
  // user["age"] = 20
  fmt.Println(user == nil)
}

類似式が並んでいるので紛らわしいですが、user := make(map[string]int)の方のコメントイン・アウトを切り替えて試してみてください。

初期化されているかどうかを検証できます。

user = make(map[string]int)
user["level"] = 1
user["age"] = 20

また、この部分は

user = map[string]int{
  "level": 1,
  "age":   20,
}

このように書いても意味は同じです。

反復処理

前回のスライスの時に使用したforを用いた反復処理にも使えます。

package main

import "fmt"

var user map[string]int

func main() {
  user = map[string]int{
    "level": 1,
    "age":   20,
  }
  for k, v := range user {
    fmt.Println(k, v)
  }
}

// >>> level 1
// >>> age 20

スライスでのrangeを用いたfor文については前回の記事でまとめておりますのでよかったらご覧ください。

package main

import "fmt"

var user map[string]int
var clone map[string]int

func main() {
  user = map[string]int{
    "level": 1,
    "age":   20,
  }
  clone = make(map[string]int)
  for k, v := range user {
    clone[k] = v
  }
  fmt.Println(user, clone)
}

// >>> map[age:20 level:1] map[age:20 level:1]

マップでのrangeはキーと値を引っ張り出せるので、キーや値を比較するのに非常に便利になります。

上のコードのようなコピーは他の言語でもよく使います。

値渡しと参照渡し

コピーが出てきたので、ついでにスライスとマップについての注意点を挙げておきます。

スライスやマップは複数の要素を含んでいることから、わざわざ丁寧に反復処理して中の値を一つ一つ代入していくより、新たに変数を定義しているのだからそのまま代入してはだめなのか?ということです。

以下のような感じです。

package main

import "fmt"

func main() {
  s1 := []int{1, 2, 3}
  s2 := s1
  m1 := map[string]int{
    "level": 1,
    "age":   20,
  }
  m2 := m1
  fmt.Println("slice", s1, s2)
  fmt.Println("map", m1, m2)
}

// >>> s1: [1 2 3], s2: [1 2 3]
// >>> m1: map[age:20 level:1], m2: map[age:20 level:1]

一見一緒じゃん!となりそうですが、新たな変数に変更を加えると、問題が生じます。

それぞれ変更してみます。

package main

import "fmt"

func main() {
  s1 := []int{1, 2, 3}
  s2 := s1
  m1 := map[string]int{
    "level": 1,
    "age":   20,
  }
  m2 := m1

  s2[1] = 100
  m2["level"]++

  fmt.Printf("s1: %v, s2: %v\n", s1, s2)
  fmt.Printf("m1: %v, m2: %v\n", m1, m2)
}

// >>> s1: [1 100 3], s2: [1 100 3]
// >>> m1: map[age:20 level:2], m2: map[age:20 level:2]

このようにコピー元の中身も変更されてしまいます

これは値ではなく参照元が渡されているからです。(といっても初見だとイメージを掴みづらかったりするので、以降の記事で掘り下げていきます。)

package main

import "fmt"

func main() {
  s1 := []int{1, 2, 3}
  s2 := make([]int, len(s1))
  copy(s2, s1)
  m1 := map[string]int{
    "level": 1,
    "age":   20,
  }
  m2 := make(map[string]int)
  for k, v := range m1 {
    m2[k] = v
  }

  s2[1] = 100
  m2["level"]++

  fmt.Printf("s1: %v, s2: %v\n", s1, s2)
  fmt.Printf("m1: %v, m2: %v\n", m1, m2)
}

// >>> s1: [1 2 3], s2: [1 100 3]
// >>> m1: map[age:20 level:1], m2: map[age:20 level:2]

要素の更新・削除

マップの変更についてですが、更新は初期化時の代入と同様にすれば値は変更されます。

package main

import "fmt"

var user map[string]int

func main() {
  user = map[string]int{
    "level": 1,
    "age":   20,
  }
  user["level"] = 2
  user["level"]++

  fmt.Println(user)
}

// >>> map[age:20 level:3]

削除については関数deleteを使用します。

また、キー自体が存在するかの真偽判定にはキーを指定した複数代入によって検証が可能です。

package main

import "fmt"

var user map[string]int

func main() {
  user = map[string]int{
    "level": 1,
    "age": 20,
  }
  delete(user, "level")
  v, ok := user["level"]

  fmt.Println(user, v, ok)
}

// >>> map[age:20] 0 false

おまけですが、こちらも前回同様キーの有無確認だけに使用するのであればアンダースコア変数が有効です。

package main

import "fmt"

var user map[string]int

func main() {
  user = map[string]int{
    "level": 1,
    "age":   20,
  }
  delete(user, "level")
  _, ok := user["level"]

  fmt.Println(ok)
}

// false

これで様々な真偽判定が捗りますね。

マップにはスライス同様まだまだ色んな落とし穴や学ぶべき概念が隠れていると思いますが、以降も学習を進めていって掘り下げながら明確にしていこうと思います。