集合-运算实现
前面对集合的定义做了一个基本实现, 这篇针对集合的运算 (交, 并, 差) 进行一个补充. 集合这块我在数据处理方面用的还是比较多的.
集合定义
- 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
至次, 集合的基本应用就到这里啦.
耐心和恒心, 总会获得回报的.