
hash table이라 불리는 테이블에 딕셔너리 쌍(키, 값)이 저장 → b개의 버킷으로 구분(ht[0], .., ht[b-1])s 개의 슬롯을 가지고 있음. 각 슬롯은 하나의 딕셔너리 쌍을 가질 수 있을 정도로 충분한 크기hash function: 키가 k인 딕셔너리 쌍의 주소 공간을 결정하는 함수. 즉 키를 버킷에 매핑하는 함수 → 모든 키 k에 대해서 h(k) → 0 ~ b-1h(k)는 따라서 키 값 k의 주소 공간T 중 테이블에 존재하는 딕셔너리 (키, 값) 쌍의 개수 n이 어느 정도인지에 따라 결정. n/Tb과 버킷이 개당 가지고 있는 슬롯의 개수 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