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