JS手写set与map

set

set是一个没有重复元素的集合,事实上我们无法完全的使用js来模拟出set的全部功能,因为浏览器原生的set底层是使用c++实现,能直接访问内存,所以我们只能实现set的一部分功能
这里我们使用class来实现
具体关于class的部分可以看我这篇文章
class

add

首先我们有了一个mySet类,这个类会接受一个可迭代对象,我们需要判断这个可迭代对象是否真的可迭代
判断依据就是每个可迭代对象内都有一个[@@iterator]成员,并且这个成员还是一个方法

class mySet {
    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
    }
}

接下来我们定义一个数组来存储相关数据,在构造方法内遍历这个可迭代对象,将其中的内容通过add方法填入数组中

class mySet {
    #data = []
    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        for (const item of iterator) {
            this.add(item)
        }
    }
    add(item) {
    }
}

has与equals

set与普通数组最大不同的一点就在于set内的元素是不会重复的,所以我们在向数组中放入元素时需要提前判断一下该元素是否已在数组中
我们可以通过调用has方法来判断此元素是否在数组中

class mySet {
    #data = []
    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        for (const item of iterator) {
            this.add(item)
        }
    }
    add(item) {
        if (!this.has(item)) this.#data.push(item)
    }
    has(item) {
    }
}

现在问题转变成了has方法怎么实现
我们可以遍历data数组,将传入的值通过Object.is来判断是否相同,如果相同就返回true,全部遍历完都没有返回的话就返回false
但是Object.is有一个问题,在Object.is中+0和-0被认为是两个不同的东西,所以我们需要将Object.is再封装一层,这个封装可有可无,但set内部是针对Object.is封装了的

class mySet {
    add(item) {
        if (!this.has(item)) this.#data.push(item)
    }
    has(item) {
        for (const iterator of this.#data) {
            if (mySet.#equal(item, iterator)) {
                return true
            }
        }
        return false;
    }
    static #equal(item1, item2) {
        if (item1 === 0 && item2 === 0) {
            return false
        }
        return Object.is(item1, item2)
    }
}

我们封装了一个静态方法equal用来比较传入的两个值是否相等

delete

接下来我们就需要实现delete方法

class mySet {
    delete(item) {
        for (const iterator of this.#data) {
            if (mySet.#equal(item, iterator)) {
                this.#data.splice(i, 1)
            }
        }
    }
}

迭代器

我们知道set是能直接被forof遍历的,这说明了set内部是实现了迭代器,
迭代器(iterator),可以把它当做是一个接口,用户可以使用该接口来遍历数据而不需关心数据内部的实现细节
具体关于迭代器的部分可以看我这篇博客
迭代器与生成器

class mySet {
    *[Symbol.iterator]() {
        yield* Object.entries(this.#data)
    }
}

完整实现

我们接下来还有一些成员没有实现,如size,clear,forEach成员,具体代码如下

class mySet {
    #data = []
    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        for (const item of iterator) {
            this.add(item)
        }
    }
    add(item) {
        if (!this.has(item)) this.#data.push(item)
    }
    has(item) {
        for (const iterator of this.#data) {
            if (mySet.#equal(item, iterator)) {
                return true
            }
        }
        return false;
    }
    delete(item) {
        for (const iterator of this.#data) {
            if (mySet.#equal(item, iterator)) {
                this.#data.splice(i, 1)
            }
        }
    }
    clear() {
        this.#data.length = 0
    }
    forEach(fn) {
        for (let i = 0; i < this.#data.length; i++) {
            const item = this.#data[i]
            fn(item, i, this)
        }
    }
    *[Symbol.iterator]() {
        yield* Object.entries(this.#data)
    }
    get size() {
        return this.#data.length
    }
    static #equal(item1, item2) {
        if (item1 === 0 && item2 === 0) {
            return false
        }
        return Object.is(item1, item2)
    }
}

测试代码

const myset = new mySet([12, 3, 56, 56, 5, 5]);
console.log(myset)
const set = new Set([1, 2, 3, 4, 5])
for (const iterator of myset) {
    console.log(iterator)
}

结果
结果

map

