갈루아의 반서재

map은 순서없는 key-value 쌍의 집합체이다. associative array 라고도 알려져 있으며, 해시 테이블 또는 딕셔너리라고도 표현되기도 한다. 연관 키를 사용하여 값을 찾을 때 사용된다. 아래의 예를 보자. 

1
var x map[string]int
cs

map 타입은 map 이라는 키워드 다음에 키의 타입이 괄호 안에 표시되고, 뒤이어 값의 타입이 표시된다. 위의 예시같은 경우 다음과 같이 읽으면 된다. 

“x is a map of strings to ints.”


배열이나 슬라이스와 마찬가지로 maps 은 괄호롤 사용해접근이 가능하다. 다음을 실행시켜보자. 

1
2
3
4
5
6
7
8
9
package main
 
import "fmt"
 
func main() {
    var x map[string]int
    x["key"= 10
    fmt.Println(x)
}
cs

실횅하면 다음과 같은 에러를 볼 수 있을 것이다.

1
2
3
4
5
6
7
8
~/go/src/golang-book/chapter6# go run main.go
panic: assignment to entry in nil map
 
goroutine 1 [running]:
main.main()
        /root/go/src/golang-book/chapter6/main.go:7 +0x4f
exit status 2
 
cs

지금까지는 컴파일 에러만 봤지만, 이번 건은 런타임 에러다. 이름에서 짐작할 수 있듯이, 컴파일 에러가 컴파일 당시 발생하듯, 런타임 에러는 프로그램을 실행할 때 발생한다. 

문제는 맵은 사용되기전 먼저 초기화되어야한다는 점이다. 다음과 같이 작성해야 한다.

1
2
3
4
5
6
7
8
9
10
package main
 
import "fmt"
 
func main() {
    x := make(map[stringint)
    x["key"= 10
    fmt.Println(x)
}
 
cs


1
2
3
~/go/src/golang-book/chapter6# go run main.go
map[key:10]
 
cs

int 타입의 키를 이용해 맵을 생성할 수도 있다.

1
2
3
4
5
6
7
8
9
10
package main
 
import "fmt"
 
func main() {
    x := make(map[int]int)
    x[1= 10
    fmt.Println(x[1])
}
 
cs


1
2
3
:~/go/src/golang-book/chapter6# go run main.go
10
 
cs

배열과 유사해보이지만, 몇 가지에서 차이점을 나타낸다. 

아이템을 추가할 때마다 맵의 길이가 달라진다는 점이다. 처음 생성되었을 때는 0 의 길이를 갖는데, x[1] = 10 이후에는 1의 길이를 가지게 된다. 둘째, 맵은 순차적이지 않다. 배열이 x[1] 이라면 x[0] 이 존재한다고 말할 수 있지만 맵에 대해서는 반드시 그렇다고 말할 수 없다. 

내장된 삭제 함수를 이용해서 맵에서 아이템을 삭제할 수 있다.

1
delete(x, 1)
cs

맵을 사용한 예제 프로그램을 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
 
import "fmt"
 
func main() {
  elements := make(map[string]string)
  elements["H"= "Hydrogen"
  elements["He"= "Helium"
  elements["Li"= "Lithium"
  elements["Be"= "Beryllium"
  elements["B"= "Boron"
  elements["C"= "Carbon"
  elements["N"= "Nitrogen"
  elements["O"= "Oxygen"
  elements["F"= "Fluorine"
  elements["Ne"= "Neon"
  fmt.Println(elements["Li"])
}
cs


1
2
3
~/go/src/golang-book/chapter6# go run main.go
Lithium
 
cs

이상은 심볼로 인덱스된 10개의 화학 원소를 나타내는 맵이다. lookup 테이블이나 딕셔너리처럼 맵을 사용하는 전형적인 예이다. 만약 존재하지 않는 원소를 다음과 같이 검색하면 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
 
import "fmt"
 
func main() {
  elements := make(map[string]string)
  elements["H"= "Hydrogen"
  elements["He"= "Helium"
  elements["Li"= "Lithium"
  elements["Be"= "Beryllium"
  elements["B"= "Boron"
  elements["C"= "Carbon"
  elements["N"= "Nitrogen"
  elements["O"= "Oxygen"
  elements["F"= "Fluorine"
  elements["Ne"= "Neon"
  fmt.Println(elements["Un"])
}
cs

아무 것도 반환되지 않음을 볼 수 있다.

1
2
3
~/go/src/golang-book/chapter6# go run main.go
 
~/go/src/golang-book/chapter6#
cs

기술적으로 맵은 비어있는 값에 대해서는 제로 값을 반환한다. 다음과 같은 조건문(elements["Un"] == "")으로 비어있는지 체크를 할 수도 있지만, Go 는 좀 더 나은 방법을 제공한다.

1
2
name, ok := elements["Un"]
fmt.Println(name, ok)
cs

맵의 원소에 접근하는 행위를 통해 2개의 값을 반환하게 된다. 첫 번째 값은 lookup 의 결과이고, 두번째는 lookup 이 성공적이었는지 알려준다. Go 에서는 다음과 같은 코드를 종종 보게 된다. 

1
2
3
if name, ok := elements["Un"]; ok {
  fmt.Println(name, ok)
}
cs

먼저 맵에서 값을 찾도록 한다. 그리고 그것이 성공적인 경우에만 블록안의 코드를 실행시키도록 하는 것이다. 








배열에서 이미 보았듯이 맵을 생성하는 좀 더 간략한 방법이 존재한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import "fmt"
 
func main() {
    elements := map[string]string{
      "H":  "Hydrogen",
      "He": "Helium",
      "Li": "Lithium",
      "Be": "Beryllium",
      "B":  "Boron",
      "C":  "Carbon",
      "N":  "Nitrogen",
      "O":  "Oxygen",
      "F":  "Fluorine",
      "Ne": "Neon",
    }  
    fmt.Println(elements["Ne"])
}
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
 
import "fmt"
 
func main() {
 
    elements := map[string]string{
      "H":  "Hydrogen",
      "He": "Helium",
      "Li": "Lithium",
      "Be": "Beryllium",
      "B":  "Boron",
      "C":  "Carbon",
      "N":  "Nitrogen",
      "O":  "Oxygen",
      "F":  "Fluorine",
      "Ne": "Neon",
    }  
    
    if name, ok := elements["Ne"]; ok {
        fmt.Println(name, ok)
    }
}
cs


1
2
3
~/go/src/golang-book/chapter6# go run main.go
Neon true
 
cs

맵은 종종 종합적인 정보를 저장하기 위해 활용할 수 있다. 단순히 원소의 이름 뿐만 아니라 표준 상태까지 저장하는 프로그램을 만들어보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main
 
import "fmt"
 
func main() {
  elements := map[string]map[string]string{
 
    "H": map[string]string{
      "name":"Hydrogen",
      "state":"gas",
    },
 
    "He": map[string]string{
      "name":"Helium",
      "state":"gas",
    },
 
    "Li": map[string]string{
      "name":"Lithium",
      "state":"solid",
    },
 
    "Be": map[string]string{
      "name":"Beryllium",
      "state":"solid",
    },
 
    "B":  map[string]string{
      "name":"Boron",
      "state":"solid",
    },
 
    "C":  map[string]string{
      "name":"Carbon",
      "state":"solid",
    },
 
    "N":  map[string]string{
      "name":"Nitrogen",
      "state":"gas",
    },
 
    "O":  map[string]string{
      "name":"Oxygen",
      "state":"gas",
    },
 
    "F":  map[string]string{
      "name":"Fluorine",
      "state":"gas",
    },
 
    "Ne":  map[string]string{
      "name":"Neon",
      "state":"gas",
    },
  }
 
  if el, ok := elements["Li"]; ok {
    fmt.Println(el["name"], el["state"])
  }
 
}
cs

실행결과는 다음과 같다.

1
2
3
~/go/src/golang-book/chapter6# go run main.go
Lithium solid
 
cs

맵의 타입이 map[string]string 에서 map[string]map[string]string 으로 변경되었음에 주목하자. 즉, 문자열을 문자열을 문자열로 매칭시키는 맵으로 매칭하는 맵을 가지게 된 셈이다. 바깥쪽 맵은 원소의 기호에 기반한 lookup 테이블처럼 사용되고, 안쪽 맵은 해당 원소에 대한 종합적인 정보를 저장하는데 사용된다.