Skip to content
Pyrula

IntSet

IntSet is an immutable set specialized for non-negative integers. It is backed by a Roaring bitmap, so union, intersection, and difference run entirely in Rust over compressed bitmaps — there is no per-element Python loop and no GIL contention during the operation. Reach for it when you are combining large sets of integer IDs and a regular ISet of boxed ints is the bottleneck.

The same engine sits underneath analytics systems like Druid, Lucene, and ClickHouse for exactly this reason: bulk set algebra over integer IDs.

from pyrula import IntSet
s = IntSet([1, 2, 3, 3, 2]) # from any iterable of ints; duplicates dropped
len(s) # 3
s.contains(2) # True
2 in s # True (__contains__)
IntSet().is_empty() # True

Values must be non-negative and fit in a 64-bit unsigned integer. Negative values, values above 2**64 - 1, and non-integers raise ValueError / TypeError.

add and remove always return a new IntSet; the original is unchanged. remove is a no-op for absent values.

s = IntSet([1, 2])
s.add(3).to_list() # [1, 2, 3]
s.to_list() # [1, 2] -- unchanged

This is the fast path. Each operation runs over compressed bitmaps in Rust.

a = IntSet([1, 2, 3, 4])
b = IntSet([3, 4, 5])
a.union(b) # IntSet([1, 2, 3, 4, 5])
a.intersection(b) # IntSet([3, 4])
a.difference(b) # IntSet([1, 2])
a.symmetric_difference(b) # IntSet([1, 2, 5])
IntSet([3, 4]).is_subset(a) # True
IntSet([7, 8]).is_disjoint(a) # True
a.cardinality() # 4

to_list returns a plain Python list[int] in ascending order.

to_bytes writes the set in the Roaring portable byte format; from_bytes reads it back. The bytes are compact (the bitmap is already compressed) and interoperable with other Roaring implementations, which makes IntSet usable as durable checkpoint state — for example, a persisted “seen” set on an exactly-once path.

s = IntSet([1, 5, 9, 2**40])
blob = s.to_bytes() # bytes, Roaring portable format
IntSet.from_bytes(blob).to_list() # [1, 5, 9, 1099511627776]

IntSet shines when the integers are dense or clustered — entity IDs, row offsets, feature flags, graph adjacency — where the Roaring bitmap compresses well and set algebra is hundreds to thousands of times faster than a Python set.

For uniformly random integers spread across the full 64-bit range, the bitmap cannot compress and Roaring is the wrong tool — a plain set or ISet will be faster. Real ID sets almost always cluster, which is the case IntSet is built for.

IntSet mirrors the role of Scala’s scala.collection.immutable.BitSet — an immutable set specialized for non-negative integers with compact storage and fast bulk operations.

PyrulaScala
IntSet([1, 2, 3])BitSet(1, 2, 3)
s.add(x)s + x
s.remove(x)s - x
s.contains(x)s.contains(x)
s.union(t)s ++ t / s.union(t)
s.intersection(t)s.intersect(t)
s.difference(t)s.diff(t)
s.symmetric_difference(t)s.xor(t)
s.is_subset(t)s.subsetOf(t)