آموزش کامل پیاده‌سازی Map در ساختمان داده

آموزش کامل پیاده‌سازی Map در ساختمان داده

مفهوم هش کد

هش کد هر عنصر مختص به خودشه و فقط با عنصری که باهاش برابره یکسانه.

فرض کنید یک کتاب داریم و میخوایم موضوع به خصوصی از کتاب رو باز و مطالعه کنیم، برای پیدا کردن صفحه ی مربوط به موضوع مورد نظر میتونیم صفحات کتاب رو دونه دونه ورق بزنیم تا موضوع مورد نظر رو پیدا کنیم ، ولی این کار وقت گیره، برای پیدا کردن موضوع مورد نظر داخل کتاب بهترین کار مراجعه کردن به فهرست مطالب کتابه و از طریق فهرست مطالب کتاب میتونیم صفحه ی مربوط به موضوع مورد نظر رو پیدا کرده و بدون اینکه بخوایم صفحات کتاب رو دونه دونه ورق بزنیم مستقیم بریم سراغ صفحه ی مربوط به موضوع مورد نظر.

با استفاده از لیست، لیست پیوندی، صف، پشته و... برای پیدا کردن یک عنصر از تمام عناصر رو دونه دونه بررسی میکنیم تا به عنصر مورد نظر برسیم ولی تو هش کردن، هر عنصر یک آدرس منحصر به فرد داره که و با استفاده از آدرس عنصر، بدون اینکه برنامه وقتشو برای جستجوی تمام عناصر تلف کنه.

در زبان های برنامه نویسی هر عنصر یک کد ثابتی داره که بهش هش کد (hash code) میگیم، هش کد مثل اثر انگشت میمونه و هش کد (hash code) هر عنصر فقط با عنصر برابر با خودش یکسانه.

a := 24
b := 24
c := 52

(a = b) != c  ∴ (a.hashcode = b.hashcode) != c.hashcode
        

از هش کد ها مثل فهرست مطالب کتاب که گفته شد برای پیدا کردن جایگاه عنصر در map استفاده می کنیم.

بررسی map

در map عناصر به صورت جفت کلید (key) و مقدار (value) نگهداری میشن.

map یک پیاده‌سازی در ساختار داده است که عناصر رو به صورت جفت کلید (key) و مقدار (value) (یک عنصر به عنوان کلید و یک عنصر به عنوان مقدار) داخل خودش نگهداری میکنه؛ در map کلید ها نمیتونن تکراری باشن ولی مقادیر میتونن تکراری باشن، هر key و value به صورت جفت داخل یک آرایه نگهداری میشه، با هش کد (hash code) ها میتونیم یک map پیاده‌سازی کنیم اگه با هش کد (hash code) ها یک map رو پیاده‌سازی کنیم بهش HashMap میگیم، به map دیکشنری (Dictionary) هم میگن.

به جفت key و value در مپ entry میگیم و برای نگهداریشون یک کلاس تعریف میکنیم.

class Entry{
  K key
  V value

  function getKey(): K{
    return key
  }

  function getValue(): V{
    return value
  }

  function setValue(newValue: V): V{
    oldValue := value
    value := newValue
    return oldValue
  }

}
        

هر value کلید مختص به خودشو داره key ها در map نمیتونن تکراری باشن، برای پیدا کردن key و value مورد نظر از هش کد کلید استفاده میکنیم، در map زمانی که دو کلید یکی باشن و هش کد های یکسانی داشته باشن میگیم دو کلید باهم برابرن.

به آرایه ای که entry ها رو نگهداری می کنه buckets میگیم، bucket میتونه مستقیم entry ها رو نگهداری کنه یا یک آرایه باشه از linkedlist که هر linkedlist یک یا چند entry رو نگهداری می کنه.

برای پیدا کردن شماره خونه ی entry مورد نظر در map مانند زیر عمل می کنیم.

