Arrays 将任意大小的矩阵/2d数组合并为一个大2d数组

Arrays 将任意大小的矩阵/2d数组合并为一个大2d数组,arrays,scala,Arrays,Scala,我是Scala的新手,这是一个无趣的问题,但我被困在这里。 我有多个整数的2D数组,我想合并成一个大的2D数组 迄今为止的守则: object Generator { // Use a HashMap for greate flexibility in the future. We could more easily add additional checks for biome placement like if(x, y-1) ... val biomes:mutable.HashM

我是Scala的新手,这是一个无趣的问题,但我被困在这里。 我有多个整数的2D数组,我想合并成一个大的2D数组

迄今为止的守则:

object Generator
{
  // Use a HashMap for greate flexibility in the future. We could more easily add additional checks for biome placement like if(x, y-1) ...
  val biomes:mutable.HashMap[Vector2, Biome] = new mutable.HashMap[Vector2, Biome]

  def create(width:Int, height:Int)
  {
    for(x <- 0 until width; y <- 0 until height)
    {
      if(y < height / 3 * 2)
        biomes.put(new Vector2(x, y), new GrasslandBiome(128, 64))
      else
        biomes.put(new Vector2(x, y), new SkyBiome(128, 64))
    }

    terrain
  }

  def terrain
  {
    var tileMap:ArrayBuffer[Array[Array[Int]]] = new ArrayBuffer[Array[Array[Int]]]
    for((position, biome) <- biomes)
    {
      tileMap += biome.tileMap
    }

    Predef.println(tileMap.toString) // TODO remove
  }
}
然后是println的NullPointerException:

我非常感谢任何帮助我解决推理和编程错误的人,并祝你度过美好的一天!:

编辑:当然,相应的生物群落数据不是未初始化的/空的!如果我手动打印,一切都如预期

编辑2:根据要求:

abstract class Biome(width:Int, height:Int)
{
  var tileMap: Array[Array[Int]]

  def create();

  def printTileMap()
  {
    printTileMap(tileMap, width, height)
  }

  def printTileMap(map: Array[Array[Int]], mapWidth:Int, mapHeight:Int)
  {
    var x = mapHeight - 1
    while(x >= 0)
    {
      for (y <- 0 until mapWidth) Predef.print(map(y)(x) + " ")
      println()
      x -= 1
    }
  }
}
还有一个例子:

class SkyBiome(width:Int, height:Int) extends Biome(width:Int, height:Int)
{
  var tileMap: Array[Array[Int]] = Array.ofDim[Int](width, height)

  create()
  override def create()
  {
    for(x <- 0 until width; y <- 0 until height)
    {
     tileMap(x)(y) = 0
    }
  }

另外,一个丑陋的解决方案是迭代整个HashMap,总结每个生物群落的宽度和高度,用这些信息初始化tileMap,然后简单地将所有内容复制到正确的位置。但这似乎是一个令人讨厌的解决方案。

首先,我找到了关于不同数组类型之间差异的解释:

现在,关于你如何从二维数组中得到一维数组的问题:但是让我们假设,我们没有数组[Array[T]],而是List[List[T]],因为我更喜欢列表;但将其应用于阵列应该没有问题

foldLeft[A]的首字母:A函数:A,B=>A做什么?它通过在每个元素上应用函数a,B=>a,在集合上迭代并聚合类型a的值。这里,B是列表的通用类型。在我们的示例中,我们通过将中间值a与列表中的下一个元素连接,将整数B列表折叠为整数a列表

编辑2:

将此应用于生物群落示例时,我们可以创建从位置到生物群落的地图,如下所示:

def create(width:Int, height:Int): Map[Vector2, Biome] =
{
  // we can define functions within functions
  def isGrasslandBiome(x: Int, y: Int): Boolean =
    y < height / 3 * 2

  // A for-comprehension returns a sequence of all computed elements
  val positionWithBiomes: Seq[(Vector2, Biome)] =
    for{
      x <- 0 until width
      y <- 0 until height
    } yield
    {
      val biome =
        if (isGrasslandBiome(x, y))
          GrasslandBiome(DefaultWidth, DefaultHeight)
        else
          SkyBiome(DefaultWidth, DefaultHeight)
      ((x, y), biome)
    }
  // We fold each element of the sequence into a map
  val biomes: Map[Vector2, Biome] =
    positionWithBiomes.foldLeft[Map[Vector2, Biome]](Map.empty){
      case (map, ((x, y), biom)) => map + ((x, y) -> biom)
    }
  biomes
}
这个类有一个私有构造函数,因为我们不希望内部表示泄漏出去。在Scala中,您可以定义一个可以访问所有私有成员的伴随对象。基本上,伴随对象包含所有在Java中声明为静态的方法

// companion object
object TileMap {
  def empty(width: Int, height: Int): TileMap = {
    val rows = new mutable.ArraySeq[mutable.ArraySeq[Int]](height)
    for (row <- 0 until height) {
      rows(row) = new mutable.ArraySeq[Int](width)
      for (cell <- 0 until width) {
        rows(row)(cell) = 0
      }
    }
    new TileMap(rows, width, height)
  }
}
这当然需要一些解释:terrainForRow将采用一行,即地图中的所有值都具有相同的x值,并以正确的顺序连接它们。顺序由y决定,因为我们希望保留列的顺序

首先,我们过滤x值相同的所有生物群落

第二,我们通过将集合转换为list向集合添加排序,并根据y的值对列表进行排序

第三,我们丢弃x和y的位置值,并提取平铺贴图。如果我们想使用reduceLeft,这是必需的,因为它的类型签名

最后,我们累积平铺映射,调用前面定义的addRows。请注意,reduceLeft仅为大小为2或更大的集合定义

在terrain方法中,我们为每一行调用terrainForRow,也就是说,在我们按行对平铺贴图进行排序之后。每个结果都已经是一个TileMap,因此我们可以对结果序列调用reduceLeft,并通过使用我们定义的函数addColumns将它们简化为一个TileMap

我已将更新的代码放在

我想添加一些您可以在scala中执行的操作,因为您希望学习该语言:

curry函数和部分应用函数

查看方法setTile的签名:它有两个参数列表。这被称为currying,允许我们部分应用函数。例如,我们可以创建如下函数:

// let us partially apply the setTile function
val setGrassTileAt: (Int, Int) => Unit =
  biomesTileMap.setTile(GrassTileValue)
// now tileBuilder will set the value of GrassTile at each position we provide
setGrassTileAt(0, 0)
setGrassTileAt(3, 1)
setGrassTileAt(0, 1)
函数setGrassTile存储在变量中,因为在scala中,函数是一等公民,取两个整数值作为坐标,并将该坐标处的生物群落值设置为GrassTileValue

统一访问原则和案例类

在Scala中,当访问对象的成员时,客户端不应该区分变量和方法。例如,width是超类型中的def还是子类型中的val并不重要

Case类是重写equals、hashCode和toString的类。此外,它们还提供了一个提取器unapply,您可以在匹配时使用该提取器,以及一个apply方法,当您将对象视为函数时,该方法将被应用。案例类对值对象最为有利


我希望我能帮你一点忙。编辑:更新答案前的代码:

能否提供Biome.tileMap的实现?我试图重建您的示例,但无法重建NullPointerException。此外,地形方法不执行任何操作:创建ArrayBuffer,修改此缓冲区,但从不返回它。未修改biome.tileMap。此外,可能有比数组更好的集合,您可以使用它们,而可变集合的使用看起来像是过早的优化。尽可能使用不可变对象@KuluLimpa:我将添加实现。我完全知道它不会返回任何东西。我想先调试,然后返回它。如果输出正常,我可以轻松添加一个返回值。我是sc的新手
ala和不可变集合的想法对我来说有点奇怪。如果我使HashMap不可变,我甚至不能在其中添加任何内容:我会很高兴地查看您的代码,并在下班回家后尝试给您一个有用的答案。在那之前或者在其他人告诉我答案之前,我建议您看看Scala的可选类型,以使可能的空性显式化,并看看您可以对Scala的集合进行的一些函数编程,例如map、collect、foldLeft。我几乎可以肯定,这些将导致一个整洁的解决方案。关于不可变映射:您基本上是从旧映射创建一个新映射,其中包含您添加的元素。如果情况有所改善,我只会求助于可变结构performance@Kulu林巴:我相信有一个很好的解决办法。我有一点Haskell的经验,函数式编程有一些问题,但我仍然被困在这里。如果您没有更好的事情要做,并且仍然想帮助我,我将非常感谢任何好的、可扩展的解决方案,将任意大小的2D数组合并到一个大的2D数组中:Heyho!首先:我真的很抱歉让你等这么久。我有一些现实生活中的事情,没有时间研究你的解决方案。有一件事tho:现在关于你的问题,关于如何从二维数组中得到一维数组。。。这不是我想做的。我想把许多2D数组的位粘合到一个巨大的2D数组中。就像现实生活中的例子!你有一个地图的一部分,并把它们粘在一起,所以你只有一个巨大的地图。仍然每个坐标都是二维的。我会深入研究你的解决方案,看看是否能尽快使它适应我的需要。再次感谢!对不起,我没有正确地理解你。所以你要做的是:biomes.foldLeft[Array[Array[Int]]]Array.emptycase a,position,biome=>a++biome.tileMap其中biomes:Map[Vector2,biome]?这应该将所有的tilemaps累积到一个二维数组中。我更新了答案,希望能回答您的问题。事情变得有点复杂,因为我们想要基于两个独立的属性来积累瓷砖:生物群落在地图中的位置,以及瓷砖贴图中瓷砖的位置。我可能会重新思考我的设计:为什么要存储单独的生物群落并将它们添加到一个组合的tilemap中,而不是直接存储组合的tilemap并让生物群落在这个tilemap中定义区域,例如,通过存储左上角和右下角?没问题。对不起,我不是以英语为母语的人,请容忍我。嗯,我认为把地图分成一束/一块,定义它们的创建=生物群落,然后将它们合并在一起,会给我更多的创作自由。我可以定义许多小的算法,并轻松更改世界/地图的构建方式,而不是一个庞大的算法:稍后我还想通过小的.json文件或其他方式动态创建生物群落。可能有很多方法可以实现我想要实现的目标,但我想避免一个大而复杂的算法,这是我最初的想法。我不是说你不应该把你的地图分割成单独的生物群落。然而,就我个人而言,我发现将地图作为一个整体存储并提取单个生物群落比存储单个生物群落并将它们粘在一起更容易。即使你把所有的东西都存储在一个非常大的2d数组中,你的算法仍然可以在生物群落的抽象上工作,你可以在它的基础上进行构建。不过,您仍然需要仔细考虑json的序列化和反序列化。
// companion object
object TileMap {
  def empty(width: Int, height: Int): TileMap = {
    val rows = new mutable.ArraySeq[mutable.ArraySeq[Int]](height)
    for (row <- 0 until height) {
      rows(row) = new mutable.ArraySeq[Int](width)
      for (cell <- 0 until width) {
        rows(row)(cell) = 0
      }
    }
    new TileMap(rows, width, height)
  }
}
def terrain(biomes: Map[Vector2, Biome]): TileMap = {
  val (rows, _) = biomes.keys.unzip
  rows.toList.sortBy{id => id} map {
    row => terrainForRow(row, biomes)
  } reduceLeft {
      (accumulator: TileMap, nexTileMap: TileMap) => accumulator addColumns nexTileMap
  }
}

def terrainForRow(column: Int, biomes: Map[Vector2, Biome]): TileMap =
  biomes.filter{
    case ((x, y), _) => x == column
  }.toList.sortBy{
    case ((_, y), _) => y
  }.map{
    case ((_, _), biome) => biome.tileMap
  }.reduceLeft[TileMap]{
    (accumulator, nextTileMap) => accumulator addRows nextTileMap
  }
// let us partially apply the setTile function
val setGrassTileAt: (Int, Int) => Unit =
  biomesTileMap.setTile(GrassTileValue)
// now tileBuilder will set the value of GrassTile at each position we provide
setGrassTileAt(0, 0)
setGrassTileAt(3, 1)
setGrassTileAt(0, 1)
abstract class Biome
{
  // Scala supports the uniform access princple
  // A client is indifferent whether this is def, val or var
  // A subclass can implement width and height as val
  def width: Int
  def height: Int

  def tileMap: TileMap

}

// Case classes automatically define the methods equals, hashCode and toString
// They also have an implicit companion object defining apply and unapply
// Note that equals is now defined on width and height, which is probably not what
// we want in this case.
case class SkyBiome(width: Int, height: Int) extends Biome {
  val tileMap: TileMap = TileMap.empty(width, height)

}

case class GrasslandBiome(width: Int, height: Int) extends Biome {
  val tileMap: TileMap = TileMap.empty(width, height)
}