26

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?

GreyCat
  • 16,622
  • 18
  • 74
  • 112
  • 2
    Note that `List` in Scala is a linked list, not an array-like data structure. – Jesper Dec 13 '12 at 15:22
  • 2
    Is this what you are looking to do: http://stackoverflow.com/questions/2381908/how-to-create-and-use-a-multi-dimensional-array-in-scala-2-8 – jcern Dec 13 '12 at 15:25
  • @jcern: Nah, it just specifies what I've already cited above. I thought of a more elegant method. – GreyCat Dec 13 '12 at 15:36
  • @Jesper: you're right, thanks, I'll fix the examples in question now – GreyCat Dec 13 '12 at 15:37
  • I think the difference is that, in Java, arrays are part of the language and therefore provide a more concise syntax for writing literals. In Scala, both Arrays and Lists are not part of the language but of the standard library. Any syntactic sugar is not part of the language itself, but provided by the designer of the library. – Eduardo Dec 13 '12 at 15:39
  • You have 4 extra commas in your Java code (I tried to edit myself but, since it is a small change, it didn't let me) – Eduardo Dec 13 '12 at 15:44
  • 1
    @Eduardo: These commas are *intended* to be there - please read my question more carefully. In fact, I like them and I want to be something just as easy-to-handle for an container initializer. – GreyCat Dec 13 '12 at 15:48

6 Answers6

16

Personally I'd suck it up and type out (or cut and paste) "Array" a few times for clarity's sake. Include the type annotation for safety, of course. But if you're really running out of e-ink, a quick easy hack would be simply to provide an alias for Array, for example:

val > = Array

val x: Array[Array[Int]] = >(
  >(3, 5, 7),
  >(0, 4, 9),
  >(1, 8, 6)
)

You could also provide a type alias for Array if you want to shorten the annotation:

type >[T] = Array[T]

val x: >[>[Int]] = ...
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 3
    I was going to downvote on basis of "I'd suck it up" .. since I would NOT want to settle for the vanilla syntax. But then your type alias's more than made up for it and so you received an upvote instead ;) – WestCoastProjects Feb 15 '14 at 22:29
  • Your syntax is not working in the repl at least: :8: error: not found: value > val x: >[>[Int]] = >( – WestCoastProjects Feb 15 '14 at 22:32
  • 1
    The syntax works fine on my REPL, had you defined val > = Array ? – gsimard Aug 09 '14 at 13:36
  • I tried again and it is working now. Too long ago to remember if there were any differences in the way I applied. I believe had cut and paste. – WestCoastProjects Jan 09 '15 at 21:50
  • Oh I got it. This syntax IS working in the REPL, but does not work in a normal Scala class. Is it possible to get working in a real program? The error is "not found: value >" – WestCoastProjects Jan 09 '15 at 22:05
13

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. ;)

kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • In general, parsing the matrix from an input string is always a possible solution, even without macros. I'm not yet familiar with 2.10 and macros. Did I get it right that, by using macros, the checks on the input string happen at compile time instead of run time? – bluenote10 Dec 14 '12 at 08:45
  • 1
    @bluenote10: Yes, that is correct. Handling it with a normal string would mean that you must wait until execution of the program to see if you typed the matrix correct. – kiritsuku Dec 14 '12 at 14:41
  • ... which makes this a superb solution :) and a very nice example of macros as well. – bluenote10 Dec 14 '12 at 14:57
  • What if you need to use variables instead of hard-coded numbers ? Won't it all of a sudden bloat the code again ? – gsimard Aug 09 '14 at 13:34
  • 1
    @gsimard: string interpolation allows the addition of variables. And you can add as much whitespace as you want if it is your goal to align all rows. – kiritsuku Aug 09 '14 at 14:35
  • Right, I had missed string interpolation. – gsimard Aug 09 '14 at 15:09
5

If using a mere List of List (which in itself cannot guarantee that every sub list is of the same size) is not a problem for you, and you are only concerned with easy syntax and avoiding errors at creation-time, scala has many ways to create nice syntax constructs.

One such possibility would be a simple helper:

object Matrix {
  def apply[X]( elements: Tuple3[X, X, X]* ): List[List[X]] = {
    elements.toList.map(_.productIterator.toList.asInstanceOf[List[X]] )
  }
  // Here you might add other overloads for Tuple4, Tuple5 etc if you need "matrixes" of those sizes
}

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

