Arrays 递归地将Map[Int,Map[Int,X]]转换为Array[Array[X]]

Arrays 递归地将Map[Int,Map[Int,X]]转换为Array[Array[X]],arrays,scala,maps,generic-programming,scala-collections,Arrays,Scala,Maps,Generic Programming,Scala Collections,我正在尝试编写一个函数,该函数在具有整数键的映射之间转换为相应的数组。我已经完成了基本情况,但正在尝试编写递归情况(即多维数组:将Map[Int,Map[Int,X]]转换为Array[Array[X]]) 这项任务产生于需要从流中构造一个数组,而不事先知道数组的大小,从而允许元素以随机顺序从流中脱落,并允许重复元素从流中脱落 我有一个函数可以做到这一点: def toArrayHard[X:ClassManifest](x:scala.collection.Map[Int, X]):Array

我正在尝试编写一个函数,该函数在具有整数键的映射之间转换为相应的数组。我已经完成了基本情况,但正在尝试编写递归情况(即多维数组:将Map[Int,Map[Int,X]]转换为Array[Array[X]])

这项任务产生于需要从流中构造一个数组,而不事先知道数组的大小,从而允许元素以随机顺序从流中脱落,并允许重复元素从流中脱落

我有一个函数可以做到这一点:

def toArrayHard[X:ClassManifest](x:scala.collection.Map[Int, X]):Array[X] =
{
    if (x.size == 0) new Array(0)
    else 
    {
        val max:Int = 1 + x.keys.max

        val a:Array[X] = new Array(max)

        var i = 0
        while (i < max)
        {
            a(i) = x(i)
            i += 1
        }
        a
    }
}
这是我得到的错误:

应为“=>”,但找到了“forSome”

由于这是一项教育性的工作,我们非常感谢您的反馈。具体地说,如果有人批评我的java外观糟糕的代码,批评做同样事情的现有scala函数,或者建议构建这些数组的替代方法,我将不胜感激。

这是毫无意义的:

case t:Map[Int, Map[Int, Y]] forSome { type Y }
由于擦除,您只能检查以下内容:

case t: Map[_, _]
事实上,这是唯一不会收到擦除警告的方法。此外,存在类型几乎总是不必要的,而且,就我个人而言,我总是觉得它的语法有点难以理解。不过,在这种情况下,使用
\uuu
来表示存在类型就足够了。但是,请注意,在我自己的代码(第一个版本)中,我不能使用它,因为如果我使用它,类型将不匹配

其次,

任意深多维 阵列

这不是一个特别好的主意。您必须知道将返回什么类型来声明它——如果深度是“任意的”,那么类型也是如此。您可以使用
数组[AnyRef]
,但使用起来会很痛苦

不过,还有一种方法。我去掉了所有的循环,用地图代替了它们。注意,我通过调用
Map m
,利用了
Map
也是
函数1
这一事实。还要注意的是,我只是在数组上使用
map
(使用
toArray
创建)来避免管理映射创建

