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

后端 未结 6 1434
深忆病人
深忆病人 2020-12-24 02:13

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,          


        
相关标签:
6条回答
  • 2020-12-24 02:42

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

    0 讨论(0)
  • 2020-12-24 02:44

    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 :)

    0 讨论(0)
  • 2020-12-24 02:47

    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))
    
    0 讨论(0)
  • 2020-12-24 02:49

    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]] = ...
    
    0 讨论(0)
  • 2020-12-24 02:55

    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.

    0 讨论(0)
  • 2020-12-24 03:05

    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.

    0 讨论(0)
提交回复
热议问题