1/* 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16/** 17 * SparseBitVector is a LLVM-interfaces-like data structure designed to efficiently 18 * represent and manipulate large sets of integers where the majority of the elements 19 * are unset (i.e., sparse). It is particularly useful in scenarios where memory efficiency 20 * is critical, and the set of integers contains large gaps between set bits. 21 * 22 * The SparseBitVector is implemented as a collection of SparseBitVectorElement objects, 23 * where each element represents a fixed-size chunk of the bit vector. This allows the 24 * structure to only allocate memory for the portions of the bit vector that contain 25 * set bits, significantly reducing memory usage for sparse data. 26 * 27 * Key Features: 28 * - **Unordered**: We implement it as unordered rather than LLVM's for performance reason. 29 * - **Sparse Storage**: Only stores the indices of set bits, making it memory-efficient 30 * for sparse datasets. 31 * - **Efficient Operations**: Supports fast bitwise operations such as union and intersection 32 * - **Iterable**: Provides an iterator to traverse all set bits in stored order. 33 * - **Dynamic Resizing**: Automatically adjusts its internal structure as bits are set 34 * or reset. 35 * 36 * Perforceman VS Array 37 * - **Random Store** 2.5:1 38 * - **Continuous Store** 1:1 39 * - **Random Test** 1:6 40 * - **Continuous Test** 1:1 41 * - **Random Iterator** 4:1 42 * - **Continuous Iterator** 2:1 43 * 44 * The SparseBitVector is parameterized by `ElementSize`, which defines the size of each 45 * chunk (element) in the bit vector and MUST be times of 32. This allows for customization 46 * based on the expected sparsity and performance requirements. 47 * 48 */ 49 50export type Word = Uint16Array; 51const BITWORD_SIZE = 16; // bits of a Word 52const DEFAULT_SIZE = 64; 53class SparseBitVectorElement { 54 private ELEMENT_SIZE; // bits of element. Default as 128 55 private BITWORDS_NUM; // number of words 56 private bits: Word; 57 58 constructor(elementSize: number = DEFAULT_SIZE) { 59 this.ELEMENT_SIZE = elementSize; 60 this.BITWORDS_NUM = Math.ceil(this.ELEMENT_SIZE / BITWORD_SIZE); 61 this.bits = new Uint16Array(this.BITWORDS_NUM); 62 } 63 64 word(idx: number): number { 65 return this.bits[idx]; 66 } 67 68 clone(): Word { 69 return new Uint16Array(this.bits); 70 } 71 72 get elementSize(): number { 73 return this.ELEMENT_SIZE; 74 } 75 76 get bitWordNum(): number { 77 return this.BITWORDS_NUM; 78 } 79 80 // Check if the element is empty (all bits are zero) 81 isEmpty(): boolean { 82 return this.isZero(); 83 } 84 85 // Set a bit at the given index 86 set(bitIdx: number): void { 87 const wordIndex = Math.floor(bitIdx / BITWORD_SIZE); 88 const bitOffset = bitIdx % BITWORD_SIZE; 89 this.bits[wordIndex] |= 1 << bitOffset; 90 } 91 92 setWord(word: Word): void { 93 this.bits = word; 94 } 95 96 // Reset a bit at the given index 97 reset(bitIdx: number): void { 98 const wordIndex = Math.floor(bitIdx / BITWORD_SIZE); 99 const bitOffset = bitIdx % BITWORD_SIZE; 100 this.bits[wordIndex] &= ~(1 << bitOffset); 101 } 102 103 // Test if a bit is set 104 test(bitIdx: number): boolean { 105 const wordIndex = Math.floor(bitIdx / BITWORD_SIZE); 106 const bitOffset = bitIdx % BITWORD_SIZE; 107 return (this.bits[wordIndex] & (1 << bitOffset)) !== 0; 108 } 109 110 // Set if not existing, else return 111 test_and_set(bitIdx: number): boolean { 112 let old = this.test(bitIdx); 113 if (!old) { 114 this.set(bitIdx); 115 return true; 116 } 117 return false; 118 } 119 120 // Count the number of set bits in this element 121 count(): number { 122 let numBits = 0; 123 this.bits.forEach(word => { 124 numBits += this.countBits(word); 125 }); 126 return numBits; 127 } 128 129 // Find the index of the first set bit in this element 130 findFirst(): number { 131 for (let i = 0; i < this.bits.length; i++) { 132 if (this.bits[i] !== 0) { 133 return i * BITWORD_SIZE + this.countTrailingZeros(this.bits[i]); 134 } 135 } 136 return -1; // No bits are set 137 } 138 139 // Find the next set bit after the given index 140 findNext(bitIdx: number): number { 141 bitIdx++; 142 let wordIndex = Math.floor(bitIdx / BITWORD_SIZE); 143 let bitOffset = bitIdx % BITWORD_SIZE; 144 145 // Check the current word 146 // Mask off previous bits 147 let word = this.bits[wordIndex] & (~0 << bitOffset); 148 if (word !== 0) { 149 return wordIndex * BITWORD_SIZE + this.countTrailingZeros(word); 150 } 151 152 // Check subsequent words 153 for (let i = wordIndex + 1; i < this.bits.length; i++) { 154 if (this.bits[i] !== 0) { 155 return i * BITWORD_SIZE + this.countTrailingZeros(this.bits[i]); 156 } 157 } 158 159 return -1; // No more bits are set 160 } 161 162 // Comparison 163 equals(rhs: SparseBitVectorElement): boolean { 164 for (let i = 0; i < this.BITWORDS_NUM; i++) { 165 if (this.bits[i] !== rhs.word(i)) { 166 return false; 167 } 168 } 169 170 return true; 171 } 172 173 // Union this element with another element and return true if this one changed 174 unionWith(other: SparseBitVectorElement): boolean { 175 let changed = false; 176 for (let i = 0; i < this.bits.length; i++) { 177 const oldWord = changed ? 0 : this.bits[i]; 178 this.bits[i] |= other.bits[i]; 179 if (!changed && oldWord !== this.bits[i]) { 180 changed = true; 181 } 182 } 183 return changed; 184 } 185 186 // Intersect this element with another element and return true if this one changed. 187 intersectWith(other: SparseBitVectorElement): boolean { 188 let changed = false; 189 for (let i = 0; i < this.bits.length; i++) { 190 const oldWord = changed ? 0 : this.bits[i]; 191 this.bits[i] &= other.bits[i]; 192 if (!changed && oldWord !== this.bits[i]) { 193 changed = true; 194 } 195 } 196 return changed; 197 } 198 199 // Subtract another SparseBitVectorElement from this one. 200 subtractWith(rhs: SparseBitVectorElement): boolean { 201 let changed = false; 202 // Perform subtraction: this = this & ~rhs 203 for (let i = 0; i < this.bits.length; i++) { 204 const oldWord = this.bits[i]; 205 this.bits[i] &= ~rhs.bits[i]; 206 207 // If any bit was changed, mark as changed 208 if (this.bits[i] !== oldWord) { 209 changed = true; 210 } 211 } 212 213 return changed; 214 } 215 216 // Count the number of set bits in a word 217 countBitsV2(word: number): number { 218 let count = 0; 219 while (word !== 0) { 220 word &= word - 1; 221 count++; 222 } 223 return count; 224 } 225 226 // Count the number of set bits in a word 227 countBits(word: number): number { 228 // assume the value is treated as a unsigned integer 229 let v = word; 230 231 // Step 1: Pairwise addition of bits 232 v = v - ((v >> 1) & 0x55555555); 233 // Step 2: Group bits into 4-bit chunks and add 234 v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 235 // Step 3: Group bits into 8-bit chunks and add 236 v = (v + (v >> 4)) & 0xf0f0f0f; 237 // Step 4: Multiply by a magic number to sum all 8-bit chunks into the highest byte 238 v = (v * 0x1010101) >> 24; 239 240 return v; 241 } 242 243 isZero(): boolean { 244 for (let i = 0; i < this.BITWORDS_NUM; i++) { 245 if (this.bits[i] !== 0) { 246 return false; 247 } 248 } 249 return true; 250 } 251 252 // Count trailing zeros in a word 253 private countTrailingZeros(word: number): number { 254 if (word === 0) { 255 return BITWORD_SIZE; 256 } 257 258 if ((word & 1) !== 0) { 259 return 0; 260 } 261 262 let zeroBits = 0; 263 let shift = BITWORD_SIZE / 2; // Start with half the bit width 264 let mask = (1 << shift) - 1; // Mask for the lower half 265 266 while (shift > 0) { 267 if ((word & mask) === 0) { 268 word >>= shift; 269 zeroBits |= Number(shift); 270 } 271 shift >>= 1; 272 mask >>= shift; 273 } 274 275 return zeroBits; 276 } 277} 278 279export class SparseBitVector { 280 private ELEMENT_SIZE: number; 281 // Unordered storage of elements. 282 // key is actually the element index (normally it is in element) 283 private elements: Map<number, SparseBitVectorElement> = new Map(); 284 285 constructor(elementsSize: number = DEFAULT_SIZE) { 286 this.ELEMENT_SIZE = elementsSize; 287 } 288 289 get elementSize(): number { 290 return this.ELEMENT_SIZE; 291 } 292 293 get elems(): Map<number, SparseBitVectorElement> { 294 return this.elements; 295 } 296 297 // Set a bit at the given index 298 set(bitIdx: number): void { 299 const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE); 300 let element = this.elements.get(elementIndex); 301 if (!element) { 302 element = new SparseBitVectorElement(this.ELEMENT_SIZE); 303 this.elements.set(elementIndex, element); 304 } 305 element.set(bitIdx % this.ELEMENT_SIZE); 306 } 307 308 // Test if a bit is set 309 test(bitIdx: number): boolean { 310 const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE); 311 const element = this.elements.get(elementIndex); 312 return element ? element.test(bitIdx % this.ELEMENT_SIZE) : false; 313 } 314 315 // Set a bit if not existing. Else return 316 testAndSet(bitIdx: number): boolean { 317 let old = this.test(bitIdx); 318 if (!old) { 319 this.set(bitIdx); 320 return true; 321 } 322 return false; 323 } 324 325 // Reset a bit at the given index 326 reset(bitIdx: number): void { 327 const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE); 328 let element = this.elements.get(elementIndex); 329 if (element) { 330 element.reset(bitIdx % this.ELEMENT_SIZE); 331 if (element.isEmpty()) { 332 this.elements.delete(elementIndex); 333 } 334 } 335 } 336 337 // Clear all elements 338 clear(): void { 339 this.elements.clear(); 340 } 341 342 // Clone, return a deep copied object 343 clone(): SparseBitVector { 344 const newVector = new SparseBitVector(this.elementSize); 345 for (const [idx, element] of this.elements) { 346 const newElement = new SparseBitVectorElement(this.elementSize); 347 newElement.setWord(element.clone()); 348 349 newVector.elems.set(idx, newElement); 350 } 351 352 return newVector; 353 } 354 355 // Find the first set bit in the vector 356 findFirst(): number { 357 if (this.elements.size === 0) { 358 return -1; 359 } 360 const firstElement = this.elements.entries().next().value; 361 if (firstElement) { 362 const firstBit = firstElement[1].findFirst(); 363 return firstElement[0] * this.ELEMENT_SIZE + firstBit; 364 } else { 365 return -1; 366 } 367 } 368 369 // Count the number of set bits in the vector 370 count(): number { 371 let count = 0; 372 this.elements.forEach((elem, _) => { 373 count += elem.count(); 374 }); 375 return count; 376 } 377 378 // Check if the vector is empty 379 isEmpty(): boolean { 380 return this.elements.size === 0; 381 } 382 383 [Symbol.iterator](): IterableIterator<number> { 384 let iter = this.elements.entries(); 385 let next = iter.next(); 386 const elementSize = this.ELEMENT_SIZE; 387 let element = next.value; 388 if (!element) { 389 return { 390 next(): { value: undefined; done: true } { 391 return { value: undefined, done: true }; 392 }, 393 [Symbol.iterator](): IterableIterator<number> { 394 return this; // Make the iterator itself iterable 395 }, 396 }; 397 } 398 let bitIndex = element[1].findFirst(); 399 return { 400 next(): IteratorResult<number> { 401 if (element) { 402 let v = element[0] * elementSize + bitIndex; 403 bitIndex = element[1].findNext(bitIndex); 404 if (bitIndex === -1) { 405 next = iter.next(); 406 element = next.value; 407 if (element) { 408 bitIndex = element[1].findFirst(); 409 } 410 } 411 return { value: v, done: false }; 412 } 413 return { value: undefined, done: true }; 414 }, 415 [Symbol.iterator](): IterableIterator<number> { 416 return this; // Make the iterator itself iterable 417 }, 418 }; 419 } 420 421 /** 422 * Check if this SparseBitVector is equal to another SparseBitVector. 423 */ 424 equals(rhs: SparseBitVector): boolean { 425 if (this.ELEMENT_SIZE !== rhs.ELEMENT_SIZE || this.elems.size !== rhs.elems.size) { 426 return false; 427 } 428 429 let rhsElems = rhs.elems; 430 for (let p of this.elements) { 431 let rhsElem = rhsElems.get(p[0]); 432 if (!rhsElem) { 433 return false; 434 } 435 if (!rhsElem.equals(p[1])) { 436 return false; 437 } 438 } 439 return true; 440 } 441 442 /** 443 * Perform a union operation with another SparseBitVector. 444 * Returns True if this vector was changed, false otherwise. 445 */ 446 unionWith(rhs: SparseBitVector): boolean { 447 if (this.equals(rhs) || rhs.elems.size === 0) { 448 return false; 449 } 450 451 let changed = false; 452 let newElems = new Map<number, SparseBitVectorElement>(); 453 for (let p of rhs.elems) { 454 let elem = this.elements.get(p[0]); 455 if (elem) { 456 changed = elem!.unionWith(p[1]) || changed; 457 } else { 458 newElems.set(p[0], p[1]); 459 } 460 } 461 462 if (newElems.size > 0) { 463 newElems.forEach((v, k) => this.elements.set(k, v)); 464 changed = true; 465 } 466 467 return changed; 468 } 469 470 /** 471 * Perform an intersection operation with another SparseBitVector. 472 * Returns True if this vector was changed, false otherwise. 473 */ 474 intersectWith(rhs: SparseBitVector): boolean { 475 if (this.equals(rhs) || rhs.elems.size === 0) { 476 return false; 477 } 478 479 let changed = false; 480 // If either vector is empty, the result is empty 481 if (this.elements.size === 0 || rhs.elems.size === 0) { 482 if (this.elements.size > 0) { 483 this.elements = new Map<number, SparseBitVectorElement>(); 484 changed = true; 485 } 486 return changed; 487 } 488 489 let needDeleteIdx: Set<number> = new Set(); 490 for (let p of this.elems) { 491 let elem = rhs.elems.get(p[0]); 492 if (elem) { 493 changed = p[1].intersectWith(elem) || changed; 494 if (changed && p[1].isZero()) { 495 needDeleteIdx.add(p[0]); 496 } 497 } else { 498 needDeleteIdx.add(p[0]); 499 } 500 } 501 502 if (needDeleteIdx.size > 0) { 503 needDeleteIdx.forEach(idx => this.elements.delete(idx)); 504 changed = true; 505 } 506 507 return changed; 508 } 509 510 /** 511 * Subtract another SparseBitVector from this one. 512 * This operation modifies the current SparseBitVector in place. 513 * Return True if the current SparseBitVector was modified, false otherwise. 514 */ 515 subtractWith(rhs: SparseBitVector): boolean { 516 if (this.elementSize !== rhs.elementSize || this.isEmpty() || rhs.isEmpty()) { 517 return false; 518 } 519 520 let needDeleteIdx: Set<number> = new Set(); 521 let changed = false; 522 for (const [elementIndex, element] of this.elements) { 523 const rhsElement = rhs.elements.get(elementIndex); 524 525 if (rhsElement) { 526 changed = element.subtractWith(rhsElement) || changed; 527 if (element.isEmpty()) { 528 needDeleteIdx.add(elementIndex); 529 } 530 } 531 } 532 533 if (needDeleteIdx.size > 0) { 534 needDeleteIdx.forEach(idx => this.elements.delete(idx)); 535 changed = true; 536 } 537 538 return changed; 539 } 540 541 toString(): string { 542 let ar = [...this]; 543 return ar.toString(); 544 } 545} 546