Initialisation D'un tableau 2D (multidimensionnel) dans Scala

Il est facile d'initialiser un tableau 2D (ou, en fait, n'importe quel tableau multidimensionnel) en Java en mettant quelque chose comme ça:

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

, Il est facile à lire, il ressemble à une matrice 2D, etc, etc.

Mais comment puis-je faire ça à Scala?

Le meilleur que je pouvais trouver des regards, Eh bien, beaucoup moins concis:

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

Les problèmes que je vois ici:

  • Il répète "Array" encore et encore (comme s'il pouvait y avoir autre chose que Array)
  • Il nécessite pour omettre la fin , dans chaque invocation de tableau
  • Si je bousille et insère quelque chose d'autre que Array() au milieu du tableau, cela ira bien avec le compilateur, mais le type de x deviendrait silencieusement Array[Any] au lieu de Array[Array[Int]]:

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

    Il y a une garde contre cela, pour spécifier le type directement, mais il semble encore plus exagéré qu'en 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)
    )
    

    Ce dernier exemple a besoin de Array même 3 fois plus que ce que j'ai à dire int[][] en Java.

Est là un moyen clair de contourner ça?

24
demandé sur GreyCat 2012-12-13 19:01:29

6 réponses

Je suggère D'utiliser Scala 2.10 et 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"))
  }

}

Utilisation avec:

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"
              ^

Je ne pense pas que vous l'obtiendrez plus court. ;)

13
répondu kiritsuku 2012-12-14 15:17:42

Personnellement, je le sucerais et taperais (ou couper et coller)" Array " plusieurs fois pour plus de clarté. Inclure l'annotation de type pour la sécurité, bien sûr. Mais si vous êtes vraiment à court d'e-ink, un hack rapide et facile serait simplement de fournir un alias pour Array, par exemple:

val > = Array

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

Vous pouvez également fournir un alias de type pour Array si vous souhaitez raccourcir l'annotation:

type >[T] = Array[T]

val x: >[>[Int]] = ...
15
répondu Luigi Plinge 2012-12-14 03:25:13

Si l'utilisation d'un simple List de List (qui en soi ne peut garantir que chaque sous-liste est de la même taille) n'est pas un problème pour vous, et vous ne vous souciez que de la syntaxe facile et d'éviter les erreurs au moment de la création, scala a plusieurs façons de créer de belles constructions syntaxiques.

Une telle possibilité serait une aide simple:

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

À propos de vos préoccupations:

Il répète" List " encore et encore (comme s'il pouvait y avoir autre chose que Liste)

Pas le cas ici.

Il faut omettre la fin, dans chaque invocation de liste

Malheureusement, c'est toujours vrai ici, pas grand-chose que vous pouvez faire étant donné les règles syntaxiques de scala.

Si je bousille et insère quelque chose en plus de List () au milieu du tableau, cela ira bien avec le compilateur, mais le type de x deviendrait silencieusement List [Any] au lieu de List [List [Int]]:

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

Le code équivalent échoue maintenant à compilez:

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,

Et enfin, si vous voulez spécifier explicitement le type d'éléments (dire que vous souhaitez protéger contre la possibilité d'une inadvertance mélange Ints et Double, vous n'avez qu'à spécifier Matrix[Int] au lieu de le laid List[List[Int]]:

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

EDIT: je vois que vous avez remplacé List par Array dans votre question. Utiliser des tableaux tout ce que vous avez à utiliser est de remplacer List avec Array et toList avec toArray dans mon code ci-dessus.

4
répondu Régis Jean-Gilles 2012-12-13 15:59:44

Comme je suis aussi dégoûté de ce problème de virgule finale (Je ne peux pas simplement échanger la dernière ligne avec une autre), j'utilise parfois une API fluide ou la syntaxe du constructeur pour obtenir la syntaxe que j'aime. Un exemple utilisant la syntaxe du constructeur serait:

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

Qui permet:

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

Malheureusement, cela repose sur des données mutables bien qu'elles ne soient utilisées que temporairement pendant la construction. Dans les cas où je veux une beauté maximale pour la syntaxe de construction je le ferais préférez cette solution.

Dans le cas où build est trop long / verbeux, vous voudrez peut-être le remplacer par une fonction apply vide.

3
répondu bluenote10 2012-12-14 11:50:00

Je ne sais pas si c'est le moyen le plus simple, mais j'ai inclus du code ci-dessous pour convertir des tuples imbriqués en tableaux '2D'.

Tout d'abord, vous avez besoin d'une plaque de chaudière pour obtenir la taille des tuples ainsi que pour convertir les tuples en [Array[Array[Double]]. La série d'étapes que j'ai utilisées étaient:

  1. calculez le nombre de lignes et de colonnes dans le tuple
  2. transforme le tuple imbriqué en un tableau d'une ligne
  3. remodeler le tableau en fonction de la taille de l'original tuple.

Le code pour cela est:

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

}

Maintenant pour l'utiliser, vous pourriez faire la suivante:

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

Alternativement, si vous pouviez simplement appeler le rowArrayTo2DArray directement en utilisant la taille du tableau que vous voulez et un tableau 1D de valeurs:

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))
1
répondu hohonuuli 2012-12-13 18:37:50

En regardant à travers les réponses, je n'ai pas trouvé ce qui me semble le moyen le plus évident et le plus simple de le faire. au lieu de Array, vous pouvez utiliser un tuple.
ressemblerait à quelque chose comme ça:

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

Semble propre et élégant?
je pense que oui :)

1
répondu gilad hoch 2012-12-16 12:20:38