Hash Tables — O(1) Lookup

Database, cache, compiler-এর মেরুদণ্ড

~45 min Advanced 18 practice problems Live code

1. The Dream: O(1) Access by Any Key

Array integer index-এ O(1); hash table যেকোনো key-এ (string, struct, etc.) O(1) গড় — key-কে একটি integer index-এ hash function মাধ্যমে রূপান্তর করে।

Python dict, Java HashMap, JavaScript Object — সবই hash table-এর উপর। ডেটাবেজ index, caching, symbol table, set membership — hash-এর প্রয়োগ অগণিত।

2. A Simple String Hash — djb2

djb2.c
#include <stdio.h>

unsigned long hash_djb2(const char *s) {
    unsigned long h = 5381;
    int c;
    while ((c = *s++)) h = ((h << 5) + h) + c;   // h * 33 + c
    return h;
}

int main(void) {
    const char *keys[] = { "apple", "banana", "mango", "apple" };
    for (int i = 0; i < 4; i++)
        printf("%-8s → %lu → bucket %lu\n",
               keys[i], hash_djb2(keys[i]), hash_djb2(keys[i]) % 16);
    return 0;
}

Identical key সবসময় identical hash দেবে। Modulo দিয়ে bucket index।

3. Collisions — Two Strategies

  • Separate chaining — প্রতিটি bucket একটি linked list।
  • Open addressing — collision-এ পরের slot দেখা (linear/quadratic probing, double hashing)।
Chaining সহজ এবং delete handle করা সহজ; Open addressing cache-friendly। ছোট table-এ open addressing দ্রুত; বড় load factor-এ chaining সুবিধাজনক।

4. Hash Map with Separate Chaining — Full Implementation

hashmap.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Entry {
    char *key;
    int   val;
    struct Entry *next;
} Entry;

#define CAP 16
static Entry *buckets[CAP];

static unsigned long h(const char *s) {
    unsigned long v = 5381; int c;
    while ((c = *s++)) v = ((v << 5) + v) + c;
    return v;
}

void map_put(const char *k, int v) {
    unsigned long i = h(k) % CAP;
    for (Entry *e = buckets[i]; e; e = e->next)
        if (strcmp(e->key, k) == 0) { e->val = v; return; }

    Entry *e = malloc(sizeof *e);
    e->key = strdup(k); e->val = v;
    e->next = buckets[i];
    buckets[i] = e;
}

int map_get(const char *k, int *out) {
    unsigned long i = h(k) % CAP;
    for (Entry *e = buckets[i]; e; e = e->next)
        if (strcmp(e->key, k) == 0) { *out = e->val; return 1; }
    return 0;
}

void map_free(void) {
    for (int i = 0; i < CAP; i++) {
        Entry *e = buckets[i];
        while (e) { Entry *t = e; e = e->next; free(t->key); free(t); }
        buckets[i] = NULL;
    }
}

int main(void) {
    map_put("apple",  10);
    map_put("banana", 5);
    map_put("mango",  20);
    map_put("apple",  42);     // update

    int v;
    if (map_get("apple", &v))   printf("apple  = %d\n", v);
    if (map_get("banana", &v))  printf("banana = %d\n", v);
    if (!map_get("cherry", &v)) puts("cherry: not found");

    map_free();
    return 0;
}

5. Load Factor & Rehashing

Load factor = n / capacity। 0.75-এর বেশি হলে capacity দ্বিগুণ করে সব entry নতুন table-এ আবার insert করুন (rehash)। Insert গড় O(1) থাকে — amortized।

6. Where Hash Tables Shine

  • Word frequency counters
  • Caches (LRU + map)
  • Compiler symbol tables
  • Database indexes
  • Set membership (just keys, no values)

