hash table
이라 불리는 테이블에 딕셔너리 쌍(키, 값)이 저장 → b
개의 버킷으로 구분(ht[0], .., ht[b-1]
)s
개의 슬롯을 가지고 있음. 각 슬롯은 하나의 딕셔너리 쌍을 가질 수 있을 정도로 충분한 크기hash function
: 키가 k
인 딕셔너리 쌍의 주소 공간을 결정하는 함수. 즉 키를 버킷에 매핑하는 함수 → 모든 키 k
에 대해서 h(k) → 0 ~ b-1
h(k)
는 따라서 키 값 k
의 주소 공간T
중 테이블에 존재하는 딕셔너리 (키, 값) 쌍의 개수 n
이 어느 정도인지에 따라 결정. n/T
b
과 버킷이 개당 가지고 있는 슬롯의 개수 s
를 곱하면 저장 가능한 총 공간이 나오는데, 이 총 공간에 현재 어느 정도로 딕셔너리 (키, 값) 쌍이 저장되어 있는지 보여줌. a=n/(sb)
synonym
으로 판단overflow
: 딕셔나리 (키, 값) 쌍을 이미 버킷이 가득 찬 곳에 삽입하려 할 때 발생collision
: 새로운 딕셔너리 (키, 값) 쌍에 대한 버킷이 삽입하는 순간 비어 있지 않은 상태. 즉 다른 딕셔너리 쌍이 차지하고 있는 상태.overflow
가 없다면 삽입, 삭제, 탐색은 현재 해시 테이블 상에 저장된 딕셔너리 (키, 값) 쌍의 개수 n
과 상관없이 (1). 해시 함수 연산 시간 (2). 하나의 버킷을 탐색하는 시간 두 가지를 합한 시간 복잡도.collsion
이 최소화될 때 총 연산 비용 감소0...D-1
)이고, 적어도 D
개의 사이즈는 되어야 함D
는 일반적으로 소수로 정하는데, (1). 작으면 홈 버킷의 분포도가 커지고 (2). 크다면 바이어스의 정도가 감소한다. k
가 여러 개의 부분으로 나눠지는데, 가능한 같은 크기로 나뉜다.least significant digit
)는 마지막 부분의 상응하는 디지트와 매칭. 다른 부분이 해시 함수를 구하기 위해 가산된다.r
을 사용하는 수로 번역k
인 딕셔너리 쌍을 삽입할 때 해시 테이블을 순서대로 탐색overflow
): 테이블 사이즈를 늘려야 함loading density
가 0.75 이상일 때 테이블 사이즈를 늘리는 게 해시 함수 비용에 이로움해시 테이블 사이즈를 늘리면 해시 함수 역시 새롭게 만들어야 한다!
import Foundation
struct CustomHash {
private var dictionary: [String?]
// bucket == slot == 1
private var bucketSize:Int
private let primeNumber = 13
init(bucketSize: Int) {
self.bucketSize = bucketSize
dictionary = Array(repeating: nil, count: bucketSize)
}
private func getHashValue(_ key: String) -> Int {
let asciiSum = key.map{Int($0.asciiValue ?? 0)}.reduce(0, +)
return asciiSum % primeNumber
}
mutating private func makeDoubleDictionary() {
let newDictionary:[String?] = Array(repeating: nil, count: bucketSize)
bucketSize *= 2
dictionary += newDictionary
}
mutating func insert(_ key: String) {
let hashValue = getHashValue(key)
for i in 0..<bucketSize {
let validIdx = (hashValue + i) % bucketSize
if dictionary[validIdx] == nil {
dictionary[validIdx] = key
return
}
}
// overflow 발생 -> 해시 테이블 사이즈 2배로 늘리고 한 번더 insert 실행
makeDoubleDictionary()
insert(key)
}
func printDictionary() {
for item in dictionary.enumerated() {
if item.element != nil {
print("Index \(item.offset) : \(item.element!)")
}
}
}
}
var customHash = CustomHash(bucketSize: 5)
customHash.insert("dog")
customHash.insert("cat")
customHash.insert("tiger")
customHash.insert("lion")
customHash.printDictionary()
// Index 0 : cat
// Index 1 : tiger
// Index 2 : dog
// Index 3 : lion
customHash.insert("panda")
customHash.insert("horse")
customHash.insert("monkey")
customHash.insert("human")
customHash.insert("sheep")
customHash.printDictionary()
// Index 0 : cat
// Index 1 : tiger
// Index 2 : dog
// Index 3 : lion
// Index 4 : panda
// Index 5 : horse
// Index 6 : human
// Index 7 : sheep
// Index 9 : monkey
linear probing
에서 i
를 제곱하여 사용함Linear Probing
: 키 값 탐색이 다른 해시 값을 가지고 있는 딕셔너리 쌍과의 비교하기 때문에 효율적으로 좋지 않음synonyms
를 가지고 있음import Foundation
struct CustomChainingHash {
private var dictionary: [[String]]
private var bucketSize: Int
private let primeNumber = 13
init(bucketSize: Int) {
self.bucketSize = bucketSize
dictionary = Array(repeating: [String](), count: bucketSize)
}
private func getHashValue(_ key: String) -> Int {
let asciiSum = key.map{Int($0.asciiValue ?? 0)}.reduce(0, +)
return asciiSum % primeNumber
}
mutating func insert(_ key: String) {
let hashValue = getHashValue(key)
dictionary[hashValue % bucketSize].append(key)
// 특정 키가 버킷 리스트 내에 존재한다 하더라도 탐색하지 않고 그대로 삽입 (중복 키 존재 가능)
// 딕셔너리 버킷 아이템 내 키 존재 여부를 통해 결정 가능
}
func printDictionary() {
for item in dictionary.enumerated() {
if !item.element.isEmpty {
print("Index \(item.offset) :", terminator: " ")
for key in item.element {
print(key, terminator: " ")
}
print()
}
}
}
}
var customChainHash = CustomChainingHash(bucketSize: 5)
customChainHash.insert("lion")
customChainHash.insert("tiger")
customChainHash.insert("whale")
customChainHash.insert("cat")
customChainHash.insert("cow")
customChainHash.printDictionary()
// Index 0 : lion cat
// Index 1 : tiger
// Index 4 : whale cow
customChainHash.insert("sheep")
customChainHash.insert("fish")
customChainHash.insert("human")
customChainHash.insert("monkey")
customChainHash.insert("ape")
customChainHash.insert("zibra")
customChainHash.insert("pig")
customChainHash.printDictionary()
// Index 0 : lion cat sheep fish
// Index 1 : tiger ape
// Index 3 : zibra pig
// Index 4 : whale cow human monkey
loading density
→ 스레드홀드 초과 시 해시 테이블의 사이즈를 변경 + 해시 함수 변경해야 하는 오버헤드 존재rebuild
)이 단 한 개의 버킷에서만 일어나도록 하는 해싱 기법. 즉 적절한 해시 테이블 퍼포먼스를 보장하는 방법d
사용: 디렉토리 크기는 디렉토리 인덱스를 알아내기 위한 해시 함수 가 가지는 비트 개수에 따라 달라짐directory path
)라고 불림 import Foundation
struct DirectoryHash {
private var directories = Array(repeating: [String](), count: 4)
private var curDepth = 2
private let bucketSize = 2
private func getHashValue(_ key: String) -> Int {
// 각 키의 비트 값은 사전에 미리 딕셔너리로 기록
let hashDict = ["A0" : "100000", "A1" : "100001", "B0" : "101000", "B1" : "101001", "C1" : "110001", "C2" : "110010", "C3" : "110011", "C5" : "110101"]
guard let bits = hashDict[key] else { return -1 }
let bitsArray = Array(bits).map{String($0)}
var LSBString = ""
for idx in (bitsArray.count-curDepth+1)..<(bitsArray.count) {
LSBString += bitsArray[idx]
}
guard let LSB = Int(LSBString) else { return -1 }
return LSB
}
mutating func insert(_ key: String) {
let hashValue = getHashValue(key) % directories.count
if hashValue == -1 {
return
}
if directories[hashValue].count < bucketSize {
directories[hashValue].append(key)
} else {
let newDepthDirectories = Array(repeating: [String](), count: Int(pow(2.0, Double(curDepth))))
directories += newDepthDirectories
curDepth += 1
relocate(hashValue)
insert(key)
}
}
mutating func relocate(_ hashValue: Int) {
var removedKeys = [String]()
for key in directories[hashValue] {
let newHashValue = getHashValue(key)
if newHashValue != hashValue {
removedKeys.append(key)
}
}
for key in removedKeys {
guard let removedIdx = directories[hashValue].firstIndex(of: key) else { continue }
directories[hashValue].remove(at: removedIdx)
}
for key in removedKeys {
insert(key)
}
}
func printDirectories() {
for directoryItem in directories.enumerated() {
if !directoryItem.element.isEmpty {
print("idx: \(directoryItem.offset)", terminator: " ")
for item in directoryItem.element {
print(item, terminator: " ")
}
print()
}
}
}
}
var directoryHash = DirectoryHash()
directoryHash.insert("A0")
directoryHash.insert("B0")
directoryHash.insert("A1")
directoryHash.insert("B1")
directoryHash.insert("C2")
directoryHash.insert("C3")
directoryHash.printDirectories()
// idx: 0 A0 B0
// idx: 1 A1 B1
// idx: 2 C2
// idx: 3 C3
directoryHash.insert("C5")
directoryHash.printDirectories()
// idx: 0 A0 B0
// idx: 1 A1 B1
// idx: 2 C2
// idx: 3 C3
// idx: 5 C5
directoryHash.insert("C1")
directoryHash.printDirectories()
// idx: 0 A0 B0
// idx: 1 A1 C1
// idx: 2 C2
// idx: 3 C3
// idx: 5 C5
// idx: 9 B1