上章节,使用javascript简单实现了集合的数据结构。而此篇实现的字典,在结构上与上章的集合很相似,只不过两者存储的数据内容略有不同。然而,为什么会有这两种不同的数据结构呢?是因为字典获取值比较快吗?这个暂时还想不明白。但是对于散列表来说,它使用hashCode最为键来保存数据,而JavaScript语言内部,就是使用散列表来表示每个对象的。感觉这类似于数组取值的方法。本章节,我是带着问题来实现字典结构的,想不明白,为什么要分集合、字典两种?难道就是因为各自类具有不同的方法吗。。。一下就是本次的代码:
公共方法(字典与散列表公用)
// 格式化key
function setKeyFn(key) {
if (key == null) return "null";
if (key == undefined) return "undefined";
if (typeof key == "string" || key instanceof String) return key;
if (typeof key == "object") return JSON.stringify(key);
};
// 字典值
function ValueOrigin(key, value) {
this.key = key;
this.value = value;
}
字典数据结构实现
function CustomMap() {
// 字典
this.data = {};
// 添加或替换字典项
CustomMap.prototype.set = function (key, value) {
if (key != null && value != null) {
let _key = setKeyFn(key);
let _value = new ValueOrigin(key, value);
this.data[_key] = _value
return true
}
return false
}
// 判断字典是否包含指定键
CustomMap.prototype.hasKey = function (key) {
let _key = setKeyFn(key);
return !!this.data[_key]
}
// 删除指定key
CustomMap.prototype.remove = function (key) {
if (this.hasKey) {
delete this.data[setKeyFn(key)];
return true
}
return false
}
// 通过key,查找value
CustomMap.prototype.get = function (key) {
let target = this.data[setKeyFn(key)];
return target
}
// 查找全部key和value
CustomMap.prototype.keyValues = function () {
return Object.values(this.data)
}
// 查找全部value
CustomMap.prototype.values = function () {
return this.keyValues().map(item => item.value)
}
// 查找全部key
CustomMap.prototype.keys = function () {
return this.keyValues().map(item => item.key)
}
// 迭代每个键值对
CustomMap.prototype.forEach = function (cal) {
let tmp = this.keyValues();
for (let i = 0; i < tmp.length; i++) {
let reslute = cal(tmp[i].value, tmp[i].key);
if (reslute === false) break;
}
}
// 获取字典的大小
CustomMap.prototype.size = function () {
return Object.keys(this.data).length
}
// 清空字典
CustomMap.prototype.clear = function () {
this.data = {};
}
// 是否为空字典
CustomMap.prototype.isEmpty = function () {
return !!this.size()
}
}
let map = new CustomMap();
map.set("a", { id: 1 })
map.set("a1", { id: 2 })
let res = map.keyValues()
let res1 = map.keys()
let res2 = map.values()
let res3 = map.forEach((item, key) => {
console.log(item, key)
if(key == 'a') return false
})
console.log(map, res, res1, res2)
这便是字典的基本数据结构了,出来value保存的内容不同之外,是不是与集合很像?
散列表
// 散列表:
// 散列算法:尽可能的在数据中,快速找到一个值.
// 目标:给定一个键,返回对应的值所在的位置.
function HashTable() {
this.data = {};
// 新增或删除一个字典值
HashTable.prototype.put = function (key, value) {
if (key != null && value != null) {
let pos = hashCode(key);
this.data[pos] = new ValueOrigin(key, value);
}
return false
}
// 获取一个字典值
HashTable.prototype.get = function (key) {
return this.data[hashCode(key)]
}
// 删除一个字典值
HashTable.prototype.remove = function (key) {
let curHashCode = hashCode(key)
if (this.data[curHashCode] !== undefined) {
delete this.data[curHashCode]
return true
}
return false
}
}
let hashTable = new HashTable();
hashTable.put('javascript', { id: 1 })
hashTable.put('gao', { id: 1 })
hashTable.put('ji', { id: 1 })
hashTable.put('cheng', { id: 1 })
hashTable.put('xu', { id: 1 })
hashTable.put('she', { id: 1 })
hashTable.put('n', { id: 1 })
// let res = hashTable.get('a')
// let res1 = hashTable.remove('a')
console.log(hashTable)
// 散列函数:把每个键中的ASCII值相加
// A ~ Z:65 - 90
// a ~ z:97 - 122
function hashCode(key) {
if (typeof key == 'number') return key;
let likeHashKey = setKeyFn(key);
// 此处也有冲突,但是很少
let target = 5381;
for (let i = 0; i < likeHashKey.length; i++) {
target = target * 33 + likeHashKey.charCodeAt(i)
}
return target % 1013;
// `javascript高级程序设n`
// 下面的方法有冲突:gao -> xu 与 ji -> n
// 解决冲突的三种办法:分离链接、线性探查、双散列法。这里不再实现
// let target = 0;
// for (let i = 0; i < likeHashKey.length; i++) {
// target = target + likeHashKey.charCodeAt(i)
// }
// return target % 37;
}
上面便是散列表的实现,个人感觉蛮不错的。尤其对冲突平衡的解决,这里暂时没实现,后面会进行补充。附带代码中提到的冲突截图:
另外,对于hashCode的功能,为什会如此?这是数学的魅力,又是谁发现的?待究…