[Golang] Map

2021-11-29 hit count image

Golang에서 자료구조중 하나인 맵(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의 기본값인 ``이 반환된 것을 확인할 수 있습니다.

키값의 존재 여부

맵에 존재하지 않는 키에 접근하면, 값의 타입의 기본값이 반환되는 것을 알 수 있었습니다. 그럼 다음과 같이 값의 기본값을 저장한 경우, 이것이 값이 할당되지 않아 기본값이 반환되는지, 기본값을 할당한 것인지 어떻게 알 수 있을까요?

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을 대입한 것인지, 기본값이 출련된 것인지 알 수 없습니다.

여기서 맵의 두번째 반환값(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에서 사용되는 다른 자료 구조들인 리스트, 큐, 스택, 링이 궁금하신 분들은 이전 블로그 포스트를 참고하시기 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

책 홍보

스무디 한 잔 마시며 끝내는 React Native 책을 출판한지 벌써 2년이 다되었네요.
이번에도 좋은 기회가 있어서 스무디 한 잔 마시며 끝내는 리액트 + TDD 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
Posts