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
...
精彩评论