Skip to content
Pyrula

ISet

ISet is a persistent immutable set. Every mutating operation returns a new ISet; the original is unchanged. Structural sharing in the underlying HAMT means that copies share most of their memory.

from pyrula import ISet
s = ISet([1, 2, 3]) # from any iterable
s2 = ISet([1, 1, 2, 2, 3]) # duplicates silently dropped
s2.size() # 3
empty = ISet() # no argument or ISet(None)
empty.is_empty() # True

add and remove always return a new ISet. The original is not modified.

s = ISet([1, 2, 3])
s2 = s.add(4)
s.size() # 3 -- unchanged
s2.size() # 4
s3 = s2.remove(4)
s3 == s # True

remove is a no-op for elements that are not present.

s = ISet([1, 2, 3])
s.contains(2) # True
2 in s # True (__contains__)
len(s) # 3
a = ISet([1, 2, 3])
b = ISet([2, 3, 4])
a.union(b) # ISet({1, 2, 3, 4})
a.intersection(b) # ISet({2, 3})
a.difference(b) # ISet({1})
ISet([1, 2]).is_subset_of(a) # True

Both return a new ISet. map may change the element type; the result is still deduplicated by Python equality.

s = ISet([1, 2, 3, 4])
s.filter(lambda x: x % 2 == 0) # ISet({2, 4})
s.map(lambda x: x * 10) # ISet({10, 20, 30, 40})
s = ISet([1, 2, 3])
s.to_set() # standard Python set {1, 2, 3}
s.to_ilist() # IList (order unspecified -- hash-set iteration order)

to_ilist is useful when you need a sequenced view for further functional chaining (map, fold_left, etc.).

s = ISet([1, 2, 3])
for x in s: # __iter__ yields elements in unspecified order
print(x)
ISet([3, 1, 2]) == ISet([1, 2, 3]) # True -- order-independent equality

ISet mirrors Scala’s scala.collection.immutable.Set. The core operations map directly:

PyrulaScala
ISet([1, 2, 3])Set(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.is_subset_of(t)s.subsetOf(t)
s.map(f)s.map(f)
s.filter(p)s.filter(p)

Scala’s Set is also backed by a HAMT for sizes above 4, giving the same O(log32 n) characteristics for add, remove, and lookup.