About your concerns:

It repeats "List" over and over again (like there could be anything else besides List)

Not the case here.

It requires to omit trailing , in every List invocation

Unfortunately that is still true here, not much you can do given scala's syntactic rules.

If I screw up and insert something besides List() in the middle of array, it will go okay with compiler, but type of x would silently become List[Any] instead of List[List[Int]]:

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

The equivalent code now faile to compile:

scala> val x = Matrix(
     |   (3, 5, 7),
     |   (0, 4), 9,
     |   (1, 8, 6)
     | )
<console>:10: error: type mismatch;
 found   : (Int, Int)
 required: (?, ?, ?)
         (0, 4), 9,

And finally if you want to explicitly specify the type of elements (say that you want to protect against the possibility of inadvertently mixing Ints and Doubles), you only have to specify Matrix[Int] instead of the ugly List[List[Int]]:

val x = Matrix[Int](
  (3, 5, 7),
  (0, 4, 9),
  (1, 8, 6)
)

EDIT: I see that you replaced List with Array in your question. To use arrays all you have to use is to replace List with Array and toList with toArray in my code above.

Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
  • 2
    It's possible to generalize your solution for TupleN using `shapeless`. – senia Dec 13 '12 at 18:19
  • I was actually going to mention shapeless :). I only refrained from doing so because I have personnaly zero experience with it (yet), and was only 95% sure that it allowed to abstract over product arity. +1 for confirming it. – Régis Jean-Gilles Dec 13 '12 at 18:27
3

Since I'm also in disgust with this trailing comma issue (i.e. I cannot simply exchange the last line with any other) I sometimes use either a fluent API or the constructor syntax trick to get the syntax I like. An example using the constructor syntax would be:

trait Matrix {
  // ... and the beast
  private val buffer = ArrayBuffer[Array[Int]]()
  def >(vals: Int*) = buffer += vals.toArray
  def build: Array[Array[Int]] = buffer.toArray
}

Which allows:

// beauty ... 
val m = new Matrix {
  >(1, 2, 3)
  >(4, 5, 6)
  >(7, 8, 9)
} build

Unfortunately, this relies on mutable data although it is only used temporarily during the construction. In cases where I want maximal beauty for the construction syntax I would prefer this solution.

In case build is too long/verbose you might want to replace it by an empty apply function.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
1

I don't know if this is the easy way, but I've included some code below for converting nested tuples into '2D' arrays.

