프로그래밍 언어 & 프레임워크/Golang

Go: 맵 (Map)

Map

은 자바스크립트의 객체 리터럴이나 파이썬의 사전과 비슷하게 키와 값을 가질 수 있는 자료구조다. 맵을 사용하면 배열과 슬라이스를 사용했을때 거쳐야 할 일부 번거로운 연산과정을 거칠 필요 없이 키와 값이 매핑되어 있기 때문에 접근이나 값을 수정하는 것이 수월하다. Go 언어의 맵은 데이터가 들어간 순서대로 정렬되지 않는다는 특징이 있어서 정렬된 데이터를 원한다면 별도의 정렬 함수를 사용하여 정렬해야 한다.

 

맵은 아래와 같이 사용할 수 있으며 슬라이스와 마찬가지로 make() 를 통해 생성해야 한다. 이 맵은 키가 스트링타입이며 값 또한 스트링 타입이다. 문법이 다소 특이하기 때문에 적응하기 어려울 수도 있다. 현재 Go 언어에서는 제네릭이 없다.

package main

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

그러나 리터럴로 맵을 사용할 때는 make() 를 사용할 필요가 없다.

package main

func main() {
	m := map[string]string{"sayHello": "Hello, Go!"}
}

제로 값

맵의 제로 값nil 이며, make() 를 통해 생성하지 않고 맵 타입을 가진 변수만 선언해두면 nil 맵이다. 또한 값을 할당하지 않은 키에 접근해두더라도 해당 값의 타입에 해당하는 제로 값이 할당되기 때문에 안전하다. 또한 nil 맵에 대해 연산을 수행하면 빈 맵처럼 여기게 되므로 세세한 처리에 대해서는 신경쓰지 않아도 된다.

package main

import "fmt"

func main() {
	var m map[string]string

	// -> true
	fmt.Println(m == nil)

	m = map[string]string{"sayHello": "Hello, Go!"}

	// -> ""
	fmt.Printf("%#v", m["Java"])
}

제어하기

맵에서 값을 추가하거나 삭제하는 것을 해보자. 아주 간단하지만 직접해볼 필요가 있다. 연관 배열의 형태로 사용하게 되면 해당 키에 해당하는 값에 접근할 수 있으며, 빌트인 함수인 delete() 함수를 사용하면 맵에서 특정 키에 해당하는 값을 제거할 수 있다.

package main

import "fmt"

func main() {
	m := map[string]string{"sayHello": "Hello, Go!"}
	m["Java"] = "verbose"

	// -> Java:verbose, sayHello:Hello, Go!
	fmt.Println(m)

	delete(m, "Java")
}

키의 존재여부 체크하기

할당되지 않은 키에 접근하면 해당 값의 제로 값으로 설정된다고 했다. 그런데 만약 맵에 존재하지 않는 키에 대해 다른 연산을 처리하고 싶다면 어떻게하면 될까. 타언어에서는 별도의 함수의 호출을 통해서 처리할 수 있는데, Go 에서는 이를 언어차원에서 지원하도록 해놓았다. 이러한 방식은 나도 처음보는 것이지만, 편리하기는 하다.

 

키에 접근할 때, 키에 해당하는 값 이외에도 또 하나의 값을 반환하게 되는데, 그것을 사용하면 해당 키가 실제로 맵에 존재하는 것인지 알 수 있다. 아래의 코드는 Java 라는 키가 존재할 경우 출력을 하도록 하는 코드인데, 일반적으로 키 체크에 대해서는 ok 라는 변수 이름을 사용한다.

package main

import "fmt"

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

	if _, ok := m["Java"]; ok {
		fmt.Printf("%#v", m["Java"])
	}
}

순회하기

맵을 순회하는 것은 배열이나 슬라이스와 똑같이 for ~ range 구문을 사용하면 된다. 반복자에는 키와 값이 할당되며 다만 주의해야 할 점은 맵은 순서가 지정되어 있지 않기 때문에 무엇이 먼저 나올지 모른다는 것이다. 아래의 코드를 보면 우리는 ABC 로 출력될 것을 기대하지만 사실 그렇지 않고 임의의 순서로 출력된다. 실행될 때마다 임의의 순서로 정해진다고 한다.

package main

import "fmt"

func main() {
	m := map[int]rune{0: 'A', 1: 'B', 2: 'C'}

	for _, r := range m {
		fmt.Println(r)
	}
}