我正在尝试编写一个函数,用于将带有整数键的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函数做同样的事情,或建议另一种方法来构建这些数组。
答案 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
上执行flatMap
,filter
和view
时,您会看到一个新视图,该操作已存储供将来使用(其他方法也可以这样工作 - 检查文档,或在有疑问时进行测试)。当您最终对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
代替。 Stream
与List
非常相似,只是它只根据需要计算它的元素。这意味着在必要时不会应用任何map
操作。要使用它,只需将最后一行替换为:
Stream.range(0, m.keys.max + 1) map (m getOrElse (_, "")) map translate toArray
Stream
优于view
的主要优点是,一旦计算了Stream
中的值,就会保持计算。这也是view
的主要劣势,具有讽刺意味的是。在这种特殊情况下,我认为view
更快。
最后,关于首先执行max
而不计算Set
(keys
的结果)。 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
会返回一个元组key
和value
,因此我们需要_1
才能获得key
。另请注意,我们在Ordering
的每个嵌套处创建了一个deMap
对象。不错,但通过在别处定义它可以做得更好。
一旦你完成了所有这些,while
循环仍然会更快,但我不知道多少。如果有足够的JVM优化,它们可能会非常接近。另一方面,如果你真的想要速度,你可以一直去装配代码。它归结为编写代码的简单和快速,维护代码的容易程度以及运行速度之间的平衡。