Map の話

今回も覚え書き用の小ネタ。

map による連想配列の定義と基本操作

まず, map を使って以下の連想配列を考える。

type platnets map[string]float64

p := platnets{
    "Mercury": 0.055,
    "Venus":   0.815,
    "Earth":   1.0,
}

この連想配列の各要素を操作する記述は以下のような感じになる。

package main

import (
    "fmt"
)

type platnets map[string]float64

func main() {
    p := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    if v, ok := p["Mars"]; ok {
        fmt.Println("Mars =", v)
    } else {
        fmt.Println("Mars is not exist.")
    }

    p["Mars"] = 0.107 //add or set
    if v, ok := p["Mars"]; ok {
        fmt.Println("Mars =", v)
    } else {
        fmt.Println("Mars is not exist.")
    }

    delete(p, "Mars")
    if v, ok := p["Mars"]; ok {
        fmt.Println("Mars =", v)
    } else {
        fmt.Println("Mars is not exist.")
    }
}

このコードの実行結果は以下の通り。

Mars is not exist.
Mars = 0.107
Mars is not exist.

この連想配列の要素を全て取り出す。 これは for range 構文を使ってこんな感じに記述する。

package main

import "fmt"

type platnets map[string]float64

func main() {
    p := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }
    for k, v := range p {
        fmt.Println(k, v)
    }
}

結果は以下の通り。

Mercury 0.055
Venus 0.815
Earth 1

ちなみに for range 構文で map を使う場合,取り出しの順番は不定になる1。 例えば,アルファベット順に取り出したければ以下のようにキーをソートした配列を用意する。

package main

import (
    "fmt"
    "sort"
    "strings"
)

type platnets map[string]float64

func main() {
    p := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    keys := []string{}
    for k, _ := range p {
        keys = append(keys, k)
    }
    sort.Slice(keys, func(i, j int) bool {
        return strings.Compare(keys[i], keys[j]) < 0
    })
    for _ , k := range keys {
        fmt.Println(k, p[k])
    }
}

結果は以下の通り

Earth 1
Mercury 0.055
Venus 0.815

map は連想配列を参照するオブジェクトである

map連想配列への参照を属性として持つオブジェクトである。 したがって map インスタンスを引数として渡した場合は見かけ上「参照渡し」として機能する。

package main

import "fmt"

type platnets map[string]float64

func (p platnets) print() {
    for k, v := range p {
        fmt.Println(k, v)
    }
}

func add(p platnets, k string, v float64) {
    p[k] = v
}

func main() {
    p := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }
    p.print()
    fmt.Println()

    add(p, "Mars", 0.107)
    p.print()
}

このコードの実行結果は以下の通り。

Mercury 0.055
Venus 0.815
Earth 1

Mercury 0.055
Venus 0.815
Earth 1
Mars 0.107

連想配列の複製

map 内の連想配列の複製を行う関数は用意されていない2 ため for range 構文で地道に処理する。

package main

import "fmt"

type platnets map[string]float64

func (p platnets) print() {
    for k, v := range p {
        fmt.Println(k, v)
    }
}

func (p platnets) copy() platnets {
    c := platnets{}
    for k, v := range p {
        c[k] = v
    }
    return c
}

func main() {
    p1 := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    p2 := p1.copy()
    p2["Mars"] = 0.107

    p1.print()
    fmt.Println()
    p2.print()
}

このコードの実行結果は以下の通り。

Mercury 0.055
Venus 0.815
Earth 1

Mercury 0.055
Venus 0.815
Earth 1
Mars 0.107

連想配列の比較

map インスタンス同士は == 演算子による比較ができない。 以下のコードはコンパイルエラーになる3

package main

import "fmt"

type platnets map[string]float64

func (p platnets) copy() platnets {
    c := platnets{}
    for k, v := range p {
        c[k] = v
    }
    return c
}

func main() {
    p1 := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    p2 := p1.copy()
    if p1 == p2 {
        fmt.Println("p1 == p2")
    } else {
        fmt.Println("p1 != p2")
    }
}
prog.go:23:8: invalid operation: p1 == p2 (map can only be compared to nil)

map が参照している連想配列の内容を比較したいのであれば reflect.DeepEqual() 関数が使える。

package main

import (
    "fmt"
    "reflect"
)

type platnets map[string]float64

func (p platnets) copy() platnets {
    c := platnets{}
    for k, v := range p {
        c[k] = v
    }
    return c
}

func main() {
    p1 := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    p2 := p1.copy()
    if reflect.DeepEqual(p1, p2) {
        fmt.Println("p1 == p2")
    } else {
        fmt.Println("p1 != p2")
    }
}

このコードの実行結果は以下の通り。

p1 == p2

map が参照している連想配列のインスタンスが同一であるかどうか調べるには reflect.ValueOf() 関数で値(=連想配列)を取得し,そのポインタ値を == 演算子で比較する。

package main

import (
    "fmt"
    "reflect"
)

type platnets map[string]float64

func (p platnets) copy() platnets {
    c := platnets{}
    for k, v := range p {
        c[k] = v
    }
    return c
}

func main() {
    p1 := platnets{
        "Mercury": 0.055,
        "Venus":   0.815,
        "Earth":   1.0,
    }

    p2 := p1
    if reflect.ValueOf(p1).Pointer() == reflect.ValueOf(p2).Pointer() {
        fmt.Println("p1 == p2")
    } else {
        fmt.Println("p1 != p2")
    }

    p2 = p1.copy()
    if reflect.ValueOf(p1).Pointer() == reflect.ValueOf(p2).Pointer() {
        fmt.Println("p1 == p2")
    } else {
        fmt.Println("p1 != p2")
    }
}

このコードの実行結果は以下の通り。

p1 == p2
p1 != p2

ブックマーク

参考図書

photo
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan Brian W. Kernighan 柴田 芳樹
丸善出版 2016-06-20
評価

スターティングGo言語 (CodeZine BOOKS) Go言語によるWebアプリケーション開発 Kotlinスタートブック -新しいAndroidプログラミング Docker実戦活用ガイド グッド・マス ギークのための数・論理・計算機科学

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。

reviewed by Spiegel on 2016-07-13 (powered by G-Tools)


  1. 言語仕様上定義されていないという意味ではなく,意図的に乱択されるらしい。 [return]
  2. 代入構文では map インスタンスの複製ができるだけで,参照している連想配列は同じものになる。 [return]
  3. &p1 == &p2 ならコンパイルエラーにはならないが,やってることは map インスタンスのポインタ値を比較しているだけである。 [return]