def deMap(m: Map[Int, AnyRef]): Array[AnyRef] = {
  def translate(x: AnyRef): AnyRef = x match {
    case map: Map[Int, AnyRef] => deMap(map)
    case s: String => s
    case other => throw new IllegalArgumentException("Expected Map or String, got "+other.getClass.toString)
  }
  m.keys.toArray.sorted map m map translate
}
有两个问题。首先,如果键不连续(
Map(例如,0->“a”,2->“b”)
,元素将不在位置。下面是一种解决方法,将下一行代码替换为:

  Array.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate
在这里,我使用一个空字符串来表示数组中的任何孔

另一个问题是,我假设所有映射的类型都是
Map[Int,AnyRef]
。要解决此问题,可能必须像这样重写:

def deMap(m: Map[Int, AnyRef]): Array[AnyRef] = {
  def translate(x: AnyRef): AnyRef = x match {
    case map: Map[_, _] => 
      val validMap = map collect { case (key: Int, value: AnyRef) => (key -> value) }
      deMap(validMap)
    case s: String => s
    case other => throw new IllegalArgumentException("Expected Map or String, got "+other.getClass.toString)
  }
  Array.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate
}
在这里,我放弃所有键不是
Int
且值不是
AnyRef
的对。您可以进一步安全地检查代码,以测试是否缺少某些内容,并在这种情况下报告错误:

def deMap(m: Map[Int, AnyRef]): Array[AnyRef] = {
  def translate(x: AnyRef): AnyRef = x match {
    case map: Map[_, _] => 
      val validMap = map collect { case (key: Int, value: AnyRef) => (key -> value) }
      if (map.size > validMap.size) {
        val wrongPairs = map.toSeq diff validMap.toSeq
        throw new IllegalArgumentException("Invalid key/value pairs found: "+wrongPairs)
      }
      deMap(validMap)
    case s: String => s
    case other => throw new IllegalArgumentException("Expected Map or String, got "+other.getClass.toString)
  }
  Array.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate
}
最后,关于性能。我使用
map
的方式意味着将为每个
map
分配一个新数组。有些人认为这意味着我必须迭代三次而不是一次,但由于每次我都执行不同的任务,所以它实际上并没有做更多的工作。但是,我每次分配一个新数组的事实肯定会对性能产生影响——既因为分配惩罚(Java预先初始化所有数组),也因为缓存局部性问题

避免这种情况的一种方法是使用
视图
。当您在
视图上执行
map
flatMap
filter
操作时,会返回一个新视图,并存储该操作以备将来使用(其他方法也可能以这种方式工作--检查文档,或在有疑问时进行测试)。当您最终对
视图
对象执行
应用
时,它将应用所有必要的操作,以获取您请求的特定元素。每次对该元素应用
时,它也会这样做,所以性能可能更好,也可能更差

这里我将从一个
范围
视图开始,以避免数组分配,然后在最后将视图转换为
数组
。尽管如此,
仍将创建一个集合,带来一些开销。在此之后,我将解释如何避免这种情况

def deMap(m: Map[Int, AnyRef]): Array[AnyRef] = {
  def translate(x: AnyRef): AnyRef = x match {
    case map: Map[_, _] => 
      val validMap = map collect { case (key: Int, value: AnyRef) => (key -> value) }
      if (map.size > validMap.size) {
        val wrongPairs = map.toSeq diff validMap.toSeq
        throw new IllegalArgumentException("Invalid key/value pairs found: "+wrongPairs)
      }
      deMap(validMap)
    case s: String => s
    case other => throw new IllegalArgumentException("Expected Map or String, got "+other.getClass.toString)
  }
  (0 to m.keys.max view) map (m getOrElse (_, "")) map translate toArray
}
但是,您应该将
视图
留给必要的优化,而不是主动使用它们。它不一定比普通的集合快,而且它的非严格性可能会很棘手。使用
视图
的另一种替代方法是使用
非常类似于
列表
,只是它只根据需要计算其元素。这意味着在必要之前,不会应用任何
map
操作。要使用它,只需将下一行替换为最后一行:

  Stream.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate toArray
视图
的主要优点是,一旦计算了
中的值,它将保持计算状态。这也是它相对于
视图的主要缺点,具有讽刺意味的是。在这种情况下,我认为
视图
更快

最后,关于先执行
max
而不计算
Set
键的结果
)。
Map
也是一个
Iterable
,并且所有
Iterable
都有一个
max
方法,因此您可以简单地执行
m.max
。但是,它将计算
Tuple2[Int,AnyRef]
的最大值,该类型没有
排序。但是,
max
接收到一个隐式参数,告诉它要使用什么
排序
,因此可以提供:

m.max(Ordering by ((_: (Int, AnyRef))._1))
这有点言过其实,所以我们可以定义我们需要的隐式,然后隐式地使用它。:-)

请注意,
max
返回一个元组,
,因此我们需要
\u 1
来获取
。另外,请注意,我们在
deMap
的每个嵌套处创建一个
Ordering
对象。不错,但可以通过在其他地方定义它来改进

一旦你完成了所有这些,
m.max(Ordering by ((_: (Int, AnyRef))._1))
def deMap(m: Map[Int, AnyRef]): Array[AnyRef] = {
  implicit val myOrdering = Ordering by ((_: (Int, AnyRef))._1)
  def translate(x: AnyRef): AnyRef = x match {
    case map: Map[_, _] => 
      val validMap = map collect { case (key: Int, value: AnyRef) => (key -> value) }
      if (map.size > validMap.size) {
        val wrongPairs = map.toSeq diff validMap.toSeq
        throw new IllegalArgumentException("Invalid key/value pairs found: "+wrongPairs)
      }
      deMap(validMap)
    case s: String => s
    case other => throw new IllegalArgumentException("Expected Map or String, got "+other.getClass.toString)
  }
  (0 to m.max._1 view) map (m getOrElse (_, "")) map translate toArray
}