function hash(K key): int{
  
  int h = key.hashcode
  h = h ^ (h >>> 16)

  return h & (buckets.length - 1)
}
        

در بالا با h ^ (h >>> 16) مطمئن میشیم هنگام گرفتن index از هش کد ها به تکرار کمتر می خوریم و همینطور با & فقط bit های مشترک بین هش کد و bucktes.length - 1 رو نگهداری می کنیم که نتیجش میشه یک عدد بین 0 تا buckets.length - 1 میتونستیم برای این کار بجای & از % استفاده کنیم اما سرعت محاسبه ی & بیشتره.

رفع تداخل index ها (collision resolution)

دو کلید مختلف، هش کد های مختلفی دارن ولی گاهی هنگام محاسبه ی index با تابع hash ممکنه index های دو کلید یکسان در بیاد، به این وضعیت تداخل (collision) میگیم.

برای رفع تداخل index ها میتونیم از زنجیره سازی مجزا (separate chaining) یا جستجو کردن خطی (linear probing) در پیاده سازی map استفاده کنیم.

زنجیره سازی مجزا (separate chaining)

در این روش یک آرایه از linkedlist ایجاد می کنیم و دو entry با index یکسان رو به linkedlist همون index اضافه می کنیم.

entries := new LinkedList[capacity]
          
رفع تداخل index ها (collision resolution) به روش زنجیره سازی مجزا (separate chaining)
رفع تداخل index ها (collision resolution) به روش زنجیره سازی مجزا (separate chaining)

جستجو کردن خطی (linear probing)

در این روش هر خونه ای در آرایه فقط یک entry رو نگهداری میکنه، زمانی که بخوایم یک entry رو به آرایه اضافه کنیم اگه index توسط یک entry دیگه پر شده باشه شروع به جستجو از index بعدی میکنیم تا یک خونه ی خالی در آرایه برای اضافه کردن entry جدید پیدا کنیم؛

entries := new Entry[capacity]
          

در زیر به صورت تصویری عملکرد روش linear probing بیان شده است.

رفع تداخل index ها (collision resolution) به روش جستجوی خطی (linear probing)
رفع تداخل index ها (collision resolution) به روش جستجوی خطی (linear probing)

رفع تداخل index ها (collision resolution) در روش separate chaining بهینه تره، در این مطلب به پیاده‌سازی map با روش separate chaining می پردازیم.

افزایش ظرفیت map

هنگامی که آرایه ی داخل map به یک حدی برسه باید ظرفیت آرایه افزایش پیدا کنه تا بتونیم عناصر جدید رو به map اضافه کنیم.

class MyHashMap{

  loadFactor := 0.75
  capacity := 16
  size := 0

  buckets := new LinkedList[capacity]

