我正在Scala中解决Dutch national flag problem,并提出了以下代码:
def dutchNationalFlag[T](a: Array[T])(implicit ordering: Ordering[T]) = {
def sort(lo: Int, hi: Int): Unit = {
Stream.iterate((lo, hi, lo + 1)) { acc =>
val (lt, gt, i) = acc
if (ordering.lt(a(i), a(lt))) {
swap(lt, i, a)
(lt + 1, gt, i + 1)
} else if (ordering.gt(a(i), a(gt))) {
swap(gt, i, a)
(lt, gt - 1, i)
} else {
(lt, gt, i + 1)
}
}
.takeWhile(acc => acc._2 >= acc._3)
.lastOption
.foreach { acc =>
val (lt, gt, _) = acc
sort(lo, lt - 1)
sort(gt + 1, hi)
}
}
sort(0, a.length - 1)
}
出于性能原因,我想修改现有阵列,而不是创建新阵列。上面的代码有效,但是从swap
调用iterate
时有明显的副作用,这在纯功能样式的代码中是有问题的。我考虑过用稍后在swap
中执行的方法引用替换foreach
,这类似于Haskell IO,但正如您可以想象的那样,这样做会使代码有些复杂。
还有其他想法吗?
答案 0 :(得分:0)
将命令式算法转换为纯函数式算法时,“出于性能原因将数组更改为原位”是一个经典问题,但是,如果您对数据结构更加聪明,则有很多解决方法。例如,如果您这样表示数组,该怎么办:
class RearrangedArray[A](elements: Array[A], indexMap: Map[Int, Int] = Map.empty) {
def apply(i: Int): A = elements(indexMap.getOrElse(i, i))
def swap(i: Int, j: Int): RearrangedArray = {
val newMap = indexMap + (i -> indexMap.getOrElse(j, j)) + (j -> indexMap.getOrElse(i, i))
new RearrangedArray(elements, newMap)
}
def toArray: Array[A] = {
(0 until elements.size).map(apply).toArray
}
}
请注意,它是完全不可变的,使用O(n)内存,交换和查找在恒定时间内进行...所有相同的性能特征,但是您可以在每次迭代中创建一个新变量,并将其与索引一起传递给使它完全参照透明。不必使数组发生变异,而是让您的所有函数都将其中一个作为参数,然后返回一个新的函数。
答案 1 :(得分:0)
以下是我倾向于解决
中的问题的方法def sort[T](low: T, high: T)(items: List[T])(implicit ordering: Ordering[T])=
{
val groupedItems = items.groupBy
{
_ match
{
case item if ordering.lt(item,low) => 0
case item if ordering.gt(item,high) => 2
case _ => 1
}
}
groupedItems(0) ++ groupedItems(1) ++ groupedItems(2)
}
这可能会比编写Java或C风格的代码效率低。通常,这没关系。 Scala编译器非常擅长优化事物,并且性能通常足够接近,您不会真正注意到差异。如果您打算对大量数据执行此操作,则Scala版本的优势在于它已经是并行算法。如果在输入列表中调用.par
,则会得到一个并行集合,该集合将跨多个内核/线程运行操作。如果将List[T]
更改为org.apache.spark.rdd.RDD[T]
,则代码可以在具有数十台计算机的分布式集群上运行。