Javaで、次のように入力することで、2D配列(または実際には任意の多次元配列)を初期化するのは簡単です。
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)
)
この最後の例では、Javaでint[][]
と言わなければならないよりも3倍もArray
が必要です。
これを回避する明確な方法はありますか?
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"
^
短くなるとは思いません。 ;)
個人的には、わかりやすくするために、「配列」を数回吸い上げて入力(またはカットアンドペースト)します。もちろん、安全のために型の注釈を含めてください。しかし、本当にe-inkが不足している場合、簡単なハックは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]] = ...
List
の単なるList
を使用する場合(それ自体ではすべてのサブリストが同じサイズであることを保証できません)、問題はなく、簡単な構文と回避のみに関心があります。作成時のエラー、scalaには、Nice構文構造を作成する多くの方法があります。
そのような可能性の1つは、単純なヘルパーです。
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 [List [Int]]ではなくList [Any]になります。
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
sとDouble
sが誤って混在する可能性から保護したい場合)、Matrix[Int]
を指定するだけで済みます。醜いList[List[Int]]
の代わりに:
val x = Matrix[Int](
(3, 5, 7),
(0, 4, 9),
(1, 8, 6)
)
編集:質問でList
をArray
に置き換えたようです。配列を使用するには、上記のコードでList
をArray
に、toList
をtoArray
に置き換えるだけです。
私もこの末尾のコンマの問題に嫌悪感を持っているので(つまり、最後の行を他の行と単純に交換することはできません)、流暢な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
が長すぎる/冗長である場合は、空の適用関数に置き換えることをお勧めします。
これが簡単な方法かどうかはわかりませんが、ネストされたタプルを「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))
または、必要な配列のサイズと値の1D配列を使用して、rowArrayTo2DArrayを直接呼び出すことができる場合:
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))
答えをちらっと見て、私はそれを行うための最も明白で簡単な方法のように思えるものを見つけられませんでした。 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))
清潔でエレガントに見えますか?
私はそう思う :)