前面對集合的定義做了一個基本實現, 這篇針對集合的運算 (交, 並, 差) 進行一個補充. 集合這塊我在資料處理方面用的還是比較多的.
集合定義
- 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
至次, 集合的基本應用就到這裡啦.