Инициализация двумерного (многомерного) массива в Scala
Легко инициализировать 2D-массив (или, фактически, любой многомерный массив) в Java, помещая что-то вроде этого:
int[][] x = new int[][] {
{ 3, 5, 7, },
{ 0, 4, 9, },
{ 1, 8, 6, },
};
Легко читается, он похож на 2D-матрицу и т.д. и т.д.
Но как это сделать в Scala?
Лучшее, что я мог бы придумать, ну, гораздо менее кратким:
val x = Array(
Array(3, 5, 7),
Array(0, 4, 9),
Array(1, 8, 6)
)
Проблемы, которые я вижу здесь:
- Повторяется "Массив" снова и снова (например, кроме
Array
) может быть что-то еще.
- Это требует, чтобы опускать конец
,
в каждом вызове массива
-
Если я испортил и вставлял что-то, кроме Array()
в середине массива, он будет работать хорошо с компилятором, но тип x
беззвучно станет Array[Any]
вместо Array[Array[Int]]
:
val x = Array(
Array(3, 5, 7),
Array(0, 4), 9, // <= OK with compiler, silently ruins x
Array(1, 8, 6)
)
Существует защита от него, чтобы указать тип напрямую, но он выглядит еще более переполненным, чем на 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)
)
Этот последний пример нуждается в Array
даже в 3 раза больше, чем я должен сказать int[][]
в Java.
Есть ли какой-либо четкий способ?
Ответы
Ответ 1
Я предлагаю использовать Scala 2.10 и макросы:
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"))
}
}
Использование с помощью
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"
^
Я не думаю, что вы получите его короче.;)
Ответ 2
Лично я бы сосать его и набирать (или вырезать и вставлять) "Массив" несколько раз для большей ясности. Разумеется, укажите аннотацию типа для безопасности. Но если у вас действительно закончились электронные чернила, быстрый легкий взлом будет просто обеспечить псевдоним для Array
, например:
val > = Array
val x: Array[Array[Int]] = >(
>(3, 5, 7),
>(0, 4, 9),
>(1, 8, 6)
)
Вы также можете указать псевдоним типа Array
, если вы хотите сократить аннотацию:
type >[T] = Array[T]
val x: >[>[Int]] = ...
Ответ 3
Если использовать только List
of List
(который сам по себе не может гарантировать, что каждый дополнительный список имеет одинаковый размер), это не проблема для вас, и вам нужно только упростить синтаксис и избежать ошибок при создании -time, scala имеет множество способов создания хороших синтаксических конструкций.
Одна такая возможность была бы простой помощницей:
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)
)
О ваших проблемах:
Повторяется "Список" снова и снова (например, помимо списка может быть что-то еще)
Не здесь.
Требуется опустить трейлинг в каждом вызове списка
К сожалению, это все еще верно, мало что можно сделать с помощью синтаксических правил scala.
Если я завинчиваю и вставляю что-то помимо List() в середину массива, он будет хорошо работать с компилятором, но тип x беззвучно станет List [Any] вместо List [List [Int]]:
val x = List(
List(3, 5, 7),
List(0, 4), 9, // <= OK with compiler, silently ruins x
List(1, 8, 6)
)
Эквивалентный код теперь скрывается для компиляции:
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,
И, наконец, если вы хотите явно указать тип элементов (скажем, что вы хотите защитить от возможности непреднамеренного смешивания Int
и Double
s), вам нужно указать Matrix[Int]
вместо уродливого List[List[Int]]
:
val x = Matrix[Int](
(3, 5, 7),
(0, 4, 9),
(1, 8, 6)
)
EDIT: Я вижу, что вы заменили List
на Array
в своем вопросе. Для использования массивов все, что вам нужно использовать, это заменить List
на Array
и toList
на toArray
в моем коде выше.
Ответ 4
Так как я также испытываю отвращение к этой проблеме с конечной запятой (т.е. я не могу просто обмениваться последней строкой с любым другим), я иногда использую либо свободный API, либо трюк синтаксиса конструктора, чтобы получить синтаксис, который мне нравится. Пример с использованием синтаксиса конструктора:
trait Matrix {
// ... and the beast
private val buffer = ArrayBuffer[Array[Int]]()
def >(vals: Int*) = buffer += vals.toArray
def build: Array[Array[Int]] = buffer.toArray
}
Что позволяет:
// beauty ...
val m = new Matrix {
>(1, 2, 3)
>(4, 5, 6)
>(7, 8, 9)
} build
К сожалению, это зависит от изменяемых данных, хотя оно используется только во время строительства. В тех случаях, когда я хочу максимальной красоты для синтаксиса построения, я бы предпочел это решение.
В случае, если build
слишком длинный/подробный, вы можете заменить его на пустую прикладную функцию.
Ответ 5
Я не знаю, является ли это простым способом, но я включил некоторый код ниже для преобразования вложенных кортежей в массивы '2D'.
Во-первых, вам нужна плита котла для получения размера кортежей, а также преобразование кортежей в [Array[Array[Double]]
. Серия шагов, которые я использовал, была:
- Определите количество строк и столбцов в кортеже
- Поверните вложенный кортеж в массив с одной строкой
- Измените массив на основе размера исходного кортежа.
Код для этого:
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)
}
}
Теперь, чтобы использовать это, вы можете просто сделать следующее:
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))
В качестве альтернативы, если вы можете просто вызвать rowArrayTo2DArray напрямую, используя размер требуемого массива и 1D массив значений:
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))
Ответ 6
просматривая ответы, я не нашел, что для меня кажется самым очевидным и простым способом сделать это. вместо Array
вы можете использовать кортеж.
будет выглядеть примерно так:
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))
кажется чистым и элегантным?
я так думаю:)