开发者

Different behavior when declaration type is different(Set vs TreeSet)

    var set = TreeSet(5,4,3,2,1)
    println(set)

    val diffSet: TreeSet[Int] = set
    // if I change above code to val diffSet: Set[Int] = set
    // the result is unsorted set.

    for (i <- diffSet; x = i) {
        println(i)
    }
    println("-" * 20)
    // the above code translates to below and print the same result
    val temp = diffSet.map(i => (i, i))
    for ((i, x) <- temp) {
        println(i)
    }

My question is if I defined a method like this:

def genSet:Set[Int] = {
  TreeSet(5, 4, 3, 2, 1)
}

and when i want to use a for loop with it

for (i <- genSet; x = i + 1) {
  println(x)
}
开发者_开发问答

the result is unsorted, how to fix this behavior without change the genSet's return type. if I use for loop like below, it will be fine, but I hope to keep the above code style.

for (i <- genSet) {
  val x = i + 1
  println(x)
}


Why the map version winds up unsorted

The map method (called with a function that we'll call func) takes an implicit CanBuildFrom parameter that takes into account the type of the collection that map is being called on, in addition to the type that func returns to choose an appropriate return type. This is used to make Map.map[Int] or BitSet.map[String] do the right thing (return general purpose lists) while Map.map[(String,Int)] or BitSet.map[Int] also do the right thing (return a Map and a BitSet) respectively.

The CanBuildFrom is chosen at compile time, so it must be chosen based on the static type of the set that you call map on (the type the compiler knows about at compile time). The static type of set is TreeSet, but the static type of diffset is Set. The dynamic type of both (at runtime) is TreeSet.

When you call map on set (a TreeSet), the compiler chooses immutable.this.SortedSet.canBuildFrom[Int](math.this.Ordering.Int) as the CanBuildFrom.

When you call map on diffset (a Set), the compiler chooses immutable.this.Set.canBuildFrom[Int] as the CanBuildFrom.

Why the for version winds up unsorted

The loop

for (i <- genSet; x = i + 1) {
  println(x)
}

desugars into

genSet.map(((i) => {
              val x = i.$plus(1);
              scala.Tuple2(i, x)
            })).foreach(((x$1) => x$1: @scala.unchecked match {
              case scala.Tuple2((i @ _), (x @ _)) => println(x)
            }))

The desugared version includes a map function which will use the unsorted CanBuildFrom as I explained above.

On the other hand, the loop

for (i <- genSet) {
  val x = i + 1
  println(x)
}

desugars into

genSet.foreach(((i) => {
              val x = i.$plus(1);
              println(x)
            }))

Which doesn't use a CanBuildFrom at all, since no new collection is being returned.


Set does not guarantee ordering. Even if the underlying class is a TreeSet, if the expected result is a Set you'll loose the ordering in the first transformation you do.

If you want ordering, do not use Set. I suggest, say, SortedSet.


Change the sig of genSet to return a SortedSet

def genSet:SortedSet[Int] = {
  TreeSet(5, 4, 3, 2, 1)
}

This is probably some sort of bug. I would have expected your code to work too.

I think map is the culprit. This results in the same behavior:

for (i <- genSet.map(_ + 1)) { println(i) }

And for(i <- genSet; x = i + 1) equates to for(x <- genSet.map({i => i + 1}))


You can do:

scala> for (i <-genSet.view; x = i + 1) println(x)
2
3
4
5
6

Although, it's the type of trick that when you look at it after a few months, you may wonder why you added .view ...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