Dictionaries and hash tables in JS

Preface

        In addition to sets, we can also use dictionaries and hash tables to store unique values.

        For collective learning please see:

Custom collections and ES6 collections http://t.csdn.cn/RcznA         In collections, we focus on each value itself. and make it the main element.

        Both dictionaries and hash tables store data in the form of [key:value].

        The difference is that each key in the dictionary can only have one value.

dictionary

        Dictionaries are very similar to collections. It stores a set of different elements. Collections store elements in the form [value:value]. Dictionaries store elements in the form of [key:value]. A dictionary is also called a map , symbol table, or associative array .

        In computer science, dictionaries are often used to store reference addresses of objects.

Dictionary

        ES6 provides an implementation of the Map class. That is the dictionary mentioned above.

        Now let’s talk about self-implementation:

export default class Dictionary {
  constructor() {
    this.table = {};
  }
}

        Similar to our custom Set class, we will store the elements in the dictionary in an instance of Object instead of an array.

        In JS, [] can get the properties of an object. Just pass in the property name as "position". So objects are also called associative arrays.

Planning key name

       In a dictionary, ideally use strings as keys. Values ​​can be of any type. So we need to make sure the key is of type string. So we need to define a type conversion method first. Make sure all keys are of type string.

export default class Dictionary {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }
}

          Unless the user customizes the conversion method. The general conversion method can be written like this:

function defaultToString(item) {
  if (item === null) {
    return 'NULL';
  } else if (item === undefined) {
    return 'UNDEFINED';
  } else if (typeof item === 'string' || item instanceof String) {
    return `${item}`;
  }
  return JSON.stringify(item);
}

Detect if key exists in dictionary

hasKey(key) {
    return Object.getOwnPropertyNames.call(this.toStrFn(key),this.table)
}

Set keys in dictionary

        First we need to figure out how to store key values. As mentioned above, it is the method of [key name: key value]. The key name is a string. What about key values?

        The string of the key name is converted. In order to save the information, we also need to save the original key. So we need to record the original key and value in the key Value. So we need to set up a ValuePair class. Used to store raw data.

class ValuePair {
    constructor(key, value) {
        this.key = key;
        this.value = value;
    }
}

        Writing Set method

set(key,value) {
    if (key != null && value !=null && !this.hasKey(key)) {
        const tableKey = this.toStrFn(key);
        this.table[tableKey ] = new ValuePair(key, value)
        return true
    }
    return false
}

Remove a key-value pair from a dictionary

  remove(key) {
    if (this.hasKey(key)) {
      delete this.table[this.toStrFn(key)];
      return true;
    }
    return false;
  }

Retrieve key-value pairs in a dictionary

  get(key) {
    const valuePair = this.table[this.toStrFn(key)];
    return valuePair == null ? undefined : valuePair.value;
  }

        or

get(key) {
    if (this.hasKey(key)) {
        return this.table[this.toStriFn(key)]
    }else {
        return undefined
    }
}

        But in the second method, we need to obtain the Key string twice and access the table object twice. The consumption for the first time is significantly smaller

Returns all original data in the dictionary (including key and value) as an array

keysAndValues() {
    return Object.values(this.table)
}

        This uses ES6 methods. We can also write it as a general method:

keysAndValues() {
    const values = [];
    for (const k in this.table) {
        if (this.hasKey(k)) {
            values.push(this.table[k])
        }
    }
    return values
}

        Why do we need to add hasKey for judgment?

        Because the for...in loop traverses every enumerable property of the object, including the enumerable properties on the prototype chain

Object.keys() only traverses its own enumerable properties, and cannot traverse enumerable and enumerable properties on the prototype chain. Object.getOwnPropertyNames() traverses itself, excluding all properties on the prototype chain (regardless of whether it is enumerable or not). lifted),

        From this we know that the for in loop may enumerate properties on the prototype chain. Another reason is that it may be a class extension through extend. Using for in may enumerate the field attributes on the parent class.

The keys method returns the original data key name 

  keys() {
    return this.keysAndValues().map(valuePair => valuePair.key);
  }

        If ES6 is not supported, you can use a for loop instead of the map method

The values ​​method returns the original data key value

values() {
    return this.keysAndValues().map(valuePair => valuePair.value)
}

