Go 기초 8. Map, Hash
Map
map은 Key와 Value 형태로 데이터를 저장하는 형태이다. 대표적인 예로 전화 번호부가 있다.
key = 이름 , value = 전화번호 라고 볼 수 있다.
Dictionary나 HashTable로 불리기도 한다.
Map 구현 방법
-
배열
Map을 구현 할때 배열로 하게 되면 특정 값을 찾을때 첫번째부터 하나 하나 비교해야 되서 O(N)으로 비효율 적인 방법이다. -
BST
BST방법을 이용하게 된다면 O(logN)으로 효율적으로 구현 할 수 있다. 이 방법을 Sorted Map 혹은 Ordered Map 이라고 한다. -
HASH
hash라는 함수를 이용하여 key를 입력하면 바로 value값을 뽑아주는 방법이다. 걸리는 시간은 O(1)으로 굉장히 효율적으로 Map을 구현 할 수 있다. 이 방법을 Hash Map 이라고 한다.
1. Hash 함수
*특징
- 출력 값의 범위가 정해져 있다.
- 같은 입력이면 같은 출력이 나온다.
- 다른 입력이면 다른 출력이 나오는 경우가 많다. (확정이 아님)
위의 특징을 만족시키는 것 들
1) 삼각함수 sin
입력이 무한대라도 나오는 출력 값은 -1~1**
0º = 0 , 90º = 1, 180º = 0, 270º = -1, 360º = 0,...
값이 일정한 것도 있는가 하면 다른 입력에 다른 출력이 나오는 경우도 있다.
2) 나머지(Modular) 연산
0 ~ ∞ % 12 을 했을때 출력 값이 0 ~ 11을 반복 하는것을 볼 수 있다. 항상 같은 출력 값도 가지고 다른 입력에 다른 출력 값도 가진다.
sin함수는 복잡하고 정수를 넣으면 실수가 나온다. 하지만 나머지 연산은 간단하고 정수를 넣으면 정수가 나온다는 장점이 있다.
3) one-way function
N x 3 = 9 라는 식이 있을때 우리는 이 N을 3이라는것을 알 수 있다. 그 이유는 x의 반대 연산인 ÷ 가 있기 때문이다. 이러한 특징을 two-way function 이라 한다.
하지만 나머지(Modular) 연산의 출력값을 보고 입력값을 찾아내는건 경우의 수가 무한대이기 때문에 어렵다. 예를 들어 N % 12 = 3 이라고 했을 때 N은 3, 15, 27,...∞ 이다. 이러한 연산을 one-way function 이라고 부른다.
이러한 특징을 이용하여 hash 함수는 암호화에 자주 사용된다.
Rolling Hash 구현
문자열이 s0 ~ sN 있을 때
s0의 해쉬 값 h0 = (h0-1 x a + s0) % b
s1의 해쉬 값 h1 = (h0 x a + s1) % b
s2의 해쉬 값 h2 = (h1 x a + s2) % b
...
이런 형식으로 해쉬값을 구해나 가는 것을 Rolling Hash 라고 한다.
이 때 한 문자열당 1byte 이므로 0 ~255 까지의 숫자가 구현 될 수 있다. 그렇기 때문에 a의 값은 '+ si' 보다 큰 값 256 정도로 지정해 준다.
b의 값은 소수(자신 이외의 자연수로는 나눌 수 없는, 1보다 큰 자연수)로 정해 주는것이 좋다.
소수를 사용하면 값의 분포도가 넓게 퍼진다.
예) 3571
ex) Rolling Hash package
package dataStruct
func Hash(s string) int {
h := 0
a := 256
b := 3571
for i := 0; i < len(s); i++ {
h = (h*a + int(s[i])) % b
}
return h
}
ex) Rolling Hash main
package main
import (
"Golesson/dataStruct"
"fmt"
)
func main() {
// 같은 값을 입력했을 경우
fmt.Println("abcde =", dataStruct.Hash("abcde"))
fmt.Println("abcde =", dataStruct.Hash("abcde"))
// 다른 값을 입력했을 경우
fmt.Println("abcdf =", dataStruct.Hash("abcdf"))
// 입력 값이 길 경우
fmt.Println("tbcdfdhdghddhd =", dataStruct.Hash("tbcdfdhdghddhd"))
}
---------------------------------
abcde = 2598
abcde = 2598 // 같음
abcdf = 2599 // 달라짐
tbcdfdhdghddhd = 1892 // 3571 범위 안
Hash Map 구현
구현 방법
- 0 ~ 3570 의 인덱스를 가지는 배열을 만든다.
- key 값으로 문자열을 받으면 hash 함수에 넣는다.
- hash 함수를 통해 나온 값에 해당하는 인덱스에 value값을 저장
- key 로 조회 했을 경우 같은 hash 값을 가지는 인덱스의 value를 반환
- 문제점
입력의 범위는 무한대 이지만 출력의 범위는 0 ~ 3570 까지 이다. 무한대의 입력이 들어왔을 때 출력이 유한대라면 정보는 손실 되고 이 것을 손실 압축 이라고 한다. 손실 압축에 경우 출력을 가지고 입력을 복원 할 수 없다(one-way fuction).
또한 다른입력에서 같은 값(hash 충돌)이 나올 수 있다.
즉 key AAA 와 key BBB 가 같은 hash 값을 가질 수 있다.
이 점을 방지 하기위해 각 각의 인덱스에 배열을 하나 더 만든다. 다른 입력에 같은 hash값이 나왔을 경우 같은 인덱스안에서 나눠서 저장한 다음 value를 찾는 방법이 있다.
ex) Hash Map package
package dataStruct
func Hash(s string) int {
h := 0
a := 256
b := 3571
for i := 0; i < len(s); i++ {
h = (h*a + int(s[i])) % b
}
return h
}
type keyValue struct {
key string
value string
}
type Map struct {
keyArray [3571][]keyValue
// 0 ~ 3570 만큼의 배열의 각 인덱스에 key와 value 값을 갖는 list를 만든다.
}
func (m *Map) Add(key, value string) {
h := Hash(key) // key에 대한 hash값
m.keyArray[h] = append(m.keyArray[h], keyValue{key, value})
// key의 hash값에 해당하는 인덱스에 key와 value를 가지는 리스트를 집어 넣음
}
// map을 만드는 함수
func CreatMap() *Map {
return &Map{}
}
// key에 대한 value를 가져오는 함수
func (m *Map) Get(key string) string {
h := Hash(key)
for i := 0; i < len(m.keyArray[h]); i++ {
if m.keyArray[h][i].key == key {
return m.keyArray[h][i].value
} // 현재 hash 값안의 리스트 에서 그 길이 만큼 돌아서 value를 찾는다.
}
return "" //없으면 빈값을 반환
}
ex) Hash Map main
package main
import (
"Golesson/dataStruct"
"fmt"
)
func main() {
m := dataStruct.CreatMap()
m.Add("AAA", "01012345789")
m.Add("BBB", "01055555555")
m.Add("asdfsdff", "01099999999")
m.Add("CCC", "010987654321")
fmt.Println("AAA =", m.Get("AAA"))
fmt.Println("CCC =", m.Get("CCC"))
fmt.Println("DDD =", m.Get("DDD"))
fmt.Println("asdfsdff =", m.Get("asdfsdff"))
}
-----------------------------
AAA = 01012345789
CCC = 010987654321
DDD =
asdfsdff = 01099999999
Hash Map
- 장점: find, add, remove 모두 다 O(1)로 빠르다.
- 단점: hash값에는 순서가 없기 때문에 정렬을 할 수가 없다.
Golang Map
go언어 에서는 기본적으로 map을 제공하고 있어서 직접 만들필요 없이 사용 가능하다.
1. map 선언 하기
var변수명 map[key타입]value타입
var m map[string]string
ex1)
package main
import (
"fmt"
)
func main() {
var m map[string]string
// 선언만 한다고 바로 쓸 수 없음
m = make(map[string]string)
// 초기화를 해줘야 됨
m["abc"] = "bbb"
fmt.Println(m["abc"])
m1 := make(map[int]string)
// 선언 초기화
m1[53] = "ddd"
fmt.Println(m1[53])
fmt.Println("m1[55] =", m1[55])
//없는 값을 입력할 경우 string의 default값 빈 문자열을 반환한다.
m2 := make(map[int]int)
m2[4] = 4
fmt.Println("m2[10] =", m1[10])
// int향의 default 값 0을 반환
m3 := make(map[int]bool)
// bool 형의 경우 설정된 값은 true 설정이 안된 값은 false
m3[4] = true
fmt.Println(m3[6], m3[4])
}
---------------------------
bbb
ddd
m1[55] =
m2[10] =
false true
설정을 안한 값은 default 값이 나온다고 한다면 만약 int형의 map에 0을 저장했을때 그게 default 값인지 내가 초기화한 값인지를 어떻게 구별 할 수 있을까?
ex2)
package main
import (
"fmt"
)
func main() {
m2 := make(map[int]int)
m2[4] = 4
m2[5] = 0
v, ok := m2[5]
v1 := m2[4]
v2, ok1 := m2[10]
// go map에서는 해당값이 있는지 없는지 확인 하기 위해 bool형 값을 동시에 반환해 준다.
fmt.Println(v, ok)
fmt.Println(v1)
fmt.Println(v2, ok1)
}
-----------------------------------
0 true // 존재하는 값
4
0 false // default 값
2. 삭제하기
ex3) delete
package main
import (
"fmt"
)
func main() {
m2 := make(map[int]int)
m2[4] = 4
m2[5] = 0
v, ok := m2[5]
v1 := m2[4]
v2, ok1 := m2[10]
fmt.Println(v, ok)
fmt.Println(v1)
fmt.Println(v2, ok1)
delete(m2, 5)
// 지우고 싶은 map의 key값을 입력
v, ok = m2[5]
fmt.Println(v, ok)
fmt.Println(v1)
fmt.Println(v2, ok1)
}
----------------------------------
0 true
4
0 false
0 false // m2[5] 가 삭제됨
4
0 false
3. 순회하기
ex3) for range
package main
import (
"fmt"
)
func main() {
m2 := make(map[int]int)
m2[4] = 4
m2[5] = 0
m2[2] = 2
m2[10] = 10
m2[8] = 8
for key, value := range m2 {
// m2의 모든 항목을 돌면서 key, value를 반환 한다.
fmt.Println(key, "=", value)
}
}
Author And Source
이 문제에 관하여(Go 기초 8. Map, Hash), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jinzza456/go-언어-기초-강좌-8저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)