7. Practice Problems

  1. Implement map_put, map_get, map_free (full chaining hash map).
    Full chaining hash map লিখুন।
    ✨ Show Answer

    Section 4-এর hashmap.c-ই সম্পূর্ণ উত্তর।

  2. Implement map_delete(key).
    map_delete যোগ করুন।
    ✨ Show Answer
    int map_delete(const char *k) {
        unsigned long i = h(k) % CAP;
        Entry **p = &buckets[i];
        while (*p) {
            if (strcmp((*p)->key, k) == 0) {
                Entry *t = *p; *p = t->next;
                free(t->key); free(t);
                return 1;
            }
            p = &(*p)->next;
        }
        return 0;
    }
  3. Count word frequencies in a file.
    ফাইলে শব্দের frequency গুনুন।
    ✨ Show Answer

    File থেকে word-by-word পড়ে map-এ increment — int v; if (map_get(w, &v)) map_put(w, v+1); else map_put(w, 1);

  4. First non-repeating character in a string (count map).
    String-এ প্রথম non-repeating character।
    ✨ Show Answer

    প্রথম pass-এ frequency বসান, দ্বিতীয় pass-এ প্রথম যে character-এর count 1 সেটাই উত্তর।

  5. Two-sum — find two indices summing to target in O(n) with hash set.
    Hash দিয়ে O(n)-এ two-sum।
    ✨ Show Answer

    Iterate করুন: প্রতিটি a[i]-র জন্য target - a[i] map-এ আছে কি না দেখুন; পেলেই return। না পেলে a[i] → i map-এ রাখুন।

  6. Group anagrams.
    Anagram একসাথে রাখুন।
    ✨ Show Answer

    প্রতিটি word-এর characters sort করে সেটিকে key হিসেবে map-এ group করুন। একই sorted key = anagram group।

  7. Longest substring without repeating characters.
    Longest substring without repeat।
    ✨ Show Answer

    Sliding window + map (char → last index)। Repeat এলে left-কে সেই index-এর পরে টানুন।

  8. Subarray with sum = K (negatives allowed).
    Subarray with sum K।
    ✨ Show Answer

    Prefix-sum count map। sum - k যতবার দেখা গেছে তত valid subarray। O(n)।

  9. LRU cache using hash map + doubly linked list.
    Hash + doubly list দিয়ে LRU।
    ✨ Show Answer

    Map = key → node pointer। List = MRU...LRU। Get: list-এ node সামনে আনুন। Put: evict লাগলে list-এর tail remove, map থেকে delete।

  10. Open addressing (linear probing) hash table.
    Linear probing hash table।
    ✨ Show Answer

    Array-এ slot state: EMPTY/FILLED/TOMBSTONE। Insert: empty বা tombstone না পাওয়া পর্যন্ত (i+1)%CAP। Delete: slot-কে TOMBSTONE করুন।

  11. Benchmark linear probing vs chaining.
    Linear probing vs chaining benchmark।
    ✨ Show Answer

    ছোট load factor-এ (≤ 0.5) open addressing cache-friendly বলে দ্রুত। Load factor ~0.75+ হলে chaining ভালো। Benchmark-এ 10⁶ key insert+lookup করুন।

  12. Why does a poor hash function destroy performance? Demonstrate.
    দুর্বল hash function performance-এ কী ক্ষতি করে?
    ✨ Show Answer

    সবাই একই bucket-এ পড়লে hash table একটি linked list-এ পরিণত হয় — lookup O(n)। দুর্বল hash-এর উদাহরণ: return key[0] — সব একই অক্ষরের শুরু-key একই bucket-এ।

  13. Resize (rehash) when load factor > 0.75.
    Load factor 0.75 ছাড়ালে rehash করুন।
    ✨ Show Answer

    নতুন capacity (দ্বিগুণ) দিয়ে নতুন buckets array; পুরনো প্রতিটি entry নতুন hash % new_cap-এ insert করুন; পুরনো free করুন।

  14. Why does modulo by a prime help weak hashes?
    Prime modulo দুর্বল hash-এ কেন সাহায্য করে?
    ✨ Show Answer

    Non-prime (বিশেষত 2-এর power) divisor-এ hash value-র নিম্ন bit গুলোই bucket নির্ধারণ করে — অনেক key একই bucket-এ পড়ে। Prime modulo সমস্ত bit-কে mix করে, ভাল distribution দেয়।

  15. Implement a simple Bloom filter.
    Simple Bloom filter বানান।
    ✨ Show Answer

    k-টি hash function + bit array। Insert: সব k-bit set। Test: সব k-bit 1 হলে probably present, কোনোটা 0 হলে definitely not। False positive থাকে, false negative নেই।

  16. Compute expected collisions for n keys in m buckets (birthday paradox).
    n key, m bucket-এ expected collision।
    ✨ Show Answer

    Expected collision-free probability ≈ e-n²/(2m)। n ≈ √m হলে ~50% collision — এজন্যই একটি 365-দিনের বছরে মাত্র 23 জন মানুষের মধ্যে একই birthday-র probability 50%।

  17. Implement a set (no values) using chaining.
    Value ছাড়া শুধু key-এর set।
    ✨ Show Answer

    Entry struct থেকে val ফেলে দিন। set_add, set_has, set_remove — map-এর মতোই।

  18. Compare: Python dict vs your C hash map — what's different?
    Python dict ও আপনার C hash map-এর পার্থক্য।
    ✨ Show Answer

    Python dict — open addressing (perturbation probe), insertion order preserved (3.7+), resizing at ~2/3, randomized hash (security)। C-তে আপনি এগুলো নিজে হাতে সাজান; trade-off বুঝে design করুন।

Glossary (শব্দকোষ)

TermMeaningবাংলায়
Hash TableKey-value structure with average O(1) access.গড়ে O(1) access-যুক্ত key-value structure।
Hash FunctionMaps a key to an integer index.Key-কে integer index-এ মাপ করে।
Bucket / SlotOne entry in the hash table array.Hash table array-এর একটি entry।
CollisionWhen two keys hash to the same bucket.দুটি key একই bucket-এ পড়ে যাওয়া।
ChainingEach bucket holds a linked list of colliding entries.প্রতি bucket-এ collision-যুক্ত entry-র list।
Open AddressingOn collision, probe other buckets in the array itself.Collision-এ অন্য bucket খুঁজে বসানো।
Linear ProbingTry buckets h+1, h+2... on collision.Collision-এ পরের পরের bucket চেষ্টা করা।
Quadratic ProbingProbe by h+1², h+2² to spread collisions.Collision ছড়াতে quadratic probe।
Double HashingProbe step computed by a second hash function.দ্বিতীয় hash দিয়ে probe step।
Load FactorRatio of entries to buckets — drives rehash.Entry/bucket ratio — rehash-এর সংকেত।
RehashingResizing the table and re-inserting all entries.Table বড় করে সব entry আবার বসানো।
TombstoneMarker for deleted slot in open addressing.Open addressing-এ delete-হওয়া slot-এর চিহ্ন।
Uniform HashingIdeal hash that distributes keys evenly.Key সমানভাবে ছড়ানো hash।

Summary — Module 28

ভালো hash + collision strategy = O(1) গড় access। সরল জন্য chaining, cache efficiency-র জন্য open addressing। Load factor বাড়লে rehash করুন। Hash function key-গুলো ভালোভাবে spread করা দরকার।

Next Module → Graphs — BFS ও DFS।