Scala collection: totally unpredictable behaviours
Have been pretty frustrated by Scala 2.8 collection behaviours. Here's the problem: I'm creating a Sudoku board. I'm labelling the cells from A1 to I9 (the letters being the rows and the digits being the columns). I want to get a list of units on the board, which is the 9 rows, the night columns, and the night quadrants.
Here's my scala class:
class Square(val row:Char, val column:Int) extends Pair[Char, Int](row, column) {
override def toString() = "" + row + column
}
object Board {
private val rows = "ABCDEFGHI"
private val cols = 1 to 9
private lazy val units = unitList(rows, cols)
private def cross(rows:Iterable[Char], columns:Iterable[Int]):Iterable[S开发者_高级运维quare] = {
for (row <- rows; col <- columns)
yield new Square(row, col)
}
private def unitList(rows:Iterable[Char], cols:Iterable[Int]) = {
val u1 = (for (col <- cols) yield cross(rows, List(col)))
val u2 = (for (row <- rows) yield cross(List(row), cols))
val u3 = (for (cols <- List("ABC", "DEF", "GHI"); rows <- List(1 to 3, 4 to 6, 7 to 9)) yield cross(cols, rows))
u1 :+ u2 :+ u3 // won't compile, reason: :+ is not a member of Iterable[Iterable[sudoku.Square]]
}
def run() {
val u1 = (for (col <- cols) yield cross(rows, List(col)))
val u2 = (for (row <- rows) yield cross(List(row), cols))
val u3 = (for (cols <- List("ABC", "DEF", "GHI"); rows <- List(1 to 3, 4 to 6, 7 to 9)) yield cross(cols, rows))
println(u1)
println(u2)
println(u3)
val u4 = u1 :+ u2 :+ u3 // compiles
println(u1 :+ u2 :+ u3) // compiles and output correctly
}
}
See the comments in the code. Specifically, why the same code won't compile in unitList but compiles and runs fine in run()?
Also, when I observe the output of the run method, it seems the collection returned by the yield keyword is randomly switching between Vector and List:
Vector(Vector(A1, B1, C1, D1, E1, F1, G1, H1, I1), Vector(A2, B2, C2, D2, E2, F2, G2, H2, I2), Vector(A3, B3, C3, D3, E3, F3, G3, H3, I3), Vector(A4, B4, C4, D4, E4, F4, G4, H4, I4), Vector(A5, B5, C5, D5, E5, F5, G5, H5, I5), Vector(A6, B6, C6, D6, E6, F6, G6, H6, I6), Vector(A7, B7, C7, D7, E7, F7, G7, H7, I7), Vector(A8, B8, C8, D8, E8, F8, G8, H8, I8), Vector(A9, B9, C9, D9, E9, F9, G9, H9, I9))
Vector(List(A1, A2, A3, A4, A5, A6, A7, A8, A9), List(B1, B2, B3, B4, B5, B6, B7, B8, B9), List(C1, C2, C3, C4, C5, C6, C7, C8, C9), List(D1, D2, D3, D4, D5, D6, D7, D8, D9), List(E1, E2, E3, E4, E5, E6, E7, E8, E9), List(F1, F2, F3, F4, F5, F6, F7, F8, F9), List(G1, G2, G3, G4, G5, G6, G7, G8, G9), List(H1, H2, H3, H4, H5, H6, H7, H8, H9), List(I1, I2, I3, I4, I5, I6, I7, I8, I9))
List(Vector(A1, A2, A3, B1, B2, B3, C1, C2, C3), Vector(A4, A5, A6, B4, B5, B6, C4, C5, C6), Vector(A7, A8, A9, B7, B8, B9, C7, C8, C9), Vector(D1, D2, D3, E1, E2, E3, F1, F2, F3), Vector(D4, D5, D6, E4, E5, E6, F4, F5, F6), Vector(D7, D8, D9, E7, E8, E9, F7, F8, F9), Vector(G1, G2, G3, H1, H2, H3, I1, I2, I3), Vector(G4, G5, G6, H4, H5, H6, I4, I5, I6), Vector(G7, G8, G9, H7, H8, H9, I7, I8, I9))
Vector(Vector(A1, B1, C1, D1, E1, F1, G1, H1, I1), Vector(A2, B2, C2, D2, E2, F2, G2, H2, I2), Vector(A3, B3, C3, D3, E3, F3, G3, H3, I3), Vector(A4, B4, C4, D4, E4, F4, G4, H4, I4), Vector(A5, B5, C5, D5, E5, F5, G5, H5, I5), Vector(A6, B6, C6, D6, E6, F6, G6, H6, I6), Vector(A7, B7, C7, D7, E7, F7, G7, H7, I7), Vector(A8, B8, C8, D8, E8, F8, G8, H8, I8), Vector(A9, B9, C9, D9, E9, F9, G9, H9, I9), Vector(List(A1, A2, A3, A4, A5, A6, A7, A8, A9), List(B1, B2, B3, B4, B5, B6, B7, B8, B9), List(C1, C2, C3, C4, C5, C6, C7, C8, C9), List(D1, D2, D3, D4, D5, D6, D7, D8, D9), List(E1, E2, E3, E4, E5, E6, E7, E8, E9), List(F1, F2, F3, F4, F5, F6, F7, F8, F9), List(G1, G2, G3, G4, G5, G6, G7, G8, G9), List(H1, H2, H3, H4, H5, H6, H7, H8, H9), List(I1, I2, I3, I4, I5, I6, I7, I8, I9)), List(Vector(A1, A2, A3, B1, B2, B3, C1, C2, C3), Vector(A4, A5, A6, B4, B5, B6, C4, C5, C6), Vector(A7, A8, A9, B7, B8, B9, C7, C8, C9), Vector(D1, D2, D3, E1, E2, E3, F1, F2, F3), Vector(D4, D5, D6, E4, E5, E6, F4, F5, F6), Vector(D7, D8, D9, E7, E8, E9, F7, F8, F9), Vector(G1, G2, G3, H1, H2, H3, I1, I2, I3), Vector(G4, G5, G6, H4, H5, H6, I4, I5, I6), Vector(G7, G8, G9, H7, H8, H9, I7, I8, I9)))
I'm totally at lost here.
The result of the yield in the for comprehension is derived from the type of the first generator, in you method, you're constraining the types of your parameters so far you lose your :+ methods.
//The type of 1 to 9 is show below
scala> 1 to 9
res0: scala.collection.immutable.Range.Inclusive with scala.collection.immutable.Range.ByOne = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
//If you cast it to Iterable[Int] it doens't have the :+ method
scala> (res0:Iterable[Int]) :+ 1
<console>:7: error: value :+ is not a member of Iterable[Int]
(res0:Iterable[Int]) :+ 1
^
//But if you don't, you have it
scala> res0 :+ 1
res6: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 1)
//And to prove that for comprehensions yield derives the type of the first generator:
scala> for(a <- res0) yield a
res7: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> for(a <- (res0:Iterable[Int])) yield a
res8: Iterable[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9)
Notwithstanding the really weird behavior, :+
cannot possibly be the operator you want. Since you did not annotate the return type of unitList
, I don't know what you expected. I assume you wanted to return either Iterable[Square]
or Iterable[Iterable[Square]]
. So let's see how you can get them, and why :+
is incorrect.
First of all, u1
, u2
and u3
are all Iterable[Iterable[Square]]
, though the exact subtype varies. This should be easy to understand: cross
returns Iterable[Square]
, so yielding cross
in a for-comprehension results in an Iterable[Iterable[Square]]
.
Next, Let's consider :+
. This method adds an element to a collection, so, if u1
is Iterable(a, b, c)
, where a, b and c are Iterable[Square]
, then u1 :+ u2
is Iterable(a, b, c, u2)
, and its type becomes Iterable[X]
, where X
is the unification of Iterable[Square]
(the type of a, b and c) and Iterable[Iterable[Square]]
(the type of u2
). The end result is an Iterable[Iterable[AnyRef]]
.
Since the type of u1
, u2
and u3
are essentially the same, the correct operation in all likelyhood is this:
u1 ++ u2 ++ u3
Which will return Iterable[Iterable[Square]]
. Now, if you want to remove the nesting and return Iterable[Square]
, you can flatten this:
(u1 ++ u2 ++ u3).flatten
One of these two things is probably what you want.
Now, as for the "random" switching, there's nothing random about it. In each case, there are two for-comprehensions, and the actual implementation of the resulting collection depends on the implementation of the original collection. So, let's consider it:
- u1: outer type derives from
Range
, inner type fromString
(first parameter to cross) - u2: outer type derives from
String
, inner type fromList
(first parameter to cross) - u3: outer type derives from
List
, inner type derives fromString
(first parameter to cross)
So it can be easily deduced that for-comprehensions over String
(WrappedString
, actually) and Range
result in Vector
, while for-comprehensions over List
result in List
.
The compiler error occurs because :+
is a member of IndexedSeq
(and thus List
) but not of Iterable
. If you change the return value of unitList
from
u1 :+ u2 :+ u3
to
List(u1) :+ u2 :+ u3
it compiles just fine.
精彩评论