  function resize(){
    oldBuckets := buckets

    mSize := 0
    mCapacity := mCapacity * 2

    buckets := new LinkedList[mSize]

    for 0 <= i < oldBuckets.length
      
      bucket := oldBuckets[i]

      if bucket != null
        for 0 <= j < bucket.size
          put(bucket.key, bucket.value)

  }
        

capacity طول آرایه ی تعریف شده در map رو نمایش می ده و size هم تعداد عناصر موجود در map رو نمایش میده.

با استفاده از loadFactor میتونیم آستانه ی افزایش ظرفیت آرایه رو مشخص کنیم، مقدار loadFactor باید بین 0 تا 1 باشه و آستانه ی ظرفیت برابر است با loadFactor * capacity زمانی که size مساوی یا بیشتر از این مقدار بشه capacity باید افزایش پیدا کنه.

صدا زدن مقدار با استفاده از کلید در map

با استفاده از key در map میتونیم یک value رو پیدا کنیم و value رو برگردونیم، پیچیدگی زمان برای پیدا کردن value برابر O(1) است.

function get(key: K): V{
  
  index := hash(key)

  bucket := buckets[index]

  if bucket != null
    for 0 <= i < bucket.size
      
      entry := bucket.get(i)
      
      if entry.key = key
        return entry.value

  return null
}
        

بررسی وجود کلید در map

اگه یک value وجود داشته باشه معلوم میشه کلید هم تو map وجود داره پیچیدگی زمان برای جستجوی key در map برابر صدا زدن value یعنی O(1) است.

function contains(key: K): boolean{
  
  return get(key) != null
}
        

ممکنه تو HashMap کلید وجود داشته باشه ولی مقدار برابر null باشه، کد بالا با وجود سادگی و خلاصه بودنش این وضعیت رو نمیتونه تشخیص بده بنابراین برای پیاده‌سازی کامل تر شبه کد زیر رو پیاده‌سازی می کنیم.

function contains(key: K): boolean {
  index := hash(key)
  bucket := buckets[index]

  if bucket != null
     for 0 <= i < bucket.size
         entry := bucket.get(i)
         if entry.key = key
            retrun true

  return false
}
        

اضافه کردن جفت کلید و مقدار جدید به map

با استفاده از تابع put میتونیم یک جفت key و value رو به map اضافه کنیم، اگه key و value از قبل وجود داشتن مقدار value رو بروزرسانی می کنیم. پیچیدگی زمان این کار O(1) است.

برای اضافه کردن جفت key و value ابتدا بررسی می کنیم آیا کلید از قبل وجود داره یا خیر اگه کلید وجود داشت فقط مقدار value رو بروزرسانی می کنیم در غیر اینصورت بررسی میکنیم آیا به ظرفیت آرایه به استانه ی افزایش ظرفیت رسیده یا خیر اگه رسیده باشه برنامه با تابع resize که تعریف کردیم ظرفیت آرایه رو دو برابر می کنه و سپس key و value رو به عنوان مقادیر جدید به آرایه اضافه می کنه.

function put(key: K, value: V): V{

  index := hash(key)

  if index < 0 
    return null

  if contains(key)
    for 0<= i < buckets[index].size
      entry := buckets[index].get(i)
      if entry.key = key
        return entry.setValue(value)
  else
  
    if size >= loadFactor * capacity
      resize()

    if buckets[index] = null
      buckets[index] := new LinkedList()


    buckets[index].add(new Entry(key, value))

    size++

}
        

حذف جفت کلید و مقدار از map

برای حذف جفت key و value از map با کلید جستجو می کنیم اگه کلید و مقدار وجود داشتن، حذفشون می کنیم؛ پیچیدگی زمان برای حدف کردن جفت کلید و مقدار O(1) است.

function remove(key: K): V{

  if contains(key)
    
    index := hash(key)

    bucket := buckets[index]

    for 0<= i < bucket.size
      if bucket.get(i).key = key
        size-- 
        return bucket.remove(i)

  return null
}
        

تبدیل map به آرایه برای جستجوی خطی بین عناصر

برای جستجوی خطی بین عناصر داخل map، میتونیم یک تابع به نام toArray تعریف کنیم و عناصر رو به صورت آرایه باهاش برگردونیم؛ پیچیدگی زمان برای این کار O(n) است.

function toArray(): Entry<K, V>[]{
  
  entries := (Entry<K, V>[]) new Entry[size]

  index := 0

  for 0<= i < buckets.length
     
     linkedList := buckets[i]
     
     if linkedList != null
        for 0<= j < linkedList.size
            entries[index++] := linkedList.get(j)

  return entries
}

        

پیاده‌سازی map در زبان های برنامه نویسی

در این قسمت میخوایم چیزایی که گفته شد رو به عنوان کلاس HashMap در زبان های برنامه نویسی جاوا، کاتلین، جاوااسکریپت و پایتون به طور عملی پیاده کنیم.

برای راحتی یک دسترسی سریع به پیاده‌سازی ها با زبان های مختلف رو در زیر قرار دادیم.

پیاده‌سازی map با زبان جاوا

در زیر یک map رو به صورت جنریک با جاوا پیاده میکنیم.

import java.util.LinkedList;

public class MyHashMap<K, V> {