map和set类似,这里只描述map与set的不同点

  1. 在set中我们只需要针对constructor中传入的对象进行判断,而在map中我们需要针对对象中的各个元素进行判断是否可迭代

    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        for (const item of iterator) {
            if (typeof item[Symbol.iterator] !== "function") {
                throw Error(`${item}不是可迭代对象`)
            }
            const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;
            const value = iterator.next().value;
            this.set({
                key,
                value
            })
        }
    }
    

    如果符合条件的话就将它的第一项拿出作为key,第二项拿出作为value,因为不能确定每个元素都是什么类型,所以只能通过迭代器去拿到相应的值

  2. 在set中我们需要大量的遍历,我们可以封装一个函数用来获取data中的元素

    #getObj(key) {
    
        for (const item of this.#data) {
            if (myMap.#equal(item.key, key)) {
                return item
            }
        }
        return undefined
    }
    

    这个函数传入一个key,如果有的话返回该元素,没有的话就返回undefined,之后其他方法如果需要遍历数组来拿到元素,就可以使用这个方法

  3. 在构建迭代器的时候我们需要知道数组里存的元素是对象,在迭代的时候我们需要将它变为二维数组

    *[Symbol.iterator]() {
        for (const item of this.#data) {
            yield [item.key, item.value]
        }
    }
    

最终代码

class myMap {
    #data = []
    constructor(iterator) {
        if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        for (const item of iterator) {
            if (typeof item[Symbol.iterator] !== "function") {
                throw Error(`${item}不是可迭代对象`)
            }
            const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;
            const value = iterator.next().value;
            this.set({
                key,
                value
            })
        }
    }
    set(item) {
        const obj = this.#getObj(item.key)
        if (this.has(item.key)) {
            obj.value = item.value
        } else {
            this.#data.push(item)
        }
    }
    has(key) {
        const item = this.#getObj(key)
        return item ? true : false
    }
    get(key) {
        return this.#getObj(key);
    }
    delete(key) {
        this.#data = this.#data.filter((value) => {
            if (value.key !== key) return value
        })
    }
    forEach(fn) {
        for (const item of this.#data) {
            fn(item.value, item.key, this)
        }
    }
    #getObj(key) {
        for (const item of this.#data) {
            if (myMap.#equal(item.key, key)) {
                return item
            }
        }
        return undefined
    }
    static #equal(item1, item2) {
        if (item1 === 0 && item2 === 0) {
            return false
        }
        return Object.is(item1, item2)
    }
    *[Symbol.iterator]() {
        for (const item of this.#data) {
            yield [item.key, item.value]
        }
    }
}

测试代码

const mymap = new myMap([[1, 'a'], [{ name: "a" }, 2], [null, 3], [undefined, 4], [5, null]])
console.log(mymap.get(null))
mymap.delete(undefined)
for (const item of mymap) {
    console.log(item)
}

结果

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是一个简单的 Java Map 实现,使用数组和链表来解决哈希冲突问题: ```java public class MyMap<K, V> { private static final int DEFAULT_CAPACITY = 16; private Entry<K, V>[] table; private int size; public MyMap() { table = new Entry[DEFAULT_CAPACITY]; } public V put(K key, V value) { int hash = key.hashCode() % table.length; Entry<K, V> entry = table[hash]; if (entry == null) { entry = new Entry<K, V>(key, value); table[hash] = entry; size++; } else { while (entry.getNext() != null) { if (entry.getKey().equals(key)) { entry.setValue(value); return value; } entry = entry.getNext(); } if (entry.getKey().equals(key)) { entry.setValue(value); return value; } entry.setNext(new Entry<K, V>(key, value)); size++; } return value; } public V get(K key) { int hash = key.hashCode() % table.length; Entry<K, V> entry = table[hash]; while (entry != null) { if (entry.getKey().equals(key)) { return entry.getValue(); } entry = entry.getNext(); } return null; } public int size() { return size; } private static class Entry<K, V> { private final K key; private V value; private Entry<K, V> next; public Entry(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } public Entry<K, V> getNext() { return next; } public void setNext(Entry<K, V> next) { this.next = next; } } } ``` 这个实现使用了一个数组来存储键值对,每个数组元素是一个链表,用来解决哈希冲突问题。在 `put` 方法中,先计算出键的哈希值,然后找到对应的链表头,遍历链表,如果找到了相同的键,则更新其值,否则在链表末尾添加新的键值对。在 `get` 方法中,也是先计算出键的哈希值,然后遍历对应的链表,寻找相同的键。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值