Go를 배워보자 8일차 - map

0

Go

목록 보기
8/12

맵(map)

1. 맵이란

슬라이스나 배열은 데이터를 저장하는 자료구조이다. 그러나, 이들의 가장 큰 단점은 인덱스로만 검색을 해야한다는 것이다.

만약 내가 원하는 데이터를 KEY-VALUE 형식으로 가지고 오고싶으면 어떻게 해야할까?? 가령, 가수를 검색한다고 하면 key는 ph1이고 노래는 value일 것이다.

배열이나 슬라이스로 저장되어 있다면 모든 자료구조를 박박뒤져가며 key가 일치하는 데이터를 가져와야 할 것이다.

그러나, 이는 매우 불편하고 성능상의 문제도 치명적이다. 이에 따라 map이라는 자료구조가 존재하게 된 것이다.

맵은 key와 value로 이루어진 자료구조로 자신이 원하는 타입의 key로 값을 가져오는 방식이다.

2. map 선언

map을 선언하는 방법은 map이라는 키워드를 사용함으로서 선언할 수 있다.

var 맵변수이름 map[키타입]값타입

예시를 보도록 하자

ph1의 현재 등수를 알고싶다고 하자

var ranks map[string]int

다음과 같이 map을 선언하면 된다.

그러나, map은 슬라이스와 마찬가지로 값이 자동으로 생성되지 않으므로 make함수로 생성해야 한다.

make함수로 바로 값을 넘겨줄 수 있다면, 이는 단축 변수 선언을 사용할 수 있다는 것이다.

package main

import "fmt"

func main() {
	var ranks map[string]int = make(map[string]int)
	albums := make(map[string]string)
	ranks["ph1"] = 1
	albums["ph1"] = "HALO"
	fmt.Println(ranks["ph1"]) // 1
 	fmt.Println(albums["ph1"]) // HALO
}

3. map 리터럴

배열이나, 슬라이스처럼 미리 값을 알고있는 경우에 맵 리터럴을 사용할 수 있다.

맵변수이름 := map[키타입]값타입{키:값, 키:값, 키:값 , ...}
package main

import "fmt"

func main() {
	ranks := map[string]int{"ph1": 1, "changmo": 2}
	fmt.Println(ranks["ph1"]) // 1
	fmt.Println(ranks["changmo"]) // 2
}

다음과 같이 쓸 수있다는 것이다.

즉, make와 맵리터럴로 map을 생성하기 때문에 단축 변수 선언이 가능하다.
만약, make나 맵리터럴로 map을 생성하지 않으면 맵 변수에는 nil이 제로값으로 들어간다.

package main

import "fmt"

func main() {
	var name map[string]int
	fmt.Println(name) // map[]
	fmt.Println(name == nil) // true
}

map에 값이 할당되지 않았기 때문에 nil이 들어간다.

또한, 할당되지않은 키에 대한 값은 기본 값에 따른다. 가령 정수형은 0, 문자열은 "" 처럼 빈 문자열이 들어간다.

4. 할당된 값과 제로 값 구분하는 방법

만약, key로 접근했을 때 0이 나온다면 이 0이 default 제로 값인지, 아니면 그냥 내가 넣어준 의도대로 0이 나온 것인지 헷갈리게 된다.

package main

import "fmt"

func main() {
	name := map[string]int{}
	name["world"] = 0
	fmt.Println(name["hello"]) // 0
	fmt.Println(name["world"]) // 0
}

바로 다음과 같은 상황이다. hello는 0을 넣어주지 않았지만 기본적으로 key로 접근할 때 넣어준 적이 없는 값은 0 이 나온다. 반면 world에는 0을 넣어주었기 때문에 0이 나온다.

이와 같은 상황을 처리하기 위해 map은 key로 값에 접근할 때 두번째 리턴값으로 bool값을 리턴한다. bool은 해당 value가 유효한 값인지 아닌지를 표현하는 것으로, 해당 값이 없는데 접근할 경우 제로값인 것을 알려주기 위해 false, 유효한 값이면 true를 뱉는다.

package main

import "fmt"

func main() {
	name := map[string]int{}
	name["world"] = 1
	value, ok := name["hello"]
	fmt.Println(value, ok) // 0 false
	value, ok = name["world"]
	fmt.Println(value, ok) // 1 true
}

다음과 같이 확인할 수 있는 것이다.

한가지 특징적인 것은 c++처럼 map에 key로 바로 접근하면 value가 생기는 것은 아니다.

즉 c++의 경우에는 다음과 같은 경우 값이 생겨버려서 문제가 생길 때가 있다.

map<string, int> table;
if( table["hello"] == 1){
    //...
} // 이 이후로 table["hello"]은 값이 생겨서 값을 넣어준 것인지 default 값인지 구분이 안된다. 

그러나 golang의 map은 호출되어 값이 생성되어도 ok값으로 구분할 수 있다.

package main

import "fmt"

func main() {
	name := map[string]int{}
	fmt.Println(name["hello"]) // 0
	value, ok := name["hello"]
	fmt.Println(value, ok) // 0 false
}

따라서, 마음놓고 key로 접근해도 된다.

5. delete 함수를 사용해서 키/값 쌍 삭제하기

내장 함수 delete를 사용해서 삭제할 수 있다.

delete(map변수, "삭제하려는키")

다음과 같은 단순한 방법으로 해당 키값을 삭제할 수 있다.

package main

import "fmt"

func main() {
	name := map[string]int{}
	name["hello"] = 2
	value, ok := name["hello"]
	fmt.Println(value, ok) // 2 true

	delete(name, "hello")

	value, ok = name["hello"]
	fmt.Println(value, ok) // 0 false
}

삭제한 이후부터는 ok도 false로 나오는 것을 확인할 수 있다.

6. for ... range 를 이용한 map 출력하기

range 키워드를 사용해서 map에 있는 key, value 값을 가져와 순회할 수 있다.

for key, value := range myMap {
    // 루프
}

다음과 같은 방법을 취하게 된다.

이를 이용해서 map을 순회해보자

package main

import "fmt"

func main() {
	ranks := map[string]int{"ph1": 1, "changmo": 2, "ash": 3, "ler": 4}
	for key, value := range ranks {
		fmt.Println(key, value)
	}
}

결과

ph1 1
changmo 2
ash 3
ler 4

아마 결과가 다를 것이다. 이유는 map은 순서를 지키지 않는 자료구조이기 때문이다. 따라서, 매 실행마다도 순회되는 값들의 순서가 달라진다.

맵은 루프를 무작위로 처리한다는 사실을 알고가도록 하자

그럼, 정렬하여 map의 값들을 출력하고 싶다면 어떻게 해야할까??
맵은 따로 정렬이 불가능하므로, 슬라이스 하나를 만들어서 정렬해보도록 하자

key의 순서대로 정렬을 하고 싶다면 다음과 같이 하면된다.

package main

import (
	"fmt"
	"sort"
)

func main() {
	ranks := map[string]int{"ph1": 1, "changmo": 2, "ash": 3, "ler": 4}
	sortedKey := make([]string, 0)
	for key := range ranks {
		sortedKey = append(sortedKey, key)
	}
	sort.Strings(sortedKey)
	fmt.Println(sortedKey) // [ash changmo ler ph1]
	for _, key := range sortedKey {
		fmt.Println(ranks[key]) //3 2 4 1
	}
}

문자열로 정렬하였기 때문에 알파벳 사전 순서로 정렬이 된다.

0개의 댓글