Skip to content

Commit

Permalink
perf: allow initializing a BitSet with an arbitrary size
Browse files Browse the repository at this point in the history
  • Loading branch information
lppedd authored and ftomassetti committed Feb 8, 2024
1 parent 91fb164 commit 584ffd8
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package com.strumenta.antlrkotlin.runtime

public expect class BitSet() {
public expect class BitSet(size: Int) {
/**
* Creates a bit set with an initial capacity of 64 bits.
*/
public constructor()

/**
* Sets the bit at the specified index to `true`.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public class SimpleBitSet(size: Int) {
private const val ALL_FALSE: Long = 0L // 0x0000_0000_0000_0000
}

init {
require(size >= 0) {
"The initial bitset size must be equal or greater than 0"
}
}

private var bits: LongArray = LongArray(bitToElementSize(size))

private val lastIndex: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,13 @@ class BitSetTest {
val bitSet1 = BitSet()
bitSet1.set(2)
bitSet1.set(5)
bitSet1.clear(10)

val bitSet2 = BitSet()
bitSet2.set(2)
bitSet2.set(5)
bitSet2.set(10000)
bitSet2.clear(10000)

assertEquals(bitSet1, bitSet2)
assertEquals(bitSet1.hashCode(), bitSet2.hashCode())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,109 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.

package com.strumenta.antlrkotlin.runtime

import js.core.delete
import org.antlr.v4.kotlinruntime.misc.MurmurHash

public actual class BitSet {
private val wrapped = js("[]").unsafeCast<Array<Boolean>>()
@Suppress("SimplifyBooleanWithConstants")
public actual class BitSet actual constructor(size: Int) {
private val bits = newArray(size)

init {
require(size >= 0) {
"The initial bitset size must be equal or greater than 0"
}
}

public actual constructor() : this(64)

public actual fun set(bitIndex: Int) {
if (bitIndex < 0) {
throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
}

wrapped[bitIndex] = true
bits[bitIndex] = true
}

public actual fun clear(bitIndex: Int) {
if (bitIndex < 0) {
throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
}

delete(wrapped[bitIndex])
delete(bits[bitIndex])
}

public actual fun get(bitIndex: Int): Boolean {
if (bitIndex < 0) {
throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
}

if (bitIndex >= wrapped.size) {
if (bitIndex >= bits.size) {
return false
}

@Suppress("SimplifyBooleanWithConstants")
return wrapped[bitIndex] == true
return bits[bitIndex] == true
}

public actual fun cardinality(): Int =
@Suppress("SimplifyBooleanWithConstants")
wrapped.count { it == true }
public actual fun cardinality(): Int {
// Note(Edoardo): keep like this for performance reasons
var c = 0

for (i in bits.indices) {
if (bits[i] == true) {
c++
}
}

return c
}

public actual fun nextSetBit(startIndex: Int): Int {
if (startIndex < 0) {
throw IndexOutOfBoundsException("fromIndex < 0: $startIndex")
}

if (startIndex >= wrapped.size) {
if (startIndex >= bits.size) {
return -1
}

for (n in startIndex..<wrapped.size) {
@Suppress("SimplifyBooleanWithConstants")
if (wrapped[n] == true) {
return n
for (i in startIndex..<bits.size) {
if (bits[i] == true) {
return i
}
}

return -1
}

public actual fun or(another: BitSet) {
for (i in 0..<another.wrapped.size) {
@Suppress("SimplifyBooleanWithConstants")
val result = wrapped[i] == true || another.wrapped[i] == true
for (i in 0..<another.bits.size) {
val result = bits[i] == true || another.bits[i] == true

// This check avoids setting a "false" boolean value,
// as we want to keep the "undefined" slots
if (result) {
wrapped[i] = true
bits[i] = true
}
}
}

override fun equals(other: Any?): Boolean =
this === other || other is BitSet && wrapped.contentEquals(other.wrapped)
this === other || other is BitSet && contentEquals(bits, other.bits)

override fun hashCode(): Int {
val fqn = "com.strumenta.antlrkotlin.runtime.BitSet"
return MurmurHash.hashCode(wrapped, fqn.hashCode())
var hashCode = MurmurHash.initialize(fqn.hashCode())
var on = 0

for (i in bits.indices) {
if (bits[i] == true) {
hashCode = MurmurHash.update(hashCode, i)
on++
}
}

return MurmurHash.finish(hashCode, on)
}

override fun toString(): String {
Expand All @@ -103,4 +127,39 @@ public actual class BitSet {
sb.append("}")
return sb.toString()
}

private fun contentEquals(one: Array<Boolean>, two: Array<Boolean>): Boolean {
val lastIndexOne = lastBitSetIndex(one)
val lastIndexTwo = lastBitSetIndex(two)

if (lastIndexOne != lastIndexTwo) {
return false
}

for (i in 0..lastIndexOne) {
if (one[i] != two[i]) {
return false
}
}

return true
}

private fun lastBitSetIndex(bits: Array<Boolean>): Int {
var i = bits.size - 1

while (i > -1) {
if (bits[i] == true) {
return i
}

i--
}

return -1
}

@Suppress("UNUSED_PARAMETER")
private fun newArray(size: Int): Array<Boolean> =
js("Array(size)").unsafeCast<Array<Boolean>>()
}

0 comments on commit 584ffd8

Please sign in to comment.