    private static final int DEFAULT_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    private int capacity;
    private float loadFactor;

    private int size = 0;

    private LinkedList<Entry<K, V>>[] buckets;

    public MyHashMap(int capacity, float loadFactor) {
        if (loadFactor <= 0 || loadFactor > 1)
            throw new IllegalArgumentException("load factor should be greater than 0 and less than or equal 1");
        if (capacity <= 0) throw new IllegalArgumentException("Capacity can not be zero or less");
        this.loadFactor = loadFactor;
        this.capacity = capacity;

        buckets = (LinkedList<Entry<K, V>>[]) new LinkedList[capacity];
    }

    public MyHashMap(int capacity) {
        this(capacity, DEFAULT_LOAD_FACTOR);
    }

    public MyHashMap() {
        this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public int hash(K key) {
        if (key == null) return -1;

        int h = key.hashCode();
        h = h ^ (h >>> 16);

        return h & (capacity - 1);
    }

    public V get(K key) {

        int index = hash(key);
        LinkedList<Entry<K, V>> bucket = buckets[index];

        if (bucket != null) {
            for (Entry<K, V> entry : bucket)
                if (entry.getKey().equals(key))
                    return entry.getValue();
        }

        return null;
    }

    public boolean containsKey(K key) {
        if (containsKey(key)){

            int index = hash(key);
            LinkedList<Entry<K, V>> bucket = buckets[index];

            for (int i=0; i<bucket.size(); i++){
                if (bucket.get(i).getKey().equals(key)) {
                    size--;
                    return bucket.remove(i).getValue();
                }
            }

        }

        return null;
    }

    public V put(K key, V value) {

        int index = hash(key);

        if (index < 0) return null;

        if (size >= capacity * loadFactor) resize();

        LinkedList<Entry<K, V>> bucket = buckets[index];

        if (containsKey(key)) {

            for (Entry<K, V> entry : bucket) {
                if (entry.getKey().equals(key)) {
                    return entry.setValue(value);
                }
            }

        } else {

            if (bucket == null)  buckets[index] = new LinkedList<>();

            buckets[index].add(new Entry<>(key, value));

            size++;
        }

        return value;
    }

    public V remove(K key) {

        if (containsKey(key)){

            int index = hash(key);
            LinkedList<Entry<K, V>> bucket = buckets[index];

            for (int i=0; i<bucket.size(); i++){
                if (bucket.get(i).getKey().equals(key)){
                    size--;
                    return bucket.remove(i).getValue();
                }
            }

        }

        return null;
    }

    public Entry<K, V>[] toArray() {

        Entry<K, V>[] entries = (Entry<K, V>[]) new Entry[size];

        int index = 0;
        for (LinkedList<Entry<K, V>> list : buckets) {
            if (list != null) {
                for (Entry<K, V> entry : list) {
                    if (entry != null) {
                        entries[index++] = entry;
                    }
                }
            }
        }

        return entries;
    
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    private void resize() {
        LinkedList<Entry<K, V>>[] oldBuckets = buckets;
        size = 0;
        capacity *= 2;
        buckets = (LinkedList<Entry<K, V>>[]) new LinkedList[capacity];

        for (LinkedList<Entry<K, V>> bucket : oldBuckets) {
            if (bucket != null) {
                for (Entry<K, V> entry : bucket) {
                    put(entry.getKey(), entry.getValue());
                }
            }
        }
    }

    @Override
    public String toString() {


        StringBuilder sb = new StringBuilder();

        sb.append("[ ");

                for(LinkedList<Entry<K, V>> bucket : buckets){
                    if (bucket != null)
                        for (Entry<K, V> entry : bucket)
                            sb.append(entry).append(" ");
                }

                    sb.append(" ]");

        return sb.toString();
    }


    public static class Entry<K, V> {
        private final K key;
        private V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        @Override
        public String toString() {
            return "{" + getKey() + " : " + getValue() + "}";
        }
    }
}
        

پیاده‌سازی map با کاتلین

در زیر یک map رو به صورت جنریک با کاتلین پیاده‌سازی میکنیم.

class MyHashMap<K, V> (var capacity: Int = DEFAULT_CAPACITY,val loadFactor: Float = DEFAULT_LOAD_FACTOR) {

    var size: Int = 0
        private set

    private var buckets: Array<LinkedList<Entry<K?, V?>>?>

    init {
        require(!(loadFactor <= 0 || loadFactor > 1)) { "load factor should be greater than 0 and less than or equal 1" }
        require(capacity > 0) { "Capacity can not be zero or less" }

        buckets = arrayOfNulls(capacity)
    }

    fun hash(key: K?): Int {
        if (key == null) return -1

        var h = key.hashCode()
        h = h xor (h ushr  16)

        return h and (capacity - 1)
    }

    fun get(key: K?): V? {
        val index = hash(key)
        val bucket: LinkedList<Entry<K?, V?>>? = buckets[index]

        if (bucket != null) {
            for (entry in bucket) if (entry.key == key) return entry.value
        }

        return null
    }

    fun containsKey(key: K?): Boolean {
        val index = hash(key)
        val bucket: LinkedList<Entry<K?, V?>>? = buckets[index]

        if (bucket != null)
            for (entry in bucket) if (entry.key == key) return true

        return false
    }

    fun put(key: K?, value: V?): V? {
        val index = hash(key)

        if (index < 0) return null

        if (size >= capacity * loadFactor) resize()

        val bucket: LinkedList<Entry<K?, V?>>? = buckets[index]

        if (containsKey(key)) {
            for (entry in bucket!!) {
                if (entry.key == key) {
                    return entry.setValue(value)
                }
            }
        } else {

            if (bucket == null) buckets[index] = LinkedList<Entry<K?, V?>>()

            buckets[index]!!.add(Entry(key, value))

            size++
        }

        return value
    }

    fun remove(key: K?): V? {
        if (containsKey(key)) {
            val index = hash(key)
            val bucket: LinkedList<Entry<K?, V?>>? = buckets[index]

            for (i in bucket!!.indices) {
                if (bucket[i].key == key) return bucket.removeAt(i).value
            }

            size--
        }

        return null
    }

    fun toArray(): Array<Entry<K?, V?>?> {

        val entries: Array<Entry<K?, V?>?> = arrayOfNulls(size)

        var index = 0
        for (list in buckets) {
            if (list != null) {
                for (entry in list) {
                    entries[index++] = entry
                }
            }
        }

        return entries
    }

    private fun resize() {
        val oldBuckets = buckets
        size = 0
        capacity *= 2
        buckets = arrayOfNulls(capacity)

        for (bucket in oldBuckets) {
            if (bucket != null) {
                for (entry in bucket) {
                    put(entry.key, entry.value)
                }
            }
        }
    }

    override fun toString(): String {
        val sb = StringBuilder()

        sb.append("[ ")

        for (bucket in buckets) {
            if (bucket != null) for (entry in bucket) sb.append(entry).append(" ")
        }

        sb.append(" ]")

        return sb.toString()
    }

    data class Entry<K, V>(val key: K?, var value: V?) {
        fun setValue(value: V): V? {
            val oldValue = this.value
            this.value = value
            return oldValue
        }

        override fun toString(): String {
            return "{" + this.key + " : " + this.value + "}"
        }
    }

    companion object {
        private const val DEFAULT_CAPACITY = 16
        private const val DEFAULT_LOAD_FACTOR = 0.75f
    }
}

        

پیاده‌سازی map با جاوااسکریپت

جاوااسکریپت یک زبان type-free است یعنی نوع متغیر هنگام مقداردهی متغیر مشخص میشه، در زیر یک map رو با جاوااسکریپت پیاده کرده ایم.

در جاوااسکریپت تابع هش کد (HashCode) وجود نداره بنابراین در مثال زیر برای جاوااسکریپت هش کد رو داخل تابع hash خودمون ایجاد می کنیم.

class MyHashMap {
    static DEFAULT_CAPACITY = 16;
    static DEFAULT_LOAD_FACTOR = 0.75;

    // Nested Entry Class
    static Entry = class {
        constructor(key, value) {
            this.key = key;
            this.value = value;
        }

        getKey() {
            return this.key;
        }

        getValue() {
            return this.value;
        }

        setValue(value) {
            const oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        toString() {
            return "{" + this.getKey() + " : " + this.getValue() + "}";
        }
    };

    constructor(capacity = MyHashMap.DEFAULT_CAPACITY, loadFactor = MyHashMap.DEFAULT_LOAD_FACTOR) {
        if (loadFactor <= 0 || loadFactor > 1)
            throw new Error("load factor should be greater than 0 and less than or equal 1");
        if (capacity <= 0) throw new Error("Capacity can not be zero or less");
        
        this.loadFactor = loadFactor;
        this.capacity = capacity;
        this.size = 0;

        this.buckets = new Array(capacity).fill(null);
    }

    hash(key) {
        if (key === null || key === undefined) return -1;

        // Custom simple hash code generator to mimic Java's .hashCode()
        let h = 0;
        const s = String(key);
        for (let i = 0; i < s.length; i++) {
            h = (31 * h + s.charCodeAt(i)) | 0;
        }
        
        h = h ^ (h >>> 16);
        return h & (this.capacity - 1);
    }

    get(key) {
        const index = this.hash(key);
        const bucket = this.buckets[index];

        if (bucket !== null) {
            // Using getSize() from our custom MyLinkedList implementation
            for (let i = 0; i < bucket.getSize(); i++) {
                const entry = bucket.get(i);
                if (entry.getKey() === key)
                    return entry.getValue();
            }
        }

        return null;
    }

    containsKey(key) {
            const index = this.hash(key);
            const bucket = this.buckets[index];

            for (let i = 0; i < bucket.getSize(); i++) {
                if (bucket.get(i).getKey() === key) {
                    return true;
                }
            }
        }
        return false;
    }

    put(key, value) {
        const index = this.hash(key);

        if (index < 0) return null;

        if (this.size >= this.capacity * this.loadFactor) this.resize();

        const bucket = this.buckets[index];

        if (this.containsKey(key)) {
            for (let i = 0; i < bucket.getSize(); i++) {
                const entry = bucket.get(i);
                if (entry.getKey() === key) {
                    return entry.setValue(value);
                }
            }
        } else {
            if (this.buckets[index] === null) this.buckets[index] = new MyLinkedList();

            this.buckets[index].addLast(new MyHashMap.Entry(key, value));
            this.size++;
        }

        return value;
    }

    remove(key) {
        if (this.containsKey(key)) {
            const index = this.hash(key);
            const bucket = this.buckets[index];

            for (let i = 0; i < bucket.getSize(); i++) {
                if (bucket.get(i).getKey() === key) {
                    this.size--;
                    return bucket.remove(i).getValue();
                }
            }
        }

        return null;
    }

    toArray() {
        const entries = new Array(this.size);
        let index = 0;
        
        for (let i = 0; i < this.buckets.length; i++) {
            const list = this.buckets[i];
            if (list !== null) {
                for (let j = 0; j < list.getSize(); j++) {
                    const entry = list.get(j);
                    if (entry !== null) {
                        entries[index++] = entry;
                    }
                }
            }
        }

        return entries;
    }

    getSize() {
        return this.size;
    }

    isEmpty() {
        return this.size === 0;
    }

    resize() {
        const oldBuckets = this.buckets;
        this.size = 0;
        this.capacity *= 2;
        this.buckets = new Array(this.capacity).fill(null);

        for (let i = 0; i < oldBuckets.length; i++) {
            const bucket = oldBuckets[i];
            if (bucket !== null) {
                for (let j = 0; j < bucket.getSize(); j++) {
                    const entry = bucket.get(j);
                    this.put(entry.getKey(), entry.getValue());
                }
            }
        }
    }

    toString() {
        let sb = "[ ";
        for (let i = 0; i < this.buckets.length; i++) {
            const bucket = this.buckets[i];
            if (bucket !== null) {
                for (let j = 0; j < bucket.getSize(); j++) {
                    const entry = bucket.get(j);
                    sb += entry.toString() + " ";
                }
            }
        }
        sb += " ]";
        return sb;
    }
}
        

پیاده‌سازی map با پایتون

پایتون یک زبان type-free است یعنی نوع متغیر هنگام مقداردهی متغیر مشخص میشه، در زیر یک map با پایتون پیاده کرده ایم.

import MyLinkedList

class MyHashMap:
    DEFAULT_CAPACITY = 16
    DEFAULT_LOAD_FACTOR = 0.75

    # Nested Entry Class
    class Entry:
        def __init__(self, key, value):
            self.key = key
            self.value = value

        def get_key(self):
            return self.key

        def get_value(self):
            return self.value

        def set_value(self, value):
            oldValue = self.value
            self.value = value
            return oldValue

        def __str__(self):
            return "{" + str(self.get_key()) + " : " + str(self.get_value()) + "}"

    def __init__(self, capacity=DEFAULT_CAPACITY, load_factor=DEFAULT_LOAD_FACTOR):
        if load_factor <= 0 or load_factor > 1:
            raise ValueError("load factor should be greater than 0 and less than or equal 1")
        if capacity <= 0: 
            raise ValueError("Capacity can not be zero or less")
        
        self.load_factor = load_factor
        self.capacity = capacity
        self._size = 0

        self.buckets = [None] * capacity

    def hash(self, key):
        if key is None: return -1

        h = hash(key)
        h = h ^ (h >> 16)

        return h & (self.capacity - 1)

    def get(self, key):
        index = self.hash(key)
        bucket = self.buckets[index]

        if bucket is not None:
            for i in range(bucket._size):
                entry = bucket.get(i)
                if entry.get_key() == key:
                    return entry.get_value()

        return None

    def contains_key(self, key):
      
        index = self.hash(key)
        bucket = self.buckets[index]

        for i in range(bucket._size):
            if bucket.get(i).get_key() == key:
                return True
        return False

    def put(self, key, value):
        index = self.hash(key)

        if index < 0: return None

        if self._size >= self.capacity * self.load_factor: 
            self.resize()

        bucket = self.buckets[index]

        if self.contains_key(key):
            for i in range(bucket._size):
                entry = bucket.get(i)
                if entry.getKey() == key:
                    return entry.set_value(value)
        else:
            if self.buckets[index] is None: 
                self.buckets[index] = MyLinkedList()

            self.buckets[index].add_last(self.Entry(key, value))
            self._size += 1

        return value

    def remove(self, key):
        if self.contains_key(key):
            index = self.hash(key)
            bucket = self.buckets[index]

            for i in range(bucket._size):
                if bucket.get(i).get_key() == key:
                    self._size -= 1
                    return bucket.remove(i).get_value()

        return None

    def to_array(self):
        entries = [None] * self._size
        index = 0
        
        for list_obj in self.buckets:
            if list_obj is not None:
                for j in range(list_obj._size):
                    entry = list_obj.get(j)
                    if entry is not None:
                        entries[index] = entry
                        index += 1

        return entries

    def is_empty(self):
        return self._size == 0

    def resize(self):
        old_buckets = self.buckets
        self._size = 0
        self.capacity *= 2
        self.buckets = [None] * self.capacity

        for bucket in old_buckets:
            if bucket is not None:
                for j in range(bucket._size):
                    entry = bucket.get(j)
                    self.put(entry.get_key(), entry.get_value())

    def __str__(self):
        sb = "[ "
        for bucket in self.buckets:
            if bucket is not None:
                for j in range(bucket._size):
                    entry = bucket.get(j)
                    sb += str(entry) + " "
        sb += " ]"
        return sb
        

استفاده از map در برنامه نویسی

میخوایم با استفاده از map که در بالا پیاده کردیم، نام هر رنگ با کد hexadecimal رنگ رو داخل یک مپ ذخیره کنیم.

MyHashMap<String, Integer> map = new MyHashMap();
map.put("Black", 0x000000);
map.put("White", 0xffffff);
map.put("Red", 0xff0000);
map.put("Green", 0x00ff00);
map.put("Blue", 0x0000ff);

System.out.println("hex code for color red to integer is :" + map.get("Red"));

System.out.println(map);

System.out.println("removing red from map the color in integer is:" + map.remove("Red"));

System.out.println("removing black from map the color value in integer is:" + map.remove("Black"));

System.out.println("Entries after removing red and black:");

System.out.println(map);
          
 val map: MyHashMap<String, Int> = MyHashMap()

map.put("Black", 0x000000)
map.put("White", 0xffffff)
map.put("Red", 0xff0000)
map.put("Green", 0x00ff00)
map.put("Blue", 0x0000ff)

println("hex code for color red to integer is : ${map.get("Red")}")

println(map)

println("removing red from map the color in integer is: ${map.remove("Red")}")
println("removing black from map the color value in integer is: ${map.remove("Black")}")

println("Entries after removing red and black:")
println(map)
          
let map = new MyHashMap();
map.put("Black", 0x000000);
map.put("White", 0xffffff);
map.put("Red", 0xff0000);
map.put("Green", 0x00ff00);
map.put("Blue", 0x0000ff);

console.log("hex code for color red to integer is :" + map.get("Red"));

console.log(map.toString());

console.log("removing red from map the color in integer is:" + map.remove("Red"));

console.log("removing black from map the color value in integer is:" + map.remove("Black"));

console.log("Entries after removing red and black:");

console.log(map.toString());
          
map = MyHashMap()
map.put("Black", 0x000000)
map.put("White", 0xffffff)
map.put("Red", 0xff0000)
map.put("Green", 0x00ff00)
map.put("Blue", 0x0000ff)

print("hex code for color red to integer is :" + str(map.get("Red")))

print(map)

print("removing red from map the color in integer is:" + str(map.remove("Red")))

print("removing black from map the color value in integer is:" + str(map.remove("Black")))

print("Entries after removing red and black:")

print(map)
          

خلاصه

  • هر عنصر هش کد مختص به خودشو داره و مقدارش با بقیه ی عناصر هم نوع خودش یکی نیست.
  • یک map نگهدارنده ی آرایه ای از جفت کلید و مقدار است.
  • به جفت کلید و مقدار entry میگیم و به آرایه ای که entry ها رو نگهداری میکنه bucket میگیم.
  • از هش کد کلید (key) برای پیدا کردن index یک entry در map استفاده می کنیم.
  • وقتی index دو کلید متفاوت با هم یکی شد میگیم دچار collision resolution شدیم.
  • میتونیم با روش separate chaining یا linear probing مساله ی collision resolution رو حل کنیم.
  • روش separate chaining بهینه تر از linear probing است.
  • پیچیدگی زمان صدا زدن، اضافه کردن و حذف entry در map برابر با O(1) است.

بازخورد و دیدگاه‌ ها

اولین نفری باشید که در این صفحه نظر می‌دهید

notifications برای اطلاع از جدیدترین مطالب و پست های مرتبط، عضو کانال تلگرام ما بشید.

arrow_drop_up
کپی شد!