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 dex
deviendrait silencieusementArray[Any]
au lieu deArray[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 à direint[][]
en Java.
Est là un moyen clair de contourner ça?
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. ;)
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]] = ...
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 Int
s 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.
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.
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:
- calculez le nombre de lignes et de colonnes dans le tuple
- transforme le tuple imbriqué en un tableau d'une ligne
- 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))
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 :)