我正在通过“Scala编程”,并编写了一个快速实现选择排序算法。但是,由于我在函数式编程方面仍然有点绿色,因此我无法转换为更多Scala-ish风格。对于那里的Scala程序员,我怎样才能使用Lists和vals而不是回到我的命令方式呢?
答案 0 :(得分:10)
作为starblue already said,您需要一个计算列表最小值的函数,并返回删除该元素的列表。这是我的尾部递归实现类似的东西(因为我相信foldl
在标准库中是尾递归的),并且我试图使其尽可能地功能化:)。它返回一个列表,其中包含原始列表的所有元素(但有些反转 - 参见下面的说明),最小值为头。
def minimum(xs: List[Int]): List[Int] =
(List(xs.head) /: xs.tail) {
(ys, x) =>
if(x < ys.head) (x :: ys)
else (ys.head :: x :: ys.tail)
}
这基本上是一个折叠,从包含xs
的第一个元素的列表开始如果xs
的第一个元素小于该列表的头部,我们将它预先附加到列表ys
。否则,我们将其作为第二个元素添加到列表ys
中。等等递归地,我们将列表折叠成一个新的列表,其中包含最小元素作为头部和列表,其中包含xs
的所有元素(不一定按相同顺序)并删除最小元素,作为尾巴。请注意,此功能不会删除重复项。
创建此辅助函数后,现在可以轻松实现选择排序。
def selectionSort(xs: List[Int]): List[Int] =
if(xs.isEmpty) List()
else {
val ys = minimum(xs)
if(ys.tail.isEmpty)
ys
else
ys.head :: selectionSort(ys.tail)
}
不幸的是,这个实现不尾递归,所以它会炸掉大型列表的堆栈。无论如何,你不应该对大型列表使用O(n ^ 2)排序,但仍然......如果实现是尾递归的话会很好。我会试着想一些......我认为它看起来像是折叠的实现。
Tail Recursive!
为了使它具有尾递归性,我在函数式编程中使用了相当常见的模式 - 累加器。它有点落后,因为现在我需要一个名为maximum
的函数,它基本上和minimum
一样,但是使用最大元素 - 它的实现是最小的,但使用{{1}而不是>
。
<
编辑:更改了答案,将辅助函数作为选择排序函数的子函数。
它基本上将最大值累积到一个列表中,它最终作为基本案例返回。您还可以通过def selectionSort(xs: List[Int]) = {
def selectionSortHelper(xs: List[Int], accumulator: List[Int]): List[Int] =
if(xs.isEmpty) accumulator
else {
val ys = maximum(xs)
selectionSortHelper(ys.tail, ys.head :: accumulator)
}
selectionSortHelper(xs, Nil)
}
替换accumulator
来查看它是尾递归的 - 然后检查堆栈跟踪。
这是使用累加器逐步排序。左侧显示列表throw new NullPointerException
,而右侧显示xs
。每一步都有一个星号表示最大值。
accumulator
以下显示了逐步折叠以计算最大值:
64* 25 12 22 11 ------- Nil
11 22 12 25* ------- 64
22* 12 11 ------- 25 64
11 12* ------- 22 25 64
11* ------- 12 22 25 64
Nil ------- 11 12 22 25 64
答案 1 :(得分:2)
这是选择排序的另一种实现(通用版本)。
def less[T <: Comparable[T]](i: T, j: T) = i.compareTo(j) < 0
def swap[T](xs: Array[T], i: Int, j: Int) { val tmp = xs(i); xs(i) = xs(j); xs(j) = tmp }
def selectiveSort[T <: Comparable[T]](xs: Array[T]) {
val n = xs.size
for (i <- 0 until n) {
val min = List.range(i + 1, n).foldLeft(i)((a, b) => if (less(xs(a), xs(b))) a else b)
swap(xs, i, min)
}
}
答案 2 :(得分:1)
您需要一个帮助函数来执行选择。它应该返回最小元素和列表的其余部分,并删除元素。
答案 3 :(得分:1)
在功能样式中进行选择排序时应该遇到问题,因为它是就地排序算法。根据定义,就地不起作用。
您将遇到的主要问题是您无法交换元素。这就是为什么这很重要。假设我有一个列表( 0 ... a x ... a n ),其中a x 是最小值。你需要得到一个 x ,然后组成一个列表( 0 ... a x-1 a x + 1 a n )。问题是,如果您希望保持纯粹的功能,则必须将 0 元素复制到 x-1 。其他功能数据结构,特别是树,可以有比这更好的性能,但基本问题仍然存在。
答案 4 :(得分:1)
我认为以功能方式进行选择排序是合理可行的,但正如Daniel指出的那样,它很有可能表现得非常糟糕。
我只是尝试编写功能性冒泡排序,作为选择排序的稍微简单和退化的情况。这就是我所做的,这暗示了你能做什么:
define bubble(data)
if data is empty or just one element: return data;
otherwise, if the first element < the second,
return first element :: bubble(rest of data);
otherwise, return second element :: bubble(
first element :: (rest of data starting at 3rd element)).
一旦完成递归,最大的元素就在列表的末尾。现在,
define bubblesort [data]
apply bubble to data as often as there are elements in data.
完成后,您的数据确实已经排序。是的,这太可怕了,但我的Clojure实现了这个伪代码。
只关注自己的第一个或第二个元素,然后将其余的工作留给递归的活动是一种lisp-y,功能性的方式来做这种事情。但是,一旦你已经习惯了这种想法,就会有更明智的方法解决这个问题。
我建议实施合并排序:
Break list into two sub-lists,
either by counting off half the elements into one sublist
and the rest in the other,
or by copying every other element from the original list
into either of the new lists.
Sort each of the two smaller lists (recursion here, obviously).
Assemble a new list by selecting the smaller from the front of either sub-list
until you've exhausted both sub-lists.
递归就在中间,我没有看到一种聪明的方法来使算法尾递归。尽管如此,我认为它的时间是O(log-2),并且不会在堆栈上放置过高的负载。
玩得开心,祝你好运!
答案 5 :(得分:1)
感谢上面的提示,他们非常鼓舞人心。这是选择排序算法的另一种功能方法。我尝试将其基于以下想法:min(A)=if A=Nil ->Int.MaxValue else min(A.head, min(A.tail))
可以很容易地找到最大/最小值。第一分钟是列表的最小值,第二分钟是两个数字的最小值。这很容易理解,但不幸的是不是尾递归。使用累加器方法可以像这样转换min定义,现在正确的Scala:
def min(x: Int,y: Int) = if (x<y) x else y
def min(xs: List[Int], accu: Int): Int = xs match {
case Nil => accu
case x :: ys => min(ys, min(accu, x))
}
(这是尾递归)
现在需要一个最小版本,它返回一个列出最小值的列表。以下函数返回一个列表,其头部是最小值,尾部包含原始列表的其余部分:
def minl(xs: List[Int]): List[Int] = minl(xs, List(Int.MaxValue))
def minl(xs: List[Int],accu:List[Int]): List[Int] = xs match {
// accu always contains min as head
case Nil => accu take accu.length-1
case x :: ys => minl(ys,
if (x<accu.head) x::accu else accu.head :: x :: accu.tail )
}
使用此选择排序可以递归写为:
def ssort(xs: List[Int], accu: List[Int]): List[Int] = minl(xs) match {
case Nil => accu
case min :: rest => ssort(rest, min::accu)
}
(撤销订单)。在具有10000个列表元素的测试中,该算法仅比通常的命令式算法慢约4倍。
答案 6 :(得分:1)
尽管在编写Scala时,我过去喜欢函数式编程风格(通过组合器或递归)而不是命令式风格(通过变量和迭代),这个时间,针对这个特定问题,旧学校命令式嵌套循环从而使代码更简单,更高效。
我不认为回归命令式风格是某些类问题的错误,例如排序算法通常会将输入缓冲区转换为适当的位置,而不是产生新的集合。
我的解决方案是:
package bitspoke.algo
import scala.math.Ordered
import scala.collection.mutable.Buffer
abstract class Sorter[T <% Ordered[T]] {
// algorithm provided by subclasses
def sort(buffer : Buffer[T]) : Unit
// check if the buffer is sorted
def sorted(buffer : Buffer[T]) = buffer.isEmpty || buffer.view.zip(buffer.tail).forall { t => t._2 > t._1 }
// swap elements in buffer
def swap(buffer : Buffer[T], i:Int, j:Int) {
val temp = buffer(i)
buffer(i) = buffer(j)
buffer(j) = temp
}
}
class SelectionSorter[T <% Ordered[T]] extends Sorter[T] {
def sort(buffer : Buffer[T]) : Unit = {
for (i <- 0 until buffer.length) {
var min = i
for (j <- i until buffer.length) {
if (buffer(j) < buffer(min))
min = j
}
swap(buffer, i, min)
}
}
}
如您所见,为了实现参数多态,而不是使用java.lang.Comparable
,我更喜欢scala.math.Ordered
和Scala View Bounds而不是Upper Bounds。由于Scala Implicit Conversions of primitive types to Rich Wrappers,这当然有效。
您可以按如下方式编写客户端程序:
import bitspoke.algo._
import scala.collection.mutable._
val sorter = new SelectionSorter[Int]
val buffer = ArrayBuffer(3, 0, 4, 2, 1)
sorter.sort(buffer)
assert(sorter.sorted(buffer))
答案 7 :(得分:1)
Scala中选择排序的简单功能程序
def selectionSort(list:List[Int]):List[Int] = {
@tailrec
def selectSortHelper(list:List[Int], accumList:List[Int] = List[Int]()): List[Int] = {
list match {
case Nil => accumList
case _ => {
val min = list.min
val requiredList = list.filter(_ != min)
selectSortHelper(requiredList, accumList ::: List.fill(list.length - requiredList.length)(min))
}
}
}
selectSortHelper(list)
}
答案 8 :(得分:0)
您可能希望尝试使用递归替换while循环,因此,您有两个地方可以创建新的递归函数。
那会开始摆脱一些变种。
这对我来说可能是最艰难的一课,试图更多地转向FP。
我毫不犹豫地在这里展示解决方案,因为我认为你先试试会更好。
但是,如果可能的话,你应该使用尾递归,以避免堆栈溢出问题(如果你要排序一个非常非常大的列表)。
答案 9 :(得分:0)
以下是我对此问题的看法:SelectionSort.scala
def selectionsort[A <% Ordered[A]](list: List[A]): List[A] = {
def sort(as: List[A], bs: List[A]): List[A] = as match {
case h :: t => select(h, t, Nil, bs)
case Nil => bs
}
def select(m: A, as: List[A], zs: List[A], bs: List[A]): List[A] =
as match {
case h :: t =>
if (m > h) select(m, t, h :: zs, bs)
else select(h, t, m :: zs, bs)
case Nil => sort(zs, m :: bs)
}
sort(list, Nil)
}
有两个内部函数:sort
和select
,它们代表原始算法中的两个循环。第一个函数sort
遍历元素并为每个元素调用select
。当源列表为空时,它返回bs
列表作为结果,最初为Nil
。 sort
函数尝试在源列表中搜索最大值(不是最小值,因为我们以反向顺序构建结果列表)元素。它假设默认情况下最大值为head
,然后只需用适当的值替换它。
这是Scala中Selection Sort的100%功能实现。
答案 10 :(得分:0)
这是我的解决方案
def sort(list: List[Int]): List[Int] = {
@tailrec
def pivotCompare(p: Int, l: List[Int], accList: List[Int] = List.empty): List[Int] = {
l match {
case Nil => p +: accList
case x :: xs if p < x => pivotCompare(p, xs, accList :+ x)
case x :: xs => pivotCompare(x, xs, accList :+ p)
}
}
@tailrec
def loop(list: List[Int], accList: List[Int] = List.empty): List[Int] = {
list match {
case x :: xs =>
pivotCompare(x, xs) match {
case Nil => accList
case h :: tail => loop(tail, accList :+ h)
}
case Nil => accList
}
}
loop(list)
}