| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:cfg/utils/bit_vector.dart'; |
| import 'package:test/test.dart'; |
| |
| void main() { |
| test('size 0', () { |
| final bv1 = BitVector(0); |
| final bv2 = BitVector(0); |
| final bv3 = BitVector(0); |
| expect(bv1.elements, isEmpty); |
| |
| bv1.intersect(bv2); |
| expect(bv1.elements, isEmpty); |
| |
| expect(bv1.addAll(bv2), isFalse); |
| expect(bv1.elements, isEmpty); |
| |
| expect(bv1.addSubtraction(bv2, bv3), isFalse); |
| expect(bv1.elements, isEmpty); |
| |
| expect(bv1.addIntersection(bv2, bv3), isFalse); |
| expect(bv1.elements, isEmpty); |
| }); |
| |
| test('indexing add remove', () { |
| for (var n in [20, 63, 64, 65, 200, 2000]) { |
| final mid = n ~/ 2; |
| final bv = BitVector(n); |
| |
| expect(bv[0], isFalse); |
| expect(bv[mid], isFalse); |
| expect(bv[n - 1], isFalse); |
| |
| bv.add(0); |
| bv.add(mid); |
| bv.add(n - 1); |
| expect(bv[0], isTrue); |
| expect(bv[1], isFalse); |
| expect(bv[mid - 1], isFalse); |
| expect(bv[mid], isTrue); |
| expect(bv[n - 2], isFalse); |
| expect(bv[n - 1], isTrue); |
| expect(bv.elements.toList(), equals([0, mid, n - 1])); |
| |
| bv.remove(0); |
| bv.remove(1); |
| bv.remove(n - 1); |
| expect(bv[0], isFalse); |
| expect(bv[1], isFalse); |
| expect(bv[mid - 1], isFalse); |
| expect(bv[mid], isTrue); |
| expect(bv[n - 2], isFalse); |
| expect(bv[n - 1], isFalse); |
| expect(bv.elements.toList(), equals([mid])); |
| |
| bv[0] = true; |
| bv[1] = false; |
| bv[mid - 1] = true; |
| bv[n - 2] = true; |
| expect(bv[0], isTrue); |
| expect(bv[1], isFalse); |
| expect(bv[mid - 1], isTrue); |
| expect(bv[mid], isTrue); |
| expect(bv[n - 2], isTrue); |
| expect(bv[n - 1], isFalse); |
| expect(bv.elements.toList(), equals([0, mid - 1, mid, n - 2])); |
| } |
| }); |
| |
| test('bulk operations', () { |
| for (var n in [20, 63, 64, 65, 200, 2000]) { |
| final bv1 = BitVector(n); |
| final bv2 = BitVector(n); |
| final bv3 = BitVector(n); |
| |
| for (var i = 0; i < n; i += 3) { |
| bv2[i] = true; |
| } |
| for (var i = 0; i < n; i += 5) { |
| bv3[i] = true; |
| } |
| |
| expect(bv1.addAll(bv2), isTrue); |
| expect(bv1.addAll(bv2), isFalse); |
| bv1.intersect(bv3); |
| for (var i = 0; i < n; ++i) { |
| expect(bv1[i], equals(i % 15 == 0)); |
| expect(bv2[i], equals(i % 3 == 0)); |
| expect(bv3[i], equals(i % 5 == 0)); |
| } |
| |
| expect(bv2.addAll(bv1), isFalse); |
| expect(bv3.addAll(bv1), isFalse); |
| for (var i = 0; i < n; ++i) { |
| expect(bv1[i], equals(i % 15 == 0)); |
| expect(bv2[i], equals(i % 3 == 0)); |
| expect(bv3[i], equals(i % 5 == 0)); |
| } |
| |
| expect(bv2.addSubtraction(bv3, bv1), isTrue); |
| for (var i = 0; i < n; ++i) { |
| expect(bv1[i], equals(i % 15 == 0)); |
| expect(bv2[i], equals(i % 3 == 0 || i % 5 == 0)); |
| expect(bv3[i], equals(i % 5 == 0)); |
| } |
| expect(bv3.addSubtraction(bv1, bv2), isFalse); |
| |
| expect(bv3.addIntersection(bv1, bv2), isFalse); |
| expect(bv1.addIntersection(bv2, bv3), isTrue); |
| for (var i = 0; i < n; ++i) { |
| expect(bv1[i], equals(i % 5 == 0)); |
| expect(bv2[i], equals(i % 3 == 0 || i % 5 == 0)); |
| expect(bv3[i], equals(i % 5 == 0)); |
| } |
| |
| bv3.clear(); |
| for (var i = 0; i < n; ++i) { |
| expect(bv3[i], isFalse); |
| } |
| |
| var k = 0; |
| for (int bit in bv1.elements) { |
| expect(bit, equals(k * 5)); |
| ++k; |
| } |
| expect(k, equals(1 + (n - 1) ~/ 5)); |
| } |
| }); |
| |
| test('expand', () { |
| for (final initialSize in [0, 20, 63, 64, 65, 120]) { |
| final bv = BitVector(initialSize); |
| final initialCapacity = bv.capacity; |
| |
| for (var i = 0; i < initialCapacity; ++i) { |
| if (i % 3 == 0) { |
| bv[i] = true; |
| } |
| } |
| |
| final expanded = bv.expand(initialCapacity + 100); |
| expect(expanded.capacity, greaterThanOrEqualTo(initialCapacity + 100)); |
| |
| // Existing bits should be preserved. |
| for (var i = 0; i < initialCapacity; ++i) { |
| expect(expanded[i], equals(i % 3 == 0)); |
| } |
| |
| // Newly added capacity should be initialized to zero/false. |
| for (var i = initialCapacity; i < expanded.capacity; ++i) { |
| expect(expanded[i], isFalse); |
| } |
| } |
| }); |
| |
| test('setRange', () { |
| void fillPattern(BitVector bv, int seed) { |
| for (var i = 0; i < bv.capacity; ++i) { |
| bv[i] = ((i * 17 + seed) & 3) == 0; |
| } |
| } |
| |
| void referenceSetRange( |
| BitVector dst, |
| int start, |
| int end, |
| BitVector src, [ |
| int skipCount = 0, |
| ]) { |
| for (var k = 0; k < end - start; ++k) { |
| dst[start + k] = src[skipCount + k]; |
| } |
| } |
| |
| void checkSetRange( |
| int dstSize, |
| int srcSize, |
| int start, |
| int end, |
| int skipCount, |
| ) { |
| final actual = BitVector(dstSize); |
| final expected = BitVector(dstSize); |
| final src = BitVector(srcSize); |
| |
| fillPattern(actual, 1); |
| fillPattern(expected, 1); |
| fillPattern(src, 5); |
| |
| actual.setRange(start, end, src, skipCount); |
| referenceSetRange(expected, start, end, src, skipCount); |
| |
| for (var i = 0; i < actual.capacity; ++i) { |
| expect( |
| actual[i], |
| equals(expected[i]), |
| reason: |
| 'Mismatch at bit $i for setRange(start=$start, end=$end, ' |
| 'skipCount=$skipCount, dstCapacity=${actual.capacity}, ' |
| 'srcCapacity=${src.capacity})', |
| ); |
| } |
| } |
| |
| // Empty range. |
| checkSetRange(100, 100, 10, 10, 5); |
| |
| // Within a single 64-bit word. |
| checkSetRange(100, 100, 5, 35, 10); |
| checkSetRange(100, 100, 0, 60, 2); |
| |
| // Spanning across a word boundary without whole inner words. |
| checkSetRange(128, 128, 50, 75, 10); |
| checkSetRange(128, 128, 60, 68, 0); |
| |
| // Word-aligned start and end (shiftLo == 0). |
| checkSetRange(256, 256, 0, 64, 0); |
| checkSetRange(256, 256, 64, 192, 64); |
| checkSetRange(256, 256, 64, 192, 0); |
| |
| // Aligned relative skipCount ((start & 63) == (skipCount & 63)) -> shiftLo == 0. |
| checkSetRange(300, 300, 10, 202, 74); |
| |
| // Unaligned relative skipCount -> shiftLo != 0. |
| checkSetRange(300, 300, 10, 202, 15); |
| checkSetRange(300, 300, 0, 150, 33); |
| checkSetRange(300, 300, 63, 195, 1); |
| |
| // Exhaustive small/medium range combinations across word boundaries. |
| for (final start in [0, 1, 31, 63, 64, 65, 100]) { |
| for (final length in [0, 1, 30, 63, 64, 65, 127, 128, 130]) { |
| for (final skipCount in [0, 1, 33, 63, 64, 65]) { |
| final end = start + length; |
| final dstSize = end + 70; |
| final srcSize = skipCount + length + 70; |
| checkSetRange(dstSize, srcSize, start, end, skipCount); |
| } |
| } |
| } |
| }); |
| } |