First, you need some boiler plate for getting the size of the tuples as well as converting the tuples into [Array[Array[Double]]. The series of steps I used were:

  1. Figure out the number of rows and columns in the tuple
  2. Turn the nested tuple into a one row Array
  3. Reshape the array based on the size of the original tuple.

The code for that is:

object Matrix {

    /**
     * Returns the size of a series of nested tuples. 
     */
    def productSize(t: Product): (Int, Int) = {
        val a = t.productArity
        val one = t.productElement(0)
        if (one.isInstanceOf[Product]) {
            val b = one.asInstanceOf[Product].productArity
            (a,  b)
        }
        else {
            (1, a)
        }
    }

    /**
     * Flattens out a nested tuple and returns the contents as an iterator. 
     */
    def flattenProduct(t: Product): Iterator[Any] = t.productIterator.flatMap {
        case p: Product => flattenProduct(p)
        case x => Iterator(x)
    }

    /**
     * Convert a nested tuple to a flattened row-oriented array.
     * Usage is:
     * {{{
     *  val t = ((1, 2, 3), (4, 5, 6))
     *  val a = Matrix.toArray(t)
     *  // a: Array[Double] = Array(1, 2, 3, 4, 5, 6)
     * }}}
     *
     * @param t The tuple to convert to an array
     */
    def toArray(t: Product): Array[Double] = flattenProduct(t).map(v =>
        v match {
            case c: Char => c.toDouble
            case b: Byte => b.toDouble
            case sh: Short => sh.toDouble
            case i: Int => i.toDouble
            case l: Long => l.toDouble
            case f: Float => f.toDouble
            case d: Double => d
            case s: String => s.toDouble
            case _ => Double.NaN
        }

    ).toArray[Double]

    def rowArrayTo2DArray[@specialized(Int, Long, Float, Double) A: Numeric](m: Int, n: Int,
            rowArray: Array[A]) = {
        require(rowArray.size == m * n)
        val numeric = implicitly[Numeric[A]]
        val newArray = Array.ofDim[Double](m, n)
        for (i <- 0 until m; j <- 0 until n) {
            val idx = i * n + j
            newArray(i)(j) = numeric.toDouble(rowArray(idx))
        }
        newArray
    }

    /**
     * Factory method for turning tuples into 2D arrays
     */
    def apply(data: Product): Array[Array[Double]] = {
        def size = productSize(data)
        def array = toArray(data)
        rowArrayTo2DArray(size._1, size._2, array)
    }

}

Now to use this, you could just do the following:

val a  = Matrix((1, 2, 3))
// a: Array[Array[Double]] = Array(Array(1.0, 2.0, 3.0))

val b = Matrix(((1, 2, 3), (4, 5, 6), (7, 8, 9)))
// b: Array[Array[Double]] = Array(Array(1.0, 2.0, 3.0), 
//                                 Array(4.0, 5.0, 6.0), 
//                                 Array(7.0, 8.0, 9.0))

val c = Matrix((1L, 2F, "3"))    // Correctly handles mixed types
// c: Array[Array[Double]] = Array(Array(1.0, 2.0, 3.0))

val d = Matrix((1L, 2F, new java.util.Date())) // Non-numeric types convert to NaN
// d: Array[Array[Double]] = Array(Array(1.0, 2.0, NaN))

Alternatively, if you could just call the rowArrayTo2DArray directly using the size of the array you want and a 1D array of values:

val e = Matrix.rowArrayTo2DArray(1, 3, Array(1, 2, 3))
// e: Array[Array[Double]] = Array(Array(1.0, 2.0, 3.0))

val f = Matrix.rowArrayTo2DArray(3, 1, Array(1, 2, 3))
// f: Array[Array[Double]] = Array(Array(1.0), Array(2.0), Array(3.0))

val g = Matrix.rowArrayTo2DArray(3, 3, Array(1, 2, 3, 4, 5, 6, 7, 8, 9))
// g: Array[Array[Double]] = Array(Array(1.0, 2.0, 3.0), 
//                                 Array(4.0, 5.0, 6.0), 
//                                 Array(7.0, 8.0, 9.0))
hohonuuli
  • 1,974
  • 15
  • 15
  • One problem I can see with this approach is that you can still pass any kind of tuple (by example a Tuple2[SomeType, AnotherType]), which is clearly not good and will compile fine (only to blow at runtime) – Régis Jean-Gilles Dec 13 '12 at 18:02
  • @Régis Jean-Gilles: 'Tis true ... Product doesn't expose the type of it's content. One could, however, just use the `rowArrayTo2DArray` method directly, which is type safe. I added an example to illustrate that. – hohonuuli Dec 13 '12 at 18:17
  • 2
    @Régis Jean-Gilles: Also, to prevent runtime blowups, one could change the line `case _ => throw new UnsupportedOperationException(...` to `case _ => Double.NaN` which would prevent blowups and provide a 'correct' way to handle non-numeric values. I'm going to change the code to reflect that. – hohonuuli Dec 13 '12 at 18:22
1

glancing through the answers, i did not find what to me seems the most obvious & simple way to do it. instead of Array, you can use a tuple.
would look something like that:

scala> val x = {(
     | (3,5,7),
     | (0,4,9),
     | (1,8,6)
     | )}

x: ((Int, Int, Int), (Int, Int, Int), (Int, Int, Int)) = ((3,5,7),(0,4,9),(1,8,6))

seems clean & elegant?
i think so :)

gilad hoch
  • 2,846
  • 2
  • 33
  • 57
  • 1
    There are a few problems with this approach: (1) iteration over rows/columns is less elegant using tuples. (2) Updating an entry in a fixed position of the matrix is cumbersome. (3) You definitely don't ever want to type that whole signature. – bluenote10 Dec 17 '12 at 09:43
  • agreed. well, it all depends on the use-case... if all you need is a simple storing of the values, without much updating, this could be quite good. moreover, if you need `Matrix`, you could write an implicit function that takes this 3X3 tuple structure, and converts it to a `Matrix` -> this way you can enjoy both worlds. elegant initialization, and easy-to-use type. – gilad hoch Dec 17 '12 at 12:55
  • I think the 3x3 array in the question is just one use case. I'm assuming GreyCat was looking for a general solution that could be applied to arbitrary sized 2D arrays. – hohonuuli Dec 18 '12 at 18:16