- 1. Pengenalan Hashing
- 2. Hash Function
- 3. Hash Table
- 4. Collision Handling
- 5. Map (Key-Value Pair)
- 6. HashMap dengan Generics
- 7. Kapan Menggunakan Hashing dan Map
- 7.1 Hash Table vs Struktur Data Lain
- 7.2 Kapan Menggunakan Hash Table / HashMap
- 7.3 Kapan Menggunakan HashSet
- 7.4 Kapan Menggunakan LinkedHashMap
- 7.5 Kapan Menggunakan TreeMap
- 7.6 Chaining vs Open Addressing
- 7.7 Tabel Keputusan Pemilihan
- 7.8 Kapan TIDAK Menggunakan Hash Table
- 7.9 Tips Memilih Hash Function
- 7.10 Best Practices
- 8. Kompleksitas Hashing
- 9. Perbandingan dengan Java Collections Framework
- 10. Latihan Praktikum
- 11. Tugas Praktikum
Hashing adalah teknik untuk memetakan data berukuran besar ke nilai berukuran tetap (hash value/hash code) menggunakan fungsi matematika yang disebut Hash Function. Hash value ini kemudian digunakan sebagai indeks untuk menyimpan dan mengambil data dengan cepat.
Bayangkan Anda memiliki 1 juta data dan ingin mencari satu data tertentu:
- Array/List: Pencarian linear O(n) = 1 juta operasi (worst case)
- Binary Search: O(log n) = ~20 operasi (data harus terurut)
- Hashing: O(1) = 1 operasi (rata-rata)!
┌─────────────────────────────────────────────────────────────┐
│ HASHING PROCESS │
├─────────────────────────────────────────────────────────────┤
│ │
│ Key (Input) Hash Function Index │
│ ┌─────────┐ ┌───────────┐ ┌─────────┐ │
│ │ "John" │ ─────> │ h(key) │ ───> │ 3 │ │
│ └─────────┘ └───────────┘ └─────────┘ │
│ │
│ Hash Table │
│ ┌────┬─────────────┐ │
│ [0] │ │ │ │
│ [1] │ │ │ │
│ [2] │ │ │ │
│ [3] │ ──>│ "John" │ <── Data disimpan │
│ [4] │ │ │ │
│ [5] │ │ │ │
│ └────┴─────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Istilah | Penjelasan |
|---|---|
| Key | Data input yang akan di-hash (bisa string, integer, object) |
| Hash Function | Fungsi matematika yang mengkonversi key menjadi index |
| Hash Value/Code | Hasil dari hash function |
| Hash Table | Array yang menyimpan data berdasarkan hash value |
| Bucket | Slot/posisi dalam hash table |
| Collision | Ketika dua key berbeda menghasilkan hash value yang sama |
| Load Factor | Rasio jumlah elemen terhadap ukuran tabel (n/m) |
- Operasi insert, delete, search rata-rata O(1)
- Efisien untuk data berukuran besar
- Cocok untuk implementasi cache, database indexing
- Membutuhkan memori tambahan
- Collision bisa menurunkan performa
- Tidak mendukung operasi range query (data tidak terurut)
- Pemilihan hash function yang buruk bisa menyebabkan banyak collision
Berikut adalah class diagram untuk struktur data Hashing dan Map yang akan dibahas dalam modul ini:
classDiagram
class HashEntry {
-int key
-int value
-boolean isDeleted
+HashEntry(key: int, value: int)
+getKey() int
+getValue() int
+setValue(value: int) void
+isDeleted() boolean
+setDeleted(deleted: boolean) void
}
class HashTable {
-HashEntry[] table
-int capacity
-int size
-double loadFactor
+HashTable(capacity: int)
+hash(key: int) int
+put(key: int, value: int) void
+get(key: int) Integer
+remove(key: int) void
+containsKey(key: int) boolean
+getSize() int
+isEmpty() boolean
+resize() void
+display() void
}
class ChainNode {
-int key
-int value
-ChainNode next
+ChainNode(key: int, value: int)
}
class HashTableChaining {
-ChainNode[] table
-int capacity
-int size
+HashTableChaining(capacity: int)
+hash(key: int) int
+put(key: int, value: int) void
+get(key: int) Integer
+remove(key: int) void
+display() void
}
class MapEntry~K,V~ {
-K key
-V value
-MapEntry~K,V~ next
+MapEntry(key: K, value: V)
+getKey() K
+getValue() V
+setValue(value: V) void
}
class MyHashMap~K,V~ {
-MapEntry~K,V~[] table
-int capacity
-int size
+MyHashMap()
+MyHashMap(initialCapacity: int)
+hash(key: K) int
+put(key: K, value: V) void
+get(key: K) V
+remove(key: K) V
+containsKey(key: K) boolean
+containsValue(value: V) boolean
+keySet() List~K~
+values() List~V~
+size() int
+isEmpty() boolean
+clear() void
}
HashTable --> HashEntry : contains
HashTableChaining --> ChainNode : contains
MyHashMap --> MapEntry : contains
- Deterministic: Input yang sama selalu menghasilkan output yang sama
- Uniform Distribution: Menyebar data secara merata ke seluruh tabel
- Efficient: Cepat dihitung
- Minimize Collision: Meminimalkan dua key berbeda menghasilkan hash yang sama
Menggunakan operasi modulo untuk mendapatkan index.
int hash(int key, int tableSize) {
return key % tableSize;
}Tips: Pilih tableSize yang merupakan bilangan prima untuk distribusi lebih baik.
Mengalikan key dengan konstanta A (0 < A < 1), mengambil bagian desimal, lalu kalikan dengan ukuran tabel.
int hash(int key, int tableSize) {
double A = 0.6180339887; // (sqrt(5) - 1) / 2 (Golden ratio)
double temp = key * A;
temp = temp - Math.floor(temp); // Ambil bagian desimal
return (int) Math.floor(tableSize * temp);
}Untuk key berupa string, gunakan teknik polynomial rolling hash.
int hashString(String key, int tableSize) {
int hash = 0;
int prime = 31; // Bilangan prima
for (int i = 0; i < key.length(); i++) {
hash = (hash * prime + key.charAt(i)) % tableSize;
}
return Math.abs(hash);
}Java menyediakan method bawaan hashCode() untuk setiap object.
String name = "John";
int hashCode = name.hashCode(); // Returns integer hash code
int index = Math.abs(hashCode % tableSize);Hash Table adalah struktur data yang menggunakan array dan hash function untuk menyimpan pasangan key-value. Data diakses berdasarkan key, bukan index numerik.
Key "apple" --hash--> index 2 --> Hash Table[2] = "apple"
Key "banana" --hash--> index 5 --> Hash Table[5] = "banana"
// File: HashEntry.java
class HashEntry {
int key;
int value;
boolean isDeleted;
public HashEntry(int key, int value) {
this.key = key;
this.value = value;
this.isDeleted = false;
}
}
// File: HashTable.java
class HashTable {
private HashEntry[] table;
private int capacity;
private int size;
private static final double MAX_LOAD_FACTOR = 0.75;
public HashTable(int capacity) {
this.capacity = capacity;
this.table = new HashEntry[capacity];
this.size = 0;
}
// Hash function
private int hash(int key) {
return Math.abs(key % capacity);
}
// Second hash for double hashing
private int hash2(int key) {
return 7 - (key % 7); // 7 adalah bilangan prima < capacity
}
// Get load factor
public double getLoadFactor() {
return (double) size / capacity;
}
// Resize table when load factor exceeds threshold
private void resize() {
System.out.println("\n[Resizing table from " + capacity + " to " + (capacity * 2) + "]");
HashEntry[] oldTable = table;
int oldCapacity = capacity;
capacity *= 2;
table = new HashEntry[capacity];
size = 0;
for (int i = 0; i < oldCapacity; i++) {
if (oldTable[i] != null && !oldTable[i].isDeleted) {
put(oldTable[i].key, oldTable[i].value);
}
}
}
// Insert key-value pair (Linear Probing)
public void put(int key, int value) {
if (getLoadFactor() >= MAX_LOAD_FACTOR) {
resize();
}
int index = hash(key);
int originalIndex = index;
int i = 1;
while (table[index] != null && !table[index].isDeleted) {
if (table[index].key == key) {
// Update existing key
table[index].value = value;
System.out.println("Update key " + key + " dengan value " + value + " di index " + index);
return;
}
// Linear probing
index = (originalIndex + i) % capacity;
i++;
if (index == originalIndex) {
System.out.println("Hash Table penuh!");
return;
}
}
table[index] = new HashEntry(key, value);
size++;
System.out.println("Insert (" + key + ", " + value + ") di index " + index);
}
// Get value by key
public Integer get(int key) {
int index = hash(key);
int originalIndex = index;
int i = 1;
while (table[index] != null) {
if (!table[index].isDeleted && table[index].key == key) {
return table[index].value;
}
index = (originalIndex + i) % capacity;
i++;
if (index == originalIndex) break;
}
return null; // Not found
}
// Remove key
public void remove(int key) {
int index = hash(key);
int originalIndex = index;
int i = 1;
while (table[index] != null) {
if (!table[index].isDeleted && table[index].key == key) {
table[index].isDeleted = true; // Tombstone
size--;
System.out.println("Remove key " + key + " dari index " + index);
return;
}
index = (originalIndex + i) % capacity;
i++;
if (index == originalIndex) break;
}
System.out.println("Key " + key + " tidak ditemukan!");
}
// Check if key exists
public boolean containsKey(int key) {
return get(key) != null;
}
// Get size
public int getSize() {
return size;
}
// Check if empty
public boolean isEmpty() {
return size == 0;
}
// Clear table
public void clear() {
table = new HashEntry[capacity];
size = 0;
System.out.println("Hash Table dikosongkan");
}
// Display table
public void display() {
System.out.println("\n=== HASH TABLE ===");
System.out.println("Index | Key | Value | Status");
System.out.println("------+---------+---------+----------");
for (int i = 0; i < capacity; i++) {
String key = "-";
String value = "-";
String status = "Empty";
if (table[i] != null) {
if (table[i].isDeleted) {
status = "Deleted";
} else {
key = String.valueOf(table[i].key);
value = String.valueOf(table[i].value);
status = "Occupied";
}
}
System.out.printf(" %2d | %5s | %5s | %s%n", i, key, value, status);
}
System.out.println("Size: " + size + "/" + capacity);
System.out.printf("Load Factor: %.2f%n", getLoadFactor());
}
}
// File: HashTableDemo.java
public class HashTableDemo {
public static void main(String[] args) {
System.out.println("=== HASH TABLE DENGAN OOP ===\n");
HashTable ht = new HashTable(7);
// Test insert
ht.put(10, 100);
ht.put(20, 200);
ht.put(30, 300);
ht.put(17, 170); // 17 % 7 = 3 (collision with 10 % 7 = 3)
ht.put(24, 240); // 24 % 7 = 3 (collision!)
ht.display();
// Test get
System.out.println("\n=== GET ===");
System.out.println("get(17) = " + ht.get(17));
System.out.println("get(99) = " + ht.get(99));
// Test contains
System.out.println("\n=== CONTAINS ===");
System.out.println("containsKey(20) = " + ht.containsKey(20));
System.out.println("containsKey(99) = " + ht.containsKey(99));
// Test update
System.out.println("\n=== UPDATE ===");
ht.put(17, 1700); // Update existing key
System.out.println("get(17) setelah update = " + ht.get(17));
// Test remove
System.out.println("\n=== REMOVE ===");
ht.remove(20);
ht.display();
// Test resize (add more elements to trigger resize)
System.out.println("\n=== TRIGGER RESIZE ===");
ht.put(5, 50);
ht.put(12, 120);
ht.display();
}
}Output:
=== HASH TABLE DENGAN OOP ===
Insert (10, 100) di index 3
Insert (20, 200) di index 6
Insert (30, 300) di index 2
Insert (17, 170) di index 4
Insert (24, 240) di index 5
=== HASH TABLE ===
Index | Key | Value | Status
------+---------+---------+----------
0 | - | - | Empty
1 | - | - | Empty
2 | 30 | 300 | Occupied
3 | 10 | 100 | Occupied
4 | 17 | 170 | Occupied
5 | 24 | 240 | Occupied
6 | 20 | 200 | Occupied
Size: 5/7
Load Factor: 0.71
=== GET ===
get(17) = 170
get(99) = null
=== CONTAINS ===
containsKey(20) = true
containsKey(99) = false
=== UPDATE ===
Update key 17 dengan value 1700 di index 4
get(17) setelah update = 1700
=== REMOVE ===
Remove key 20 dari index 6
=== HASH TABLE ===
Index | Key | Value | Status
------+---------+---------+----------
0 | - | - | Empty
1 | - | - | Empty
2 | 30 | 300 | Occupied
3 | 10 | 100 | Occupied
4 | 17 | 1700 | Occupied
5 | 24 | 240 | Occupied
6 | - | - | Deleted
Size: 4/7
Load Factor: 0.57
=== TRIGGER RESIZE ===
[Resizing table from 7 to 14]
Insert (30, 300) di index 2
Insert (10, 100) di index 10
Insert (17, 1700) di index 3
Insert (24, 240) di index 10
Insert (5, 50) di index 5
Insert (12, 120) di index 12
=== HASH TABLE ===
Index | Key | Value | Status
------+---------+---------+----------
0 | - | - | Empty
1 | - | - | Empty
2 | 30 | 300 | Occupied
3 | 17 | 1700 | Occupied
4 | - | - | Empty
5 | 5 | 50 | Occupied
6 | - | - | Empty
7 | - | - | Empty
8 | - | - | Empty
9 | - | - | Empty
10 | 10 | 100 | Occupied
11 | 24 | 240 | Occupied
12 | 12 | 120 | Occupied
13 | - | - | Empty
Size: 6/14
Load Factor: 0.43
Collision terjadi ketika dua key berbeda menghasilkan hash value yang sama.
hash("apple") = 3 ─┐
├──> Collision! Kedua key ingin menempati index 3
hash("orange") = 3 ─┘
┌────────────────────────────────────────────────────────────────────┐
│ COLLISION HANDLING │
├──────────────────────────────┬─────────────────────────────────────┤
│ CHAINING │ OPEN ADDRESSING │
│ (Separate Chaining) │ │
├──────────────────────────────┼─────────────────────────────────────┤
│ │ │
│ [0] -> [A] -> [B] -> null │ [0] | A │
│ [1] -> null │ [1] | │
│ [2] -> [C] -> null │ [2] | B <- probe │
│ [3] -> null │ [3] | C <- probe │
│ │ │
│ Menggunakan Linked List │ Linear/Quadratic/Double Hashing │
│ di setiap bucket │ Mencari slot kosong berikutnya │
│ │ │
└──────────────────────────────┴─────────────────────────────────────┘
Setiap bucket dalam hash table adalah linked list. Ketika collision terjadi, elemen baru ditambahkan ke linked list di bucket tersebut.
// File: ChainNode.java
class ChainNode {
int key;
int value;
ChainNode next;
public ChainNode(int key, int value) {
this.key = key;
this.value = value;
this.next = null;
}
}
// File: HashTableChaining.java
class HashTableChaining {
private ChainNode[] table;
private int capacity;
private int size;
public HashTableChaining(int capacity) {
this.capacity = capacity;
this.table = new ChainNode[capacity];
this.size = 0;
}
private int hash(int key) {
return Math.abs(key % capacity);
}
// Insert
public void put(int key, int value) {
int index = hash(key);
// Check if key already exists
ChainNode current = table[index];
while (current != null) {
if (current.key == key) {
current.value = value; // Update
System.out.println("Update (" + key + ", " + value + ") di index " + index);
return;
}
current = current.next;
}
// Insert at beginning of chain
ChainNode newNode = new ChainNode(key, value);
newNode.next = table[index];
table[index] = newNode;
size++;
System.out.println("Insert (" + key + ", " + value + ") di index " + index);
}
// Get
public Integer get(int key) {
int index = hash(key);
ChainNode current = table[index];
while (current != null) {
if (current.key == key) {
return current.value;
}
current = current.next;
}
return null;
}
// Remove
public void remove(int key) {
int index = hash(key);
if (table[index] == null) {
System.out.println("Key " + key + " tidak ditemukan!");
return;
}
// If head node is to be deleted
if (table[index].key == key) {
table[index] = table[index].next;
size--;
System.out.println("Remove key " + key + " dari index " + index);
return;
}
// Search in chain
ChainNode current = table[index];
while (current.next != null) {
if (current.next.key == key) {
current.next = current.next.next;
size--;
System.out.println("Remove key " + key + " dari index " + index);
return;
}
current = current.next;
}
System.out.println("Key " + key + " tidak ditemukan!");
}
// Get chain length at index
public int getChainLength(int index) {
int length = 0;
ChainNode current = table[index];
while (current != null) {
length++;
current = current.next;
}
return length;
}
// Display
public void display() {
System.out.println("\n=== HASH TABLE (CHAINING) ===");
for (int i = 0; i < capacity; i++) {
System.out.print("[" + i + "] -> ");
ChainNode current = table[i];
if (current == null) {
System.out.println("null");
} else {
while (current != null) {
System.out.print("(" + current.key + "," + current.value + ")");
if (current.next != null) {
System.out.print(" -> ");
}
current = current.next;
}
System.out.println(" -> null");
}
}
System.out.println("Total Size: " + size);
}
public int getSize() {
return size;
}
}
// File: ChainingDemo.java
public class ChainingDemo {
public static void main(String[] args) {
System.out.println("=== COLLISION HANDLING: CHAINING ===\n");
HashTableChaining ht = new HashTableChaining(7);
// Insert elements that will cause collisions
ht.put(10, 100); // 10 % 7 = 3
ht.put(17, 170); // 17 % 7 = 3 (collision!)
ht.put(24, 240); // 24 % 7 = 3 (collision!)
ht.put(31, 310); // 31 % 7 = 3 (collision!)
ht.put(5, 50); // 5 % 7 = 5
ht.put(12, 120); // 12 % 7 = 5 (collision!)
ht.put(20, 200); // 20 % 7 = 6
ht.display();
// Test get
System.out.println("\n=== GET ===");
System.out.println("get(17) = " + ht.get(17));
System.out.println("get(24) = " + ht.get(24));
System.out.println("get(99) = " + ht.get(99));
// Test remove
System.out.println("\n=== REMOVE ===");
ht.remove(17); // Remove from middle of chain
ht.display();
// Show chain lengths
System.out.println("\n=== CHAIN LENGTHS ===");
for (int i = 0; i < 7; i++) {
System.out.println("Index " + i + ": " + ht.getChainLength(i) + " elements");
}
}
}Output:
=== COLLISION HANDLING: CHAINING ===
Insert (10, 100) di index 3
Insert (17, 170) di index 3
Insert (24, 240) di index 3
Insert (31, 310) di index 3
Insert (5, 50) di index 5
Insert (12, 120) di index 5
Insert (20, 200) di index 6
=== HASH TABLE (CHAINING) ===
[0] -> null
[1] -> null
[2] -> null
[3] -> (31,310) -> (24,240) -> (17,170) -> (10,100) -> null
[4] -> null
[5] -> (12,120) -> (5,50) -> null
[6] -> (20,200) -> null
Total Size: 7
=== GET ===
get(17) = 170
get(24) = 240
get(99) = null
=== REMOVE ===
Remove key 17 dari index 3
=== HASH TABLE (CHAINING) ===
[0] -> null
[1] -> null
[2] -> null
[3] -> (31,310) -> (24,240) -> (10,100) -> null
[4] -> null
[5] -> (12,120) -> (5,50) -> null
[6] -> (20,200) -> null
Total Size: 6
=== CHAIN LENGTHS ===
Index 0: 0 elements
Index 1: 0 elements
Index 2: 0 elements
Index 3: 3 elements
Index 4: 0 elements
Index 5: 2 elements
Index 6: 1 elements
Pada Open Addressing, semua elemen disimpan langsung dalam array. Ketika collision terjadi, algoritma mencari slot kosong berikutnya menggunakan teknik probing.
Jika slot h(k) penuh, coba h(k)+1, h(k)+2, dst.
index = (hash(key) + i) % capacity; // i = 0, 1, 2, 3, ...Masalah: Primary Clustering - elemen cenderung mengelompok.
Menggunakan fungsi kuadrat untuk probing.
index = (hash(key) + i*i) % capacity; // i = 0, 1, 2, 3, ...**Lebih baik dari linear probing, mengurangi clustering.
Menggunakan dua hash function.
index = (hash1(key) + i * hash2(key)) % capacity; // i = 0, 1, 2, 3, ...// File: OpenAddressingDemo.java
public class OpenAddressingDemo {
static final int CAPACITY = 11; // Bilangan prima
static int[] keys;
static int[] values;
static boolean[] occupied;
static boolean[] deleted;
static int size;
// Initialize
public static void init() {
keys = new int[CAPACITY];
values = new int[CAPACITY];
occupied = new boolean[CAPACITY];
deleted = new boolean[CAPACITY];
size = 0;
}
// Primary hash function
public static int hash1(int key) {
return Math.abs(key % CAPACITY);
}
// Secondary hash function for double hashing
public static int hash2(int key) {
return 7 - (Math.abs(key) % 7); // Never returns 0
}
// ==================== LINEAR PROBING ====================
public static void insertLinear(int key, int value) {
if (size >= CAPACITY) {
System.out.println("Table penuh!");
return;
}
int index = hash1(key);
int i = 0;
while (occupied[index] && !deleted[index]) {
if (keys[index] == key) {
values[index] = value;
System.out.println("[Linear] Update (" + key + ", " + value + ") di index " + index);
return;
}
i++;
index = (hash1(key) + i) % CAPACITY;
}
keys[index] = key;
values[index] = value;
occupied[index] = true;
deleted[index] = false;
size++;
System.out.println("[Linear] Insert (" + key + ", " + value + ") di index " + index + " (probes: " + i + ")");
}
// ==================== QUADRATIC PROBING ====================
public static void insertQuadratic(int key, int value) {
if (size >= CAPACITY) {
System.out.println("Table penuh!");
return;
}
int index = hash1(key);
int i = 0;
while (occupied[index] && !deleted[index]) {
if (keys[index] == key) {
values[index] = value;
System.out.println("[Quadratic] Update (" + key + ", " + value + ") di index " + index);
return;
}
i++;
index = (hash1(key) + i * i) % CAPACITY;
}
keys[index] = key;
values[index] = value;
occupied[index] = true;
deleted[index] = false;
size++;
System.out.println("[Quadratic] Insert (" + key + ", " + value + ") di index " + index + " (probes: " + i + ")");
}
// ==================== DOUBLE HASHING ====================
public static void insertDouble(int key, int value) {
if (size >= CAPACITY) {
System.out.println("Table penuh!");
return;
}
int index = hash1(key);
int step = hash2(key);
int i = 0;
while (occupied[index] && !deleted[index]) {
if (keys[index] == key) {
values[index] = value;
System.out.println("[Double] Update (" + key + ", " + value + ") di index " + index);
return;
}
i++;
index = (hash1(key) + i * step) % CAPACITY;
}
keys[index] = key;
values[index] = value;
occupied[index] = true;
deleted[index] = false;
size++;
System.out.println("[Double] Insert (" + key + ", " + value + ") di index " + index + " (probes: " + i + ")");
}
// Display
public static void display(String method) {
System.out.println("\n=== HASH TABLE (" + method + ") ===");
System.out.println("Index | Key | Value | Status");
System.out.println("------+-----+-------+--------");
for (int i = 0; i < CAPACITY; i++) {
String k = occupied[i] ? String.valueOf(keys[i]) : "-";
String v = occupied[i] ? String.valueOf(values[i]) : "-";
String status = deleted[i] ? "Deleted" : (occupied[i] ? "Occupied" : "Empty");
System.out.printf(" %2d | %3s | %5s | %s%n", i, k, v, status);
}
}
public static void main(String[] args) {
// Test semua data yang sama untuk setiap metode
int[][] data = {{22, 220}, {33, 330}, {44, 440}, {55, 550}, {11, 110}, {77, 770}};
// ==================== LINEAR PROBING ====================
System.out.println("==================== LINEAR PROBING ====================\n");
init();
for (int[] d : data) {
insertLinear(d[0], d[1]);
}
display("LINEAR");
// ==================== QUADRATIC PROBING ====================
System.out.println("\n==================== QUADRATIC PROBING ====================\n");
init();
for (int[] d : data) {
insertQuadratic(d[0], d[1]);
}
display("QUADRATIC");
// ==================== DOUBLE HASHING ====================
System.out.println("\n==================== DOUBLE HASHING ====================\n");
init();
for (int[] d : data) {
insertDouble(d[0], d[1]);
}
display("DOUBLE");
}
}Output:
==================== LINEAR PROBING ====================
[Linear] Insert (22, 220) di index 0 (probes: 0)
[Linear] Insert (33, 330) di index 0 (probes: 0)
[Linear] Insert (44, 440) di index 0 (probes: 0)
[Linear] Insert (55, 550) di index 0 (probes: 0)
[Linear] Insert (11, 110) di index 0 (probes: 0)
[Linear] Insert (77, 770) di index 0 (probes: 0)
=== HASH TABLE (LINEAR) ===
Index | Key | Value | Status
------+-----+-------+--------
0 | 22 | 220 | Occupied
1 | 33 | 330 | Occupied
2 | 44 | 440 | Occupied
3 | 55 | 550 | Occupied
4 | 11 | 110 | Occupied
5 | 77 | 770 | Occupied
6 | - | - | Empty
7 | - | - | Empty
8 | - | - | Empty
9 | - | - | Empty
10 | - | - | Empty
==================== QUADRATIC PROBING ====================
[Quadratic] Insert (22, 220) di index 0 (probes: 0)
[Quadratic] Insert (33, 330) di index 0 (probes: 0)
[Quadratic] Insert (44, 440) di index 0 (probes: 0)
[Quadratic] Insert (55, 550) di index 0 (probes: 0)
[Quadratic] Insert (11, 110) di index 0 (probes: 0)
[Quadratic] Insert (77, 770) di index 0 (probes: 0)
=== HASH TABLE (QUADRATIC) ===
Index | Key | Value | Status
------+-----+-------+--------
0 | 22 | 220 | Occupied
1 | 33 | 330 | Occupied
4 | 44 | 440 | Occupied
5 | 55 | 550 | Occupied
9 | 11 | 110 | Occupied
3 | 77 | 770 | Occupied
...
==================== DOUBLE HASHING ====================
[Double] Insert (22, 220) di index 0 (probes: 0)
[Double] Insert (33, 330) di index 0 (probes: 0)
[Double] Insert (44, 440) di index 0 (probes: 0)
...
Map adalah struktur data yang menyimpan pasangan key-value, dimana setiap key bersifat unik. Map menggunakan hashing untuk operasi yang efisien.
┌───────────────────────────────────────┐
│ MAP │
├─────────────┬─────────────────────────┤
│ KEY │ VALUE │
├─────────────┼─────────────────────────┤
│ "nama" │ "John" │
│ "umur" │ 25 │
│ "kota" │ "Jakarta" │
│ "email" │ "john@email.com" │
└─────────────┴─────────────────────────┘
import java.util.ArrayList;
import java.util.List;
class MapEntry<K, V> {
K key;
V value;
MapEntry<K, V> next; // For chaining
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
this.next = null;
}
}
class MyHashMap<K, V> {
private MapEntry<K, V>[] table;
private int capacity;
private int size;
private static final int DEFAULT_CAPACITY = 16;
private static final double LOAD_FACTOR = 0.75;
@SuppressWarnings("unchecked")
public MyHashMap() {
this.capacity = DEFAULT_CAPACITY;
this.table = new MapEntry[capacity];
this.size = 0;
}
@SuppressWarnings("unchecked")
public MyHashMap(int initialCapacity) {
this.capacity = initialCapacity;
this.table = new MapEntry[capacity];
this.size = 0;
}
private int hash(K key) {
return Math.abs(key.hashCode() % capacity);
}
@SuppressWarnings("unchecked")
private void resize() {
System.out.println("\n[Resizing from " + capacity + " to " + (capacity * 2) + "]");
MapEntry<K, V>[] oldTable = table;
int oldCapacity = capacity;
capacity *= 2;
table = new MapEntry[capacity];
size = 0;
for (int i = 0; i < oldCapacity; i++) {
MapEntry<K, V> entry = oldTable[i];
while (entry != null) {
put(entry.key, entry.value);
entry = entry.next;
}
}
}
// Put key-value pair
public void put(K key, V value) {
if ((double) size / capacity >= LOAD_FACTOR) {
resize();
}
int index = hash(key);
MapEntry<K, V> entry = table[index];
// Check if key exists
while (entry != null) {
if (entry.key.equals(key)) {
entry.value = value; // Update
System.out.println("Update: " + key + " = " + value);
return;
}
entry = entry.next;
}
// Insert at beginning of chain
MapEntry<K, V> newEntry = new MapEntry<>(key, value);
newEntry.next = table[index];
table[index] = newEntry;
size++;
System.out.println("Put: " + key + " = " + value);
}
// Get value by key
public V get(K key) {
int index = hash(key);
MapEntry<K, V> entry = table[index];
while (entry != null) {
if (entry.key.equals(key)) {
return entry.value;
}
entry = entry.next;
}
return null;
}
// Get value or default
public V getOrDefault(K key, V defaultValue) {
V value = get(key);
return value != null ? value : defaultValue;
}
// Remove by key
public V remove(K key) {
int index = hash(key);
MapEntry<K, V> entry = table[index];
MapEntry<K, V> prev = null;
while (entry != null) {
if (entry.key.equals(key)) {
if (prev == null) {
table[index] = entry.next;
} else {
prev.next = entry.next;
}
size--;
System.out.println("Remove: " + key);
return entry.value;
}
prev = entry;
entry = entry.next;
}
return null;
}
// Check if key exists
public boolean containsKey(K key) {
return get(key) != null;
}
// Check if value exists
public boolean containsValue(V value) {
for (int i = 0; i < capacity; i++) {
MapEntry<K, V> entry = table[i];
while (entry != null) {
if (entry.value.equals(value)) {
return true;
}
entry = entry.next;
}
}
return false;
}
// Get all keys
public List<K> keySet() {
List<K> keys = new ArrayList<>();
for (int i = 0; i < capacity; i++) {
MapEntry<K, V> entry = table[i];
while (entry != null) {
keys.add(entry.key);
entry = entry.next;
}
}
return keys;
}
// Get all values
public List<V> values() {
List<V> vals = new ArrayList<>();
for (int i = 0; i < capacity; i++) {
MapEntry<K, V> entry = table[i];
while (entry != null) {
vals.add(entry.value);
entry = entry.next;
}
}
return vals;
}
// Get size
public int size() {
return size;
}
// Check if empty
public boolean isEmpty() {
return size == 0;
}
// Clear map
@SuppressWarnings("unchecked")
public void clear() {
table = new MapEntry[capacity];
size = 0;
System.out.println("Map cleared");
}
// Display
public void display() {
System.out.println("\n=== MY HASH MAP ===");
System.out.println("{");
for (int i = 0; i < capacity; i++) {
MapEntry<K, V> entry = table[i];
while (entry != null) {
System.out.println(" " + entry.key + " : " + entry.value);
entry = entry.next;
}
}
System.out.println("}");
System.out.println("Size: " + size);
}
}
public class HashMapDemo {
public static void main(String[] args) {
System.out.println("=== MY HASH MAP DENGAN OOP ===\n");
MyHashMap<String, Object> map = new MyHashMap<>(4);
// Test put dengan berbagai tipe value
map.put("nama", "John Doe");
map.put("umur", 25);
map.put("gaji", 15000000.50);
map.put("aktif", true);
map.put("hobi", "Programming");
map.display();
// Test get
System.out.println("\n=== GET ===");
System.out.println("get(nama) = " + map.get("nama"));
System.out.println("get(umur) = " + map.get("umur"));
System.out.println("get(alamat) = " + map.get("alamat"));
// Test getOrDefault
System.out.println("\n=== GET OR DEFAULT ===");
System.out.println("getOrDefault(alamat, 'N/A') = " + map.getOrDefault("alamat", "N/A"));
// Test contains
System.out.println("\n=== CONTAINS ===");
System.out.println("containsKey(gaji) = " + map.containsKey("gaji"));
System.out.println("containsValue(25) = " + map.containsValue(25));
System.out.println("containsValue(100) = " + map.containsValue(100));
// Test keySet and values
System.out.println("\n=== KEYS & VALUES ===");
System.out.println("Keys: " + map.keySet());
System.out.println("Values: " + map.values());
// Test update
System.out.println("\n=== UPDATE ===");
map.put("umur", 26);
System.out.println("get(umur) setelah update = " + map.get("umur"));
// Test remove
System.out.println("\n=== REMOVE ===");
map.remove("hobi");
map.display();
// Test dengan Integer key
System.out.println("\n=== MAP DENGAN INTEGER KEY ===");
MyHashMap<Integer, String> intMap = new MyHashMap<>();
intMap.put(1, "Satu");
intMap.put(2, "Dua");
intMap.put(3, "Tiga");
intMap.display();
}
}Output:
=== MY HASH MAP DENGAN OOP ===
Put: nama = John Doe
Put: umur = 25
Put: gaji = 1.50000005E7
[Resizing from 4 to 8]
Put: umur = 25
Put: nama = John Doe
Put: gaji = 1.50000005E7
Put: aktif = true
Put: hobi = Programming
=== MY HASH MAP ===
{
umur : 25
aktif : true
nama : John Doe
hobi : Programming
gaji : 1.50000005E7
}
Size: 5
=== GET ===
get(nama) = John Doe
get(umur) = 25
get(alamat) = null
=== GET OR DEFAULT ===
getOrDefault(alamat, 'N/A') = N/A
=== CONTAINS ===
containsKey(gaji) = true
containsValue(25) = true
containsValue(100) = false
=== KEYS & VALUES ===
Keys: [umur, aktif, nama, hobi, gaji]
Values: [25, true, John Doe, Programming, 1.50000005E7]
=== UPDATE ===
Update: umur = 26
get(umur) setelah update = 26
=== REMOVE ===
Remove: hobi
=== MY HASH MAP ===
{
umur : 26
aktif : true
nama : John Doe
gaji : 1.50000005E7
}
Size: 4
=== MAP DENGAN INTEGER KEY ===
Put: 1 = Satu
Put: 2 = Dua
Put: 3 = Tiga
=== MY HASH MAP ===
{
1 : Satu
2 : Dua
3 : Tiga
}
Size: 3
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
class Entry<K, V> {
final K key;
V value;
Entry<K, V> next;
final int hash;
public Entry(K key, V value, int hash) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = null;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setValue(V value) { this.value = value; }
@Override
public String toString() {
return key + "=" + value;
}
}
class GenericHashMap<K, V> implements Iterable<Entry<K, V>> {
private Entry<K, V>[] buckets;
private int capacity;
private int size;
private final double loadFactor;
private static final int DEFAULT_CAPACITY = 16;
private static final double DEFAULT_LOAD_FACTOR = 0.75;
@SuppressWarnings("unchecked")
public GenericHashMap() {
this.capacity = DEFAULT_CAPACITY;
this.loadFactor = DEFAULT_LOAD_FACTOR;
this.buckets = new Entry[capacity];
this.size = 0;
}
@SuppressWarnings("unchecked")
public GenericHashMap(int initialCapacity, double loadFactor) {
this.capacity = initialCapacity;
this.loadFactor = loadFactor;
this.buckets = new Entry[capacity];
this.size = 0;
}
// Compute hash
private int hash(K key) {
if (key == null) return 0;
int h = key.hashCode();
// Spread bits untuk distribusi lebih baik
return (h ^ (h >>> 16)) & (capacity - 1);
}
// Resize when load factor exceeded
@SuppressWarnings("unchecked")
private void resize() {
Entry<K, V>[] oldBuckets = buckets;
int oldCapacity = capacity;
capacity *= 2;
buckets = new Entry[capacity];
size = 0;
for (int i = 0; i < oldCapacity; i++) {
Entry<K, V> entry = oldBuckets[i];
while (entry != null) {
put(entry.key, entry.value);
entry = entry.next;
}
}
}
// PUT
public V put(K key, V value) {
if ((double) size / capacity >= loadFactor) {
resize();
}
int hash = hash(key);
int index = hash & (capacity - 1);
Entry<K, V> entry = buckets[index];
while (entry != null) {
if (entry.hash == hash &&
(entry.key == key || (key != null && key.equals(entry.key)))) {
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
entry = entry.next;
}
Entry<K, V> newEntry = new Entry<>(key, value, hash);
newEntry.next = buckets[index];
buckets[index] = newEntry;
size++;
return null;
}
// GET
public V get(K key) {
int hash = hash(key);
int index = hash & (capacity - 1);
Entry<K, V> entry = buckets[index];
while (entry != null) {
if (entry.hash == hash &&
(entry.key == key || (key != null && key.equals(entry.key)))) {
return entry.value;
}
entry = entry.next;
}
return null;
}
// REMOVE
public V remove(K key) {
int hash = hash(key);
int index = hash & (capacity - 1);
Entry<K, V> entry = buckets[index];
Entry<K, V> prev = null;
while (entry != null) {
if (entry.hash == hash &&
(entry.key == key || (key != null && key.equals(entry.key)))) {
if (prev == null) {
buckets[index] = entry.next;
} else {
prev.next = entry.next;
}
size--;
return entry.value;
}
prev = entry;
entry = entry.next;
}
return null;
}
// Contains Key
public boolean containsKey(K key) {
return get(key) != null;
}
// Contains Value
public boolean containsValue(V value) {
for (int i = 0; i < capacity; i++) {
Entry<K, V> entry = buckets[i];
while (entry != null) {
if (entry.value == value ||
(value != null && value.equals(entry.value))) {
return true;
}
entry = entry.next;
}
}
return false;
}
// Get all entries
public List<Entry<K, V>> entrySet() {
List<Entry<K, V>> entries = new ArrayList<>();
for (int i = 0; i < capacity; i++) {
Entry<K, V> entry = buckets[i];
while (entry != null) {
entries.add(entry);
entry = entry.next;
}
}
return entries;
}
// Get all keys
public List<K> keySet() {
List<K> keys = new ArrayList<>();
for (Entry<K, V> entry : entrySet()) {
keys.add(entry.key);
}
return keys;
}
// Get all values
public List<V> values() {
List<V> vals = new ArrayList<>();
for (Entry<K, V> entry : entrySet()) {
vals.add(entry.value);
}
return vals;
}
// Size
public int size() { return size; }
// Is Empty
public boolean isEmpty() { return size == 0; }
// Clear
@SuppressWarnings("unchecked")
public void clear() {
buckets = new Entry[capacity];
size = 0;
}
// Iterator implementation
@Override
public Iterator<Entry<K, V>> iterator() {
return entrySet().iterator();
}
// Compute if absent
public V computeIfAbsent(K key, java.util.function.Function<K, V> mappingFunction) {
V value = get(key);
if (value == null) {
value = mappingFunction.apply(key);
put(key, value);
}
return value;
}
// Merge
public V merge(K key, V value, java.util.function.BiFunction<V, V, V> remappingFunction) {
V oldValue = get(key);
V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
put(key, newValue);
return newValue;
}
// ForEach
public void forEach(java.util.function.BiConsumer<K, V> action) {
for (Entry<K, V> entry : entrySet()) {
action.accept(entry.key, entry.value);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Entry<K, V> entry : entrySet()) {
if (!first) sb.append(", ");
sb.append(entry.key).append("=").append(entry.value);
first = false;
}
sb.append("}");
return sb.toString();
}
}
// File: GenericHashMapDemo.java
public class GenericHashMapDemo {
public static void main(String[] args) {
System.out.println("=== GENERIC HASH MAP ===\n");
// Map<String, Integer> - Word frequency
GenericHashMap<String, Integer> wordCount = new GenericHashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
System.out.println("Word Frequency:");
System.out.println(wordCount);
// ForEach
System.out.println("\nIterating with forEach:");
wordCount.forEach((k, v) -> System.out.println(" " + k + " appears " + v + " times"));
// Map<Integer, String> - Student grades
System.out.println("\n=== STUDENT GRADES ===");
GenericHashMap<Integer, String> grades = new GenericHashMap<>();
grades.put(101, "A");
grades.put(102, "B");
grades.put(103, "A");
grades.put(104, "C");
System.out.println("Grades: " + grades);
System.out.println("Student 102 grade: " + grades.get(102));
// ComputeIfAbsent
System.out.println("\n=== COMPUTE IF ABSENT ===");
GenericHashMap<String, List<String>> categoryMap = new GenericHashMap<>();
categoryMap.computeIfAbsent("fruits", k -> new ArrayList<>()).add("apple");
categoryMap.computeIfAbsent("fruits", k -> new ArrayList<>()).add("banana");
categoryMap.computeIfAbsent("vegetables", k -> new ArrayList<>()).add("carrot");
System.out.println("Categories: " + categoryMap);
// Using enhanced for-loop
System.out.println("\n=== USING FOR-EACH LOOP ===");
for (Entry<String, Integer> entry : wordCount) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}Output:
=== GENERIC HASH MAP ===
Word Frequency:
{apple=3, banana=2, cherry=1}
Iterating with forEach:
apple appears 3 times
banana appears 2 times
cherry appears 1 times
=== STUDENT GRADES ===
Grades: {101=A, 102=B, 103=A, 104=C}
Student 102 grade: B
=== COMPUTE IF ABSENT ===
Categories: {fruits=[apple, banana], vegetables=[carrot]}
=== USING FOR-EACH LOOP ===
apple -> 3
banana -> 2
cherry -> 1
| Kriteria | Hash Table | Array | Linked List | BST |
|---|---|---|---|---|
| Search by key | O(1) avg | O(n) | O(n) | O(log n) |
| Insert | O(1) avg | O(n) | O(1) | O(log n) |
| Delete | O(1) avg | O(n) | O(n) | O(log n) |
| Ordered traversal | Tidak | Tidak | Tidak | Ya |
| Range query | Tidak | Ya (sorted) | Tidak | Ya |
| Memory overhead | Sedang | Rendah | Tinggi | Tinggi |
Gunakan Hash Table ketika:
- Perlu lookup/search cepat by key → O(1)
- Tidak perlu data terurut
- Key bersifat unik
- Operasi utama adalah insert, delete, search
Contoh penggunaan nyata:
| Aplikasi | Penggunaan Hash Table |
|---|---|
| Database indexing | Index kolom untuk query cepat |
| Caching | Store key-value pairs di memory |
| Symbol table | Compiler menyimpan variabel |
| Spell checker | Dictionary lookup |
| Count frequency | Word count, character frequency |
| Detect duplicates | Check apakah item sudah ada |
| Two Sum problem | Store complement untuk O(n) solution |
Gunakan HashSet ketika:
- Hanya perlu menyimpan keys (tanpa values)
- Perlu check membership cepat
- Perlu menghilangkan duplikat
- Tidak perlu data terurut
Contoh penggunaan:
// Cek duplikat dalam array
HashSet<Integer> seen = new HashSet<>();
for (int num : array) {
if (seen.contains(num)) {
System.out.println("Duplikat: " + num);
}
seen.add(num);
}Gunakan LinkedHashMap ketika:
- Perlu mempertahankan urutan insertion
- Implementasi LRU Cache
- Perlu iterasi sesuai urutan masuk
Gunakan TreeMap ketika:
- Perlu data terurut by key
- Perlu operasi range (subMap, headMap, tailMap)
- Perlu find min/max key
- Trade-off: O(log n) vs O(1)
| Kriteria | Chaining | Open Addressing |
|---|---|---|
| Implementasi | Lebih mudah | Lebih kompleks |
| Load factor tinggi | Lebih stabil | Degradasi performa |
| Memory | Extra untuk pointer | In-place |
| Cache performance | Buruk | Lebih baik |
| Deletion | Mudah | Perlu tombstone |
| Cocok untuk | Unknown load | Low load factor |
Rekomendasi:
- Load factor < 0.5 → Open Addressing (Linear/Quadratic Probing)
- Load factor > 0.7 → Chaining
- Banyak deletion → Chaining
- Memory terbatas → Open Addressing
| Kebutuhan | Rekomendasi | Alasan |
|---|---|---|
| Lookup by key O(1) | HashMap | Average O(1) untuk semua operasi |
| Data perlu terurut | TreeMap | Sorted by key |
| Urutan insertion penting | LinkedHashMap | Maintain insertion order |
| Hanya perlu keys | HashSet | Tanpa value overhead |
| Keys terurut + unique | TreeSet | Sorted Set |
| Frequency counting | HashMap<K, Integer> | Key → count |
| Cache dengan eviction | LinkedHashMap (LRU) | removeEldestEntry() |
| Bidirectional mapping | Dua HashMap | key→value dan value→key |
| Situasi | Alternatif | Alasan |
|---|---|---|
| Perlu data terurut | TreeMap/TreeSet | Hash tidak maintain order |
| Perlu range query | TreeMap | subMap(), headMap() |
| Perlu find min/max | TreeMap atau Heap | Hash tidak support |
| Key tidak bisa di-hash | TreeMap | Butuh Comparable |
| Memory sangat terbatas | Array | Hash punya overhead |
| Banyak collision expected | TreeMap | Worst case O(log n) vs O(n) |
| Tipe Key | Hash Function |
|---|---|
| Integer | key % tableSize (tableSize = prime) |
| String | Polynomial rolling hash |
| Object | Override hashCode() dan equals() |
| Multiple fields | Combine hash dari setiap field |
- Pilih initial capacity yang tepat - Hindari resize berulang
- Jaga load factor < 0.75 - Default Java HashMap
- Override hashCode() dan equals() - Untuk custom object sebagai key
- Gunakan immutable object sebagai key - Hindari hash berubah
- Pilih prime number sebagai table size - Distribusi lebih baik
| Operasi | Average Case | Worst Case (Banyak Collision) |
|---|---|---|
| Insert (put) | O(1) | O(n) |
| Search (get) | O(1) | O(n) |
| Delete (remove) | O(1) | O(n) |
| Contains | O(1) | O(n) |
Dengan hash function yang baik dan load factor yang terjaga:
- Data terdistribusi merata
- Jumlah collision minimal
- Akses langsung ke bucket menggunakan index
- Hash function buruk: Semua key menghasilkan hash yang sama
- Load factor tinggi: Terlalu banyak elemen relatif terhadap ukuran tabel
- Banyak collision: Rantai chain menjadi panjang
| Operasi | Array (unsorted) | Array (sorted) | Hash Table | BST (balanced) |
|---|---|---|---|---|
| Search | O(n) | O(log n) | O(1)* | O(log n) |
| Insert | O(1)** | O(n) | O(1)* | O(log n) |
| Delete | O(n) | O(n) | O(1)* | O(log n) |
*Average case **Di akhir array
- Pilih ukuran tabel yang prima untuk distribusi hash lebih baik
- Jaga load factor < 0.75 untuk performa optimal
- Resize tabel ketika load factor melebihi threshold
- Gunakan hash function yang baik untuk meminimalkan collision
- Pertimbangkan chaining vs open addressing sesuai kebutuhan
| Implementasi Manual | Java Built-in | Perbedaan Utama |
|---|---|---|
HashTable (Open Addressing) |
java.util.HashMap |
HashMap menggunakan chaining + tree |
HashTable (Chaining) |
java.util.HashMap |
Mirip, tapi HashMap lebih optimized |
MyHashMap<K,V> |
java.util.HashMap<K,V> |
HashMap punya resize, tree-ify |
| Custom HashSet | java.util.HashSet |
HashSet dibangun di atas HashMap |
| - | java.util.LinkedHashMap |
Menjaga insertion order |
| - | java.util.TreeMap |
Sorted by key (Red-Black Tree) |
| - | java.util.Hashtable |
Legacy, synchronized |
| - | java.util.ConcurrentHashMap |
Thread-safe, modern |
| Kebutuhan | Rekomendasi | Alasan |
|---|---|---|
| Key-value storage umum | HashMap |
O(1) average, paling efisien |
| Perlu iteration order sesuai insertion | LinkedHashMap |
Maintains insertion order |
| Perlu key terurut | TreeMap |
Sorted by natural order atau Comparator |
| Set unik tanpa value | HashSet |
Built on HashMap |
| Set unik terurut | TreeSet |
Built on TreeMap |
| Thread-safe | ConcurrentHashMap |
Modern concurrent implementation |
| Legacy code | Hashtable |
Avoid for new code |
import java.util.HashMap;
import java.util.Map;
public class JavaHashMapDemo {
public static void main(String[] args) {
// Membuat HashMap
Map<String, Integer> scores = new HashMap<>();
// PUT - menambah/update entry
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
System.out.println("Scores: " + scores);
// GET - mengambil value
System.out.println("Alice's score: " + scores.get("Alice"));
// CONTAINS - cek keberadaan
System.out.println("Contains Bob: " + scores.containsKey("Bob"));
System.out.println("Contains 95: " + scores.containsValue(95));
// UPDATE - put dengan key yang sama
scores.put("Bob", 90);
System.out.println("Bob's new score: " + scores.get("Bob"));
// REMOVE - menghapus entry
scores.remove("Charlie");
System.out.println("After remove Charlie: " + scores);
// GETORDEFAULT - get dengan default value
System.out.println("David's score: " + scores.getOrDefault("David", 0));
// PUTIFABSENT - put hanya jika key belum ada
scores.putIfAbsent("Alice", 100); // Tidak berubah
scores.putIfAbsent("Eve", 88); // Ditambahkan
System.out.println("After putIfAbsent: " + scores);
// Iterasi
System.out.println("\n=== Iterasi ===");
// 1. Iterasi key
System.out.print("Keys: ");
for (String key : scores.keySet()) {
System.out.print(key + " ");
}
System.out.println();
// 2. Iterasi value
System.out.print("Values: ");
for (Integer value : scores.values()) {
System.out.print(value + " ");
}
System.out.println();
// 3. Iterasi entry
System.out.println("Entries:");
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(" " + entry.getKey() + " -> " + entry.getValue());
}
// 4. forEach dengan lambda
System.out.println("With forEach:");
scores.forEach((k, v) -> System.out.println(" " + k + " = " + v));
}
}Output:
Scores: {Alice=95, Bob=87, Charlie=92}
Alice's score: 95
Contains Bob: true
Contains 95: true
Bob's new score: 90
After remove Charlie: {Alice=95, Bob=90}
David's score: 0
After putIfAbsent: {Alice=95, Eve=88, Bob=90}
=== Iterasi ===
Keys: Alice Eve Bob
Values: 95 88 90
Entries:
Alice -> 95
Eve -> 88
Bob -> 90
With forEach:
Alice = 95
Eve = 88
Bob = 90
import java.util.HashSet;
import java.util.Set;
public class JavaHashSetDemo {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
// ADD
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
fruits.add("Apple"); // Duplikat, tidak ditambahkan
System.out.println("Fruits: " + fruits);
System.out.println("Size: " + fruits.size()); // 3
// CONTAINS
System.out.println("Contains Banana: " + fruits.contains("Banana"));
// REMOVE
fruits.remove("Banana");
System.out.println("After remove: " + fruits);
// Set operations
Set<String> moreFruits = new HashSet<>();
moreFruits.add("Cherry");
moreFruits.add("Date");
moreFruits.add("Elderberry");
// Union
Set<String> union = new HashSet<>(fruits);
union.addAll(moreFruits);
System.out.println("Union: " + union);
// Intersection
Set<String> intersection = new HashSet<>(fruits);
intersection.retainAll(moreFruits);
System.out.println("Intersection: " + intersection);
// Difference
Set<String> difference = new HashSet<>(fruits);
difference.removeAll(moreFruits);
System.out.println("Difference: " + difference);
}
}Output:
Fruits: [Apple, Cherry, Banana]
Size: 3
Contains Banana: true
After remove: [Apple, Cherry]
Union: [Apple, Cherry, Elderberry, Date]
Intersection: [Cherry]
Difference: [Apple]
import java.util.*;
public class MapVariantsDemo {
public static void main(String[] args) {
// LinkedHashMap - menjaga insertion order
System.out.println("=== LinkedHashMap ===");
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("Zebra", 1);
linkedMap.put("Apple", 2);
linkedMap.put("Mango", 3);
System.out.println(linkedMap); // {Zebra=1, Apple=2, Mango=3}
// TreeMap - sorted by key
System.out.println("\n=== TreeMap ===");
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Zebra", 1);
treeMap.put("Apple", 2);
treeMap.put("Mango", 3);
System.out.println(treeMap); // {Apple=2, Mango=3, Zebra=1}
// TreeMap dengan custom comparator (descending)
System.out.println("\n=== TreeMap (Descending) ===");
Map<String, Integer> descendingMap = new TreeMap<>(Comparator.reverseOrder());
descendingMap.put("Zebra", 1);
descendingMap.put("Apple", 2);
descendingMap.put("Mango", 3);
System.out.println(descendingMap); // {Zebra=1, Mango=3, Apple=2}
// TreeMap navigation methods
TreeMap<Integer, String> navMap = new TreeMap<>();
navMap.put(10, "Ten");
navMap.put(20, "Twenty");
navMap.put(30, "Thirty");
navMap.put(40, "Forty");
System.out.println("\n=== TreeMap Navigation ===");
System.out.println("First key: " + navMap.firstKey()); // 10
System.out.println("Last key: " + navMap.lastKey()); // 40
System.out.println("Floor 25: " + navMap.floorKey(25)); // 20
System.out.println("Ceiling 25: " + navMap.ceilingKey(25)); // 30
System.out.println("Lower 30: " + navMap.lowerKey(30)); // 20
System.out.println("Higher 30: " + navMap.higherKey(30)); // 40
}
}Output:
=== LinkedHashMap ===
{Zebra=1, Apple=2, Mango=3}
=== TreeMap ===
{Apple=2, Mango=3, Zebra=1}
=== TreeMap (Descending) ===
{Zebra=1, Mango=3, Apple=2}
=== TreeMap Navigation ===
First key: 10
Last key: 40
Floor 25: 20
Ceiling 25: 30
Lower 30: 20
Higher 30: 40
| Method | HashMap | LinkedHashMap | TreeMap |
|---|---|---|---|
put(K, V) |
O(1) | O(1) | O(log n) |
get(K) |
O(1) | O(1) | O(log n) |
remove(K) |
O(1) | O(1) | O(log n) |
containsKey(K) |
O(1) | O(1) | O(log n) |
containsValue(V) |
O(n) | O(n) | O(n) |
keySet() |
Unordered | Insertion order | Sorted |
firstKey() |
N/A | N/A | O(log n) |
lastKey() |
N/A | N/A | O(log n) |
| Thread-safe | No | No | No |
import java.util.HashMap;
import java.util.Map;
public class ComputeMethodsDemo {
public static void main(String[] args) {
Map<String, Integer> wordCount = new HashMap<>();
// compute - selalu compute ulang
wordCount.compute("hello", (k, v) -> (v == null) ? 1 : v + 1);
wordCount.compute("hello", (k, v) -> (v == null) ? 1 : v + 1);
System.out.println("After compute: " + wordCount); // {hello=2}
// computeIfAbsent - hanya jika key tidak ada
wordCount.computeIfAbsent("world", k -> 1);
wordCount.computeIfAbsent("hello", k -> 100); // Tidak berubah
System.out.println("After computeIfAbsent: " + wordCount); // {hello=2, world=1}
// computeIfPresent - hanya jika key ada
wordCount.computeIfPresent("hello", (k, v) -> v * 10);
wordCount.computeIfPresent("foo", (k, v) -> v * 10); // Tidak ada efek
System.out.println("After computeIfPresent: " + wordCount); // {hello=20, world=1}
// merge - untuk menggabungkan value
wordCount.merge("hello", 5, Integer::sum);
wordCount.merge("new", 1, Integer::sum);
System.out.println("After merge: " + wordCount); // {hello=25, world=1, new=1}
}
}Diberikan array integer dan target sum, temukan dua elemen yang jumlahnya sama dengan target.
import java.util.HashMap;
import java.util.Map;
public class TwoSum {
public static int[] findTwoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
return new int[] { -1, -1 };
}
public static void main(String[] args) {
int[] nums = {2, 7, 11, 15};
int target = 9;
int[] result = findTwoSum(nums, target);
System.out.println("Two Sum Problem");
System.out.println("Array: [2, 7, 11, 15], Target: 9");
System.out.println("Result: indices [" + result[0] + ", " + result[1] + "]");
System.out.println("Values: " + nums[result[0]] + " + " + nums[result[1]] + " = " + target);
}
}Output:
Two Sum Problem
Array: [2, 7, 11, 15], Target: 9
Result: indices [0, 1]
Values: 2 + 7 = 9
Temukan karakter pertama yang tidak berulang dalam string.
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;
public class FirstNonRepeating {
public static char findFirstNonRepeating(String str) {
// LinkedHashMap untuk menjaga urutan insertion
Map<Character, Integer> charCount = new LinkedHashMap<>();
// Count frequency
for (char c : str.toCharArray()) {
charCount.put(c, charCount.getOrDefault(c, 0) + 1);
}
// Find first with count 1
for (Map.Entry<Character, Integer> entry : charCount.entrySet()) {
if (entry.getValue() == 1) {
return entry.getKey();
}
}
return '\0'; // Not found
}
public static void main(String[] args) {
String[] testCases = {
"leetcode",
"loveleetcode",
"aabb"
};
System.out.println("First Non-Repeating Character\n");
for (String s : testCases) {
char result = findFirstNonRepeating(s);
System.out.println("String: \"" + s + "\"");
if (result != '\0') {
System.out.println("First non-repeating: '" + result + "'");
} else {
System.out.println("No non-repeating character found");
}
System.out.println();
}
}
}Output:
First Non-Repeating Character
String: "leetcode"
First non-repeating: 'l'
String: "loveleetcode"
First non-repeating: 'v'
String: "aabb"
No non-repeating character found
Kelompokkan kata-kata yang merupakan anagram.
import java.util.*;
public class GroupAnagrams {
public static List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
// Sort characters sebagai key
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
// Gunakan computeIfAbsent
map.computeIfAbsent(key, k -> new ArrayList<>()).add(str);
}
return new ArrayList<>(map.values());
}
public static void main(String[] args) {
String[] words = {"eat", "tea", "tan", "ate", "nat", "bat"};
System.out.println("Group Anagrams\n");
System.out.println("Input: " + Arrays.toString(words));
List<List<String>> result = groupAnagrams(words);
System.out.println("\nGrouped Anagrams:");
for (List<String> group : result) {
System.out.println(" " + group);
}
}
}Output:
Group Anagrams
Input: [eat, tea, tan, ate, nat, bat]
Grouped Anagrams:
[eat, tea, ate]
[tan, nat]
[bat]
Implementasikan Least Recently Used (LRU) Cache menggunakan HashMap dan Double Linked List.
import java.util.HashMap;
import java.util.Map;
class LRUNode {
int key;
int value;
LRUNode prev;
LRUNode next;
public LRUNode(int key, int value) {
this.key = key;
this.value = value;
}
}
public class LRUCache {
private int capacity;
private Map<Integer, LRUNode> cache;
private LRUNode head; // Most recently used
private LRUNode tail; // Least recently used
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
// Dummy head and tail
head = new LRUNode(0, 0);
tail = new LRUNode(0, 0);
head.next = tail;
tail.prev = head;
}
// Move node to front (most recently used)
private void moveToFront(LRUNode node) {
removeNode(node);
addToFront(node);
}
// Add node right after head
private void addToFront(LRUNode node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
// Remove node from current position
private void removeNode(LRUNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// Get value
public int get(int key) {
if (!cache.containsKey(key)) {
System.out.println("GET " + key + " -> -1 (not found)");
return -1;
}
LRUNode node = cache.get(key);
moveToFront(node);
System.out.println("GET " + key + " -> " + node.value);
return node.value;
}
// Put key-value
public void put(int key, int value) {
if (cache.containsKey(key)) {
LRUNode node = cache.get(key);
node.value = value;
moveToFront(node);
System.out.println("PUT " + key + ":" + value + " (update)");
} else {
if (cache.size() >= capacity) {
// Remove LRU (node before tail)
LRUNode lru = tail.prev;
removeNode(lru);
cache.remove(lru.key);
System.out.println("PUT " + key + ":" + value + " (evict " + lru.key + ")");
} else {
System.out.println("PUT " + key + ":" + value);
}
LRUNode newNode = new LRUNode(key, value);
cache.put(key, newNode);
addToFront(newNode);
}
}
// Display cache state
public void display() {
System.out.print("Cache (MRU -> LRU): ");
LRUNode current = head.next;
while (current != tail) {
System.out.print("[" + current.key + ":" + current.value + "] ");
current = current.next;
}
System.out.println();
}
public static void main(String[] args) {
System.out.println("=== LRU CACHE ===\n");
LRUCache cache = new LRUCache(3);
cache.put(1, 100);
cache.put(2, 200);
cache.put(3, 300);
cache.display();
cache.get(1); // Access 1, makes it MRU
cache.display();
cache.put(4, 400); // Evicts 2 (LRU)
cache.display();
cache.get(2); // Should return -1
cache.get(3); // Access 3
cache.display();
cache.put(5, 500); // Evicts 1
cache.display();
}
}Output:
=== LRU CACHE ===
PUT 1:100
PUT 2:200
PUT 3:300
Cache (MRU -> LRU): [3:300] [2:200] [1:100]
GET 1 -> 100
Cache (MRU -> LRU): [1:100] [3:300] [2:200]
PUT 4:400 (evict 2)
Cache (MRU -> LRU): [4:400] [1:100] [3:300]
GET 2 -> -1 (not found)
GET 3 -> 300
Cache (MRU -> LRU): [3:300] [4:400] [1:100]
PUT 5:500 (evict 1)
Cache (MRU -> LRU): [5:500] [3:300] [4:400]
Temukan K elemen yang paling sering muncul.
import java.util.*;
public class TopKFrequent {
public static List<Integer> topKFrequent(int[] nums, int k) {
// Count frequency
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Use bucket sort
@SuppressWarnings("unchecked")
List<Integer>[] buckets = new List[nums.length + 1];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
int freq = entry.getValue();
buckets[freq].add(entry.getKey());
}
// Collect top k from highest frequency
List<Integer> result = new ArrayList<>();
for (int i = buckets.length - 1; i >= 0 && result.size() < k; i--) {
result.addAll(buckets[i]);
}
return result.subList(0, Math.min(k, result.size()));
}
public static void main(String[] args) {
int[] nums = {1, 1, 1, 2, 2, 3, 4, 4, 4, 4};
int k = 2;
System.out.println("Top K Frequent Elements\n");
System.out.println("Array: " + Arrays.toString(nums));
System.out.println("K: " + k);
List<Integer> result = topKFrequent(nums, k);
System.out.println("Top " + k + " frequent: " + result);
// Show frequency
System.out.println("\nFrequency breakdown:");
Map<Integer, Integer> freq = new HashMap<>();
for (int num : nums) {
freq.put(num, freq.getOrDefault(num, 0) + 1);
}
freq.forEach((key, value) ->
System.out.println(" " + key + " appears " + value + " times"));
}
}Output:
Top K Frequent Elements
Array: [1, 1, 1, 2, 2, 3, 4, 4, 4, 4]
K: 2
Top 2 frequent: [4, 1]
Frequency breakdown:
1 appears 3 times
2 appears 2 times
3 appears 1 times
4 appears 4 times
-
Implementasikan Spell Checker menggunakan HashMap untuk menyimpan dictionary dan memberikan saran kata yang mirip
-
Buat Phone Directory yang mendukung pencarian partial (autocomplete) menggunakan kombinasi HashMap dan Trie
-
Implementasikan Consistent Hashing untuk distributed system sederhana
-
Buat Cache dengan Time Expiry - entries otomatis expired setelah waktu tertentu
-
Implementasikan HashSet menggunakan HashMap (value selalu sama/null)
Selamat belajar!