Search code examples
scalamultidimensional-arrayinitialization

Initializing a 2D (multi-dimensional) array in Scala


It's easy to initialize a 2D array (or, in fact, any multidimensional array) in Java by putting something like that:

int[][] x = new int[][] {
        { 3, 5, 7, },
        { 0, 4, 9, },
        { 1, 8, 6, },
};

It's easy to read, it resembles a 2D matrix, etc, etc.

But how do I do that in Scala?

The best I could come up with looks, well, much less concise:

val x = Array(
    Array(3, 5, 7),
    Array(0, 4, 9),
    Array(1, 8, 6)
)

The problems I see here:

  • It repeats "Array" over and over again (like there could be anything else besides Array)
  • It requires to omit trailing , in every Array invocation
  • If I screw up and insert something besides Array() in the middle of array, it will go okay with compiler, but type of x would silently become Array[Any] instead of Array[Array[Int]]:

    val x = Array(
        Array(3, 5, 7),
        Array(0, 4), 9, // <= OK with compiler, silently ruins x
        Array(1, 8, 6)
    )
    

    There is a guard against it, to specify the type directly, but it looks even more overkill than in Java:

    val x: Array[Array[Int]] = Array(
        Array(3, 5, 7),
        Array(0, 4), 9, // <= this one would trigger a compiler error
        Array(1, 8, 6)
    )
    

    This last example needs Array even 3 times more than I have to say int[][] in Java.

Is there any clear way around this?


Solution

  • I suggest to use Scala 2.10 and macros:

    object MatrixMacro {
    
      import language.experimental.macros
    
      import scala.reflect.macros.Context
      import scala.util.Try
    
      implicit class MatrixContext(sc: StringContext) {
        def matrix(): Array[Array[Int]] = macro matrixImpl
      }
    
      def matrixImpl(c: Context)(): c.Expr[Array[Array[Int]]] = {
        import c.universe.{ Try => _, _ }
    
        val matrix = Try {
          c.prefix.tree match {
            case Apply(_, List(Apply(_, List(Literal(Constant(raw: String)))))) =>
    
              def toArrayAST(c: List[TermTree]) =
                Apply(Select(Select(Ident("scala"), newTermName("Array")), newTermName("apply")), c)
    
              val matrix = raw split "\n" map (_.trim) filter (_.nonEmpty) map {
                _ split "," map (_.trim.toInt)
              }
              if (matrix.map(_.length).distinct.size != 1)
                c.abort(c.enclosingPosition, "rows of matrix do not have the same length")
    
              val matrixAST = matrix map (_ map (i => Literal(Constant(i)))) map (i => toArrayAST(i.toList))
    
              toArrayAST(matrixAST.toList)
          }
        }
    
        c.Expr(matrix getOrElse c.abort(c.enclosingPosition, "not a matrix of Int"))
      }
    
    }
    

    Usage with:

    scala> import MatrixMacro._
    import MatrixMacro._
    
    scala> matrix"1"
    res86: Array[Array[Int]] = Array(Array(1))
    
    scala> matrix"1,2,3"
    res87: Array[Array[Int]] = Array(Array(1, 2, 3))
    
    scala> matrix"""
         |   1, 2, 3
         |   4, 5, 6
         |   7, 8, 9
         | """
    res88: Array[Array[Int]] = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9))
    
    scala> matrix"""
         |   1, 2
         |   1
         | """
    <console>:57: error: rows of matrix do not have the same length
    matrix"""
    ^
    
    scala> matrix"a"
    <console>:57: error: not a matrix of Int
                  matrix"a"
                  ^
    

    I don't think you will get it shorter. ;)