集合-运算实现

前面对集合的定义做了一个基本实现, 这篇针对集合的运算 (交, 并, 差) 进行一个补充. 集合这块我在数据处理方面用的还是比较多的.

集合定义

  • has() , 判断元素是否在集合中
  • add() , 添加元素
  • delete() , 删除元素
  • size() , 集合大小
  • values() , 集合值组成的数组
class Set {
  constructor() {
    // 这里用对象来实现集合, key 唯一比较方便, 当然也可以用数组
    this.obj = {} 
  }

  // 查询元素是否在集合中
  has(element) {
    // return element in this.obj 
    return Object.prototype.hasOwnProperty.call(this.obj, element)
  }

  // 添加元素
  add(element) {
    if (! this.has(element)) {
      this.obj[element] = element
      return true
    }
    return false 
  }

  // 删除元素
  delete(element) {
    if (this.has(element)) {
      // 能用 delete 是因为底层用的是对象
      delete this.obj[element]
      return true 
    }
    return false 
  }

  // 清空集合
  clear() {
    this.obj = {}
  }

  // 集合元素个数
  size() {
    // 方法1: 使用一个 count 变量, 每次新增/删除时进行控制
    // 方法2: 用 ES6 语法, Object.keys().length 返回键组成的数组
    // return Object.keys(this.obj).length

    // 方法3: 手动提取底层对象的属性进行计数
    let count = 0
    for (let key in this.obj) {
      // in 会包含原型链上的, hasOwnProperty() 只查找实例自身的
      if (this.obj.hasOwnProperty(key)) count++
    }
    return count 
  }

  // 返回集合元素值
  values() {
    // 方法1: 用 ES6, Object.values() 返回值组成的数组
    // return Object.values(this.obj)

    // 方法2: 遍历对象 key 都存起来
    let arr = []
    for (let key in this.obj) {
      if (this.obj.hasOwnProperty(key)) arr.push(key)
    }
    return arr 
  }
}

集合运算

  • union() , 并集运算
  • intersection() , 交集运算
  • difference() , 差集运算
  • isSubSet() , 是否为子集

并集运算

对于集合 A, B, 分别遍历出两个集合元素, 都添加到新的结果集合中即可.

// 并集 union
union(otherSet) {
	const unionSet = new Set()
    
    this.values().forEach(value => unionSet.add(value))
    otherSet.values().forEach(value => unionSet.add(value))
    
    return unionSet
}

交集运算

对于集合 A, B, 遍历集合 A 的每个元素时, 用集合B的 has() 方法判断是否存在集合 B中.

// 交集 intersection
intersection(otherSet) {
    const intersectionSet = new Set()
    
    const values = this.values()
    if (!values) return undefined 
    
    for (let i = 0; i < values.length; i++) {
        const curValue = values[i]
        if (otherSet.has(curValue)) intersectionSet.add(curValue)
    }
    return intersectionSet
}

这里有个优化的点是, 如果我们事先知道了 A, B 那个元素更少, 则直接遍历更少的, 效率就更高.

// 交集优化
intersection2(otherSet) {
    const intersectionSet = new Set()
    
    // 区分出长度, 只遍历短的即可
    let bigSet = this.values()
    let smallSet = otherSet.values()
    
    if (bigSet.length < smallSet.length) {
        bigSet = otherSet.values()
        smallSet = bigSet.values
    }
    
    // 用更短的遍历
    smallSet.forEach(value => {
        if (bigSet.includes(value)) intersectionSet.add(value)
    })
    return intersectionSet
}

差集运算

对于集合 A, B, 差集就是 A-B = {X|X∈A ^ X !∈ B}, 即 A 有, 但B 没有的元素.

// 差集 A-B = {X|X∈A ^ X !∈ B}
difference(otherSet) {
    const differenceSet = new Set()
    
    // 遍历集合A的元素值, 如果它在B中不存在, 就拿捏
    this.values().forEach(value => {
        if (! otherSet.has(value)) differenceSet.add(value)
    })
    return differenceSet
}

判断子集

对于集合 A, B, 如果 A ∈ B , 即 A 的所有元素都在 B 中, 则称 A 是 B 的子集.

// 子集: A∈B
isSubSet(otherSet) {
    if (this.size() < otherSet.size()) return false 

    let isSubSet = true
    this.values().every(value => {
        if (! otherSet.has(value)) {
            isSubSet = false 
            return false
        }
        return true 
    })
    return isSubSet
}

