Announcing sparse-set v0.4.0: Efficient sparse set data structures for Haskell

Hi there :waving_hand: I wanted to share sparse-set for the first time, an efficient vector-based sparse set for Haskell. This package provides lazy, strict, mutable, and unboxed variants, each wrapping their respective counterparts in vector.

Game engines, especially ones with an ECS, often make heavy use of sparse sets for their efficient iteration and lookup. For example, ECS queries can be done with Data.SparseSet.intersection:

import qualified Data.SparseSet as S

main :: IO ()
main = print $ S.intersection as bs
  where
    as = S.insert (10 :: Int) "B" $ S.insert 0 "A" S.empty
    bs = S.insert (10 :: Int) "B" S.empty
9 Likes

It’s cool that there are mutable and unboxed variants, but it’s unclear where the boxed immutable sets stand relative to the standard (also sparse) sets provided by [unordered-]containers. I would guess that they’re more compactly represented with worse asymptotics for certain operations, but this could do to be documented.

Actually, taking a look at the implementation, it’s using

newtype SparseVector a = SparseVector {unSparseVector :: Vector (Bool, a)}

which, contrary to its name, looks pretty dense to me? I expected something like

data SparseVector a = SparseVector
  { entries :: !(V.Vector a)
  , indices :: !(U.Vector Int) -- Invariant: sorted
  }

If it’s intended that these “sparse” vectors and sets are not sparsely represented but only sparse on some semantic level, then that absolutely merits big warnings somewhere.

2 Likes

Thanks for checking it out!

Hmm that’s an interesting point as far as memory layout goes but I think this is close to the inspirations I got from Bevy and ECS back and forth to have this sparse set structure:

data SparseSet i a = SparseSet
  { dense :: Vector a,
    sparse :: SparseVector i -- Invariant: sorted
  }


(from https://skypjack.github.io/)

EDIT: This will i.e. allocate slots for all elements up to index 1,000 in inserting there, which is good for game engines but probably not typical behavior so I should definitely note that in the docs

As far as the containers packages go I think the closest structures I found were Map or HashMap since this would probably be closer to Maps than Sets

2 Likes

Since your keys are ints, I’d suggest to compare with IntMap.

6 Likes

Yep, I agree with this. Usually a sparse data structure is one that does not store the zeroes/nulls etc.

is there a reasonable union operation on sparse sets? this looks fairly useful for a project I’m working on but I need to be able to union as part of monoidal accumulation. I’ve noticed none of the sparse set libraries I can find provide union so I’m guessing it’s not a sensible operation.

Set is indeed a semigroup, out of the box: https://hackage-content.haskell.org/package/containers-0.8/docs/src/Data.Set.Internal.html#line-301