JS中的字典和散列表

前言

        除了集合,我们还可以用字典和散列表来存储唯一值。

        集合学习请见:

自定义集合和ES6集合http://t.csdn.cn/RcznA        在集合中,我们关注的是每个值本身。并将它作为主要元素。

        而字典和散列表都是以[键:值]的形式来存储数据。

        不同的是,字典的每一个键只能有一个值。

字典

        字典和集合很相似。其存储的都是一组互不相同的元素。集合以[值:值]的形式存储元素。而字典以[键:值]的形式存储元素。字典也称为映射符号表或关联数组

        在计算机科学中,字典常用来保存对象的引用地址。

字典类

        ES6提供了一个Map类的实现。即上文所说的字典。

        现在我们讲讲自行的实现:

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

        与我们自定义的Set类相似,我们都将在一个Object的实例而不是数组中存储字典中的元素。

        在JS中,[]可以获取对象的属性。将属性名作为“位置”传入即可。所以对象也被称为关联数组。

规划化键名

       在字典中,理想情况是用字符串作为键名。值可以是任何类型。所以我们需要确保key为字符串类型。于是我们需要先定义一个类型转换方法。确保所有key键都为字符串类型。

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

          除非用户自定义转化方法。一般的转化方法我们可以这么书写:

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);
}

检测键是否存在于字典中

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

在字典中设置键

        首先我们要搞清楚要以什么方式去储存键值。上面已经提过是[键名:键值]这种方式。键名为字符串。那么键值呢?

        键名的字符串是经过转换的。而为了保存信息的需要,我们同样要保存原始的key。于是我们需要在键值Value里面记录原始的key和value。于是我们需要设置一个ValuePair类。用于存储原始数据。

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

        书写Set方法

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(key) {
    if (this.hasKey(key)) {
      delete this.table[this.toStrFn(key)];
      return true;
    }
    return false;
  }

在字典中检索键值对

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

        或者

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

        但是第二种方法我们需要获取两次Key的字符串已经访问两次table对象。第一次的消耗明显更小

以数组的方式返回字典中所有的原始数据(包括key和value)

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

        这使用了ES6的方法。我们也可以写成通用的方法:

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

        为什么要加hasKey进行判断呢?

        因为for…in循环是 遍历对象的每一个可枚举属性,包括原型链上面的可枚举属性

Object.keys()只是遍历自身的可枚举属性,不可以遍历原型链上的可枚可枚举属性Object.getOwnPropertyNames()则是遍历自身不包括原型链上面的所有属性(不论是否是可枚举的),

        由此我们知道,for in循环可能枚举原型链上的属性。而且还有一个原因是可能是通过extend进行的类扩展,用for in可能枚举父类上的字段属性

keys方法返回原始数据键名 

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

        如果不支持ES6,可以用for循环替代map方法

values方法返回原始数据键值

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

自定义字典的forEach迭代

        我们需要创建一个能迭代这种数据结构中每个键值的方法。同时允许我们注入回调函数callbackFn并通过回调函数返回的结果中断迭代。

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方法

返回字典中键的个数

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

clear方法

clear() {
    this.table = {}
}

isEmpty方法

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

toString方法

 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;
  }

散列表

        HashTable类,也叫HashMap类。它是字典类的一种散列表实现方式。

作用

        在字典里,如果要找到你想要的值(不是键,也不是健和值,而是值)。需要遍历整个数据结构来找到它。

散列算法与散列函数

        狭义的算法常用的有排序、递归、动态规划、贪心算法、散列算法等等

        数据结构常用的有数组、对象、堆栈、队列、链表、集合(Set 类)、字典(Map 类)、散列表(以及散列集合)、二叉树等等。

        散列算法的作用是尽可能快地在数据结构中找到一个值

        散列函数的作用是给定一个健值,然后返回值在表中的地址。

        我们前面已经提到了,数组和链表的差别在于前者是检索很快,有一个下标就能快速定位到值,但是插入和删除项就没有后者强,而后者不需要像数组一样,改动一个项,其他的项在内存中的位置也会跟着变化,但是检索却更慢,因为要从头部或者尾部开始寻找。

        那么如果像数组、对象或者是集合这些数据结构,配合散列算法(就是散列函数)使用的话,那么可以达到插入和检索的性能都很高的效果。

案例:

        我们要维护一个数据结构,这个结构要存储以健为人名,以值为邮箱,业务需求是要不断往里面新增新的项,或者删除项,并且检索的频率也很高。

        我们用最常见的散列函数lose lose散列函数进行存值。

        lose lose散列函数:方法是简单地将每个健值中的每个字符的 ASCII 值相加。

        最终的 key 的存储方式就是每个字符的 ASCII 值相加的结果。

        散列函数代码:

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);
}

        这里为什么要取余数?为了得到比较小的hash值。我们会使用hash和一个任意数做除法的余数。这样可以规避操作数超过数值变量最大表示范围的风险                

案例实现:

        我们基于一个关联数组(对象)来表示我们的数据结构。

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

put方法 向散列表中增加一个新的项(此方法会更新散列表)

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

get方法 从散列表中获取一个值 

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

        HashTable和Dictionary类很相似。不同之处在于在字典类中,我们将valuePair保存在table的key属性中(在它呗转换为字符串之后)。而在HashTable中,我们将key生成一个数(键),并将valuerPair保存在值,从而形成一张新的hash表。

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

我们也可以把散列表称为散列映射

以上为字典和散列表的基础介绍,如果需要了解散列表的进阶应用,请看下一篇推文《JavaScript散列表及其扩展》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值