Search code examples
scalasetbitset

BitSet to Set[Int] or vice versa in Scala


I have a library that uses BitSet, and I need to change Set[Int] type data to use the library.

What comes to mind is to use .toSeq:_* operations, but I'm not sure this is efficient way to convert from BitSet to Set[Int] or the other way round.

scala> BitSet(Set(1,2,3).toSeq:_*)
res55: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> Set(BitSet(1,2,3).toSeq:_*)
res56: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

Is there a better way?


Solution

  • Scala's Set hierarchy is a little weird, but it is the case that BitSet has Set[Int] as a super type, so you can simply pass a BitSet to a method that expects a Set[Int], etc.

    This might not seem to be the case because the Set you get by default is immutable.Set (under scala.collection), while it's possible that the library you're using is working with the BitSet directly under scala.collection, not immutable.BitSet. This isn't the case in your example code, where everything is in immutable, but I'm not sure how simplified that is.

    If you're lucky enough to be working with the immutable package's versions of both Set and BitSet, the BitSet to Set direction is trivial:

    scala> import scala.collection.immutable.BitSet
    import scala.collection.immutable.BitSet
    
    scala> val bs = BitSet(0, 100, 200)
    bs: scala.collection.immutable.BitSet = BitSet(0, 100, 200)
    
    scala> def takesSet(s: Set[Int]): Int = s.size
    takesSet: (s: Set[Int])Int
    
    scala> takesSet(bs)
    res0: Int = 3
    

    If somehow you've got a scala.collection.BitSet, just use toSet:

    scala> takesSet(scala.collection.BitSet(0, 100, 200).toSet)
    res1: Int = 3
    

    For the other direction, your version is fine:

    scala> val s = Set(1, 2, 3)
    s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
    
    scala> BitSet(s.toSeq: _*)
    res2: scala.collection.immutable.BitSet = BitSet(1, 2, 3)
    

    But it's also worth noting that in many cases you can avoid this conversion with some CanBuildFrom magic:

    scala> val bs: BitSet = Set("1", "2", "3").map(_.toInt)(collection.breakOut)
    bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)
    

    This produces the same result as the following:

    scala> val bs: BitSet = BitSet(Set("1", "2", "3").map(_.toInt).toSeq: _*)
    bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)
    

    But instead of constructing an intermediate set (and sequence) before the BitSet, the collection.breakOut parameter tells the compiler to perform the map using an instance of the CanBuildFrom type class that will construct the BitSet directly. Passing a CanBuildFrom instance explicitly in this way isn't just for map, either—it'll work with flatMap, scanLeft, ++, and other collections operations that allow you to change the element type.