完整实现

class Set {
  constructor() {
    // 这里用对象来实现集合, key 唯一比较方便, 当然也可以用数组
    this.obj = {} 
  }

  // 查询元素是否在集合中
  has(element) {
    // return element in this.obj 
    return Object.prototype.hasOwnProperty.call(this.obj, element)
  }

  // 添加元素
  add(element) {
    if (! this.has(element)) {
      this.obj[element] = element
      return true
    }
    return false 
  }

  // 删除元素
  delete(element) {
    if (this.has(element)) {
      // 能用 delete 是因为底层用的是对象
      delete this.obj[element]
      return true 
    }
    return false 
  }

  // 清空集合
  clear() {
    this.obj = {}
  }

  // 集合元素个数
  size() {
    // 方法1: 使用一个 count 变量, 每次新增/删除时进行控制
    // 方法2: 用 ES6 语法, Object.keys().length 返回键组成的数组
    // return Object.keys(this.obj).length

    // 方法3: 手动提取底层对象的属性进行计数
    let count = 0
    for (let key in this.obj) {
      // in 会包含原型链上的, hasOwnProperty() 只查找实例自身的
      if (this.obj.hasOwnProperty(key)) count++
    }
    return count 
  }

  // 返回集合元素值
  values() {
    // 方法1: 用 ES6, Object.values() 返回值组成的数组
    // return Object.values(this.obj)

    // 方法2: 遍历对象 key 都存起来
    let arr = []
    for (let key in this.obj) {
      if (this.obj.hasOwnProperty(key)) arr.push(key)
    }
    return arr 
  }

  // 集合运算
  // 并集 union
  union(otherSet) {
    const unionSet = new Set()
    // 分别遍历出两个集合元素, 都添加到新集合里面即可
    this.values().forEach(value => unionSet.add(value))
    otherSet.values().forEach(value => unionSet.add(value))
    return unionSet
  }

  // 交集 intersection
  intersection(otherSet) {
    const intersectionSet = new Set()
    // 遍历集合A的每个元素时, 用集合B的 has() 方法判断是否存在集合B中
    const values = this.values()
    if (!values) return undefined
    
    for (let i = 0; i < values.length; i++) {
      const curValue = values[i]
      if (otherSet.has(curValue)) {
        intersectionSet.add(curValue)
      }
    }
    return intersectionSet
  }

  // 交集优化
  intersection2(otherSet) {
    const intersectionSet = new Set()

    // 区分出长度, 然后用短的遍历
    let biggerSet = this.values()
    let smallSet = otherSet.values()

    if (biggerSet.length < smallSet.length) {
      biggerSet = otherSet.values()
      smallSet = this.values()
    }
    // 用短的进行遍历
    smallSet.forEach(value => {
      if (biggerSet.includes(value)) {
        intersectionSet.add(value)
      }
    })
    return intersectionSet
  }

  // 差集 A-B = {X|X∈A ^ X !∈ B}
  difference(otherSet) {
    const differenceSet = new Set()
    // 遍历就行
    this.values().forEach(value => {
      if (! otherSet.has(value)) differenceSet.add(value)
    })
    return differenceSet
  }

  // 子集: A∈B
  isSubSet(otherSet) {
    if (this.size() < otherSet.size()) return false 

    let isSubSet = true
    this.values().every(value => {
      if (! otherSet.has(value)) {
        isSubSet = false 
        return false
      }
      return true 
    })
    return isSubSet
  }

}


// test 
const setA = new Set()
setA.add(1)
setA.add(2)
setA.add(3)

const setB = new Set()
setB.add(3)
setB.add(4)
setB.add(5)

const unionAB = setA.union(setB)
console.log('A + B: ', unionAB.values());

// const intersectionAB = setA.intersection(setB)
const intersectionAB = setA.intersection2(setB)
console.log('A && B:', intersectionAB.values());

const differenceAB = setA.difference(setB)
console.log('A-B: ', differenceAB);

const isSubSetAB = setA.isSubSet(setB)
console.log('A∈B:', isSubSetAB);

测试输出:

A + B:  [ '1', '2', '3', '4', '5' ]

A && B: [ '3' ]

A-B:  Set { obj: { '1': '1', '2': '2' } }

A∈B: false

至次, 集合的基本应用就到这里啦.

posted @ 2024-06-21 13:33  致于数据科学家的小陈  阅读(15)  评论(0编辑  收藏  举报