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

时间:2010-12-04 23:08:21

标签: arrays scala maps generic-programming scala-collections

我正在尝试编写一个函数,用于将带有整数键的Maps转换为相应的数组。我已完成基本案例,但我正在尝试编写递归案例(即多维数组:将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
    }
}

注意,我知道如果地图包含密钥k但是不包含密钥i,其中0 <= i&lt; ķ。这对我来说没问题。

现在我希望对任意深度的多维数组做同样的事情。 例如,在Map [Int,Map [Int,X]]到Array [Array [X]]之间进行转换。不幸的是,我被这些类型绊倒了。以上面为基础案例,这是我到目前为止所做的:

def toArrayHardRec[X:ClassManifest](x:scala.collection.Map[Int, X]):Array[X] =
{
    import scala.collection.Map

    if (x.size == 0) new Array(0)
    else 
    {
        x match
        {
            case t:Map[Int, Map[Int, Y]] forSome { type Y } =>
            {
                val f0 = t.mapValues{m => toArrayHardRec[Map[Int, Y]](m)}
                toArrayHard(f0)
            }
            case _ => toArrayHard(x)
        }
    }
}

这是我得到的错误:

  

=&GT;'预期但'forSome'找到了。

由于这是一项教育追求,因此非常感谢任何反馈。具体来说,我很感激任何代码批评我看起来很糟糕的代码,现有的scala函数做同样的事情,或建议另一种方法来构建这些数组。

1 个答案:

答案 0 :(得分:11)

这没有意义:

case t:Map[Int, Map[Int, Y]] forSome { type Y }

因为删除,所有你可以检查的是:

case t: Map[_, _]
实际上,这是你不会收到关于擦除的警告的唯一方法。此外,存在类型几乎总是不必要的,而且,就个人而言,我总是发现它的语法有点棘手。尽管如此,这是使用_来表示存在类型的情况。但请注意,在我自己的代码(第一个版本)中,我无法使用它,因为如果我这样做,类型将不匹配。

接下来,

  任意深度多维   阵列

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

不过,这是一种方法。我取消了所有循环,用地图替换它们。请注意,我通过调用Map来利用Function1也是map m这一事实。另请注意,我只是在数组(使用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预初始化所有数组),以及缓存局部性问题。

避免这种情况的一种方法是使用view。当您在map上执行flatMapfilterview时,您会看到一个新视图,该操作已存储供将来使用(其他方法也可以这样工作 - 检查文档,或在有疑问时进行测试)。当您最终对apply对象执行view时,它将应用获取您要求的特定元素所需的所有操作。每次apply也会为该元素执行此操作,因此性能可能更好或更差。

这里我将从Range视图开始,以避免数组分配,然后在最后将视图转换为Array。仍然,keys将创建一个集合,增加一些开销。在此之后,我将解释如何避免这种情况。

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
}

但是,您应该view进行必要的优化,而不是主动使用它们。它不一定比普通集合更快,并且它的非严格性可能很棘手。使用view的另一种方法是使用Stream代替。 StreamList非常相似,只是它只根据需要计算它的元素。这意味着在必要时不会应用任何map操作。要使用它,只需将最后一行替换为:

  Stream.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate toArray

Stream优于view的主要优点是,一旦计算了Stream中的值,就会保持计算。这也是view的主要劣势,具有讽刺意味的是。在这种特殊情况下,我认为view更快。

最后,关于首先执行max而不计算Setkeys的结果)。 Map也是Iterable,所有Iterable都有max方法,因此您只需m.max即可。但是,这将计算Tuple2[Int, AnyRef]的最大值,这是一种没有Ordering的类型。但是,max会收到一个隐式参数,告诉它使用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
}

请注意,max会返回一个元组keyvalue,因此我们需要_1才能获得key。另请注意,我们在Ordering的每个嵌套处创建了一个deMap对象。不错,但通过在别处定义它可以做得更好。

一旦你完成了所有这些,while循环仍然会更快,但我不知道多少。如果有足够的JVM优化,它们可能会非常接近。另一方面,如果你真的想要速度,你可以一直去装配代码。它归结为编写代码的简单和快速,维护代码的容易程度以及运行速度之间的平衡。