forEach iteration of custom dictionary

        We need to create a method that iterates over each key value in this data structure. It also allows us to inject the callback function callbackFn and interrupt the iteration with the result returned by the callback function.

forEach(callbackFn) {
    const valuePairs = this.keyAndValues();
    for (let i = 0; i < valuePairs.length; i++) {
        const result =  callBackFn(valuePairs[i].key,valuePairs[i].value)
        if (result === false) {
            break;
        }
    }
}

size method

Returns the number of keys in the dictionary

size() {
    const valuePairs = this.keyAndValues()
    return valuePairs.length
}

clear method

clear() {
    this.table = {}
}

isEmpty method

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

toString method

 toString() {
    if (this.isEmpty()) {
      return '';
    }
    const valuePairs = this.keyValues();
    let objString = `${valuePairs[0].toString()}`;
    for (let i = 1; i < valuePairs.length; i++) {
      objString = `${objString},${valuePairs[i].toString()}`;
    }
    return objString;
  }

hash table

        HashTable class, also called HashMap class. It is a hash table implementation of the dictionary class.

effect

        In a dictionary, if you want to find the value you want (not the key, not the key and value, but the value) . Need to traverse the entire data structure to find it.

Hashing Algorithms and Hash Functions

        Commonly used algorithms in the narrow sense include sorting, recursion, dynamic programming, greedy algorithms, hashing algorithms, etc.

        Commonly used data structures include arrays, objects, stacks, queues, linked lists, collections (Set class), dictionaries (Map class), hash tables (and hash sets), binary trees, etc.

        The purpose of a hashing algorithm is to find a value in a data structure as quickly as possible

        The function of the hash function is to give a key value and then return the address of the value in the table.

        As we have mentioned before, the difference between arrays and linked lists is that the former can be retrieved very quickly, and a subscript can quickly locate the value. However, inserting and deleting items is not as strong as the latter, and the latter does not need to change an item like an array. item, the position of other items in the memory will also change accordingly, but the retrieval will be slower because the search must start from the head or tail.

        So if data structures such as arrays, objects, or sets are used with hashing algorithms (that is, hash functions), then high performance in both insertion and retrieval can be achieved.

Case:

        We need to maintain a data structure. This structure needs to store the key as the person's name and the value as the mailbox . The business requirement is to continuously add new items to it or delete items, and the frequency of retrieval is also very high.

        We use the most common hash function lose lose hash function to store the value.

        lose lose hash function: The method is to simply add the ASCII value of each character in each key value.

        The final key storage method is the result of adding the ASCII value of each character.

        Hash function code:

loseloseHashCode(key) {
    if (typeof key === 'number') {
      return key;
    }
    const tableKey = this.toStrFn(key);
    let hash = 0;
    for (let i = 0; i < tableKey.length; i++) {
      hash += tableKey.charCodeAt(i);
    }
    return hash % 37;
}
hashCode(key) {
    return this.loseloseHashCode(key);
}

        Why do we need to take the remainder here? In order to get a smaller hash value. We will use a hash and an arbitrary number as the remainder of division. This can avoid the risk that the operand exceeds the maximum representation range of the numerical variable.                

Case implementation:

        We represent our data structure based on an associative array (object).

class HashTable {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }
} 

The put method adds a new item to the hash table (this method updates the hash table)

put(key,value) {
    if (key !=null && value != null) {
        const position = this.hashCode(key)
        this.table[position] = new ValuePair(key,value);
        return true
    }
    return false
}

The get method gets a value from the hash table 

get(key) {
    const valuePair = this.table[this.hashCode(key)]
    return valuePair  == null ? undefined : valuePair.value
}

        HashTable and Dictionary classes are very similar. The difference is that in the dictionary class, we save the valuePair in the key attribute of the table (after it is converted to a string). In HashTable, we generate a number (key) from key and save the valuerPair in the value, thus forming a new hash table.

  remove(key) {
    const hash = this.hashCode(key);
    const valuePair = this.table[hash];
    if (valuePair != null) {
      delete this.table[hash];
      return true;
    }
    return false;
  }

We can also call a hash table a hash map

The above is a basic introduction to dictionaries and hash tables. If you need to know the advanced applications of hash tables, please read the next tweet "JavaScript Hash Tables and Extensions"

Guess you like

Origin blog.csdn.net/weixin_42274805/article/details/130948798