用于检查集合是否已订购的惯用法构造

时间:2011-10-21 16:33:02

标签: scala recursion fold idiomatic

为了学习并进一步了解question,我仍然对用于检查列表(或集合)是否被排序的算法的显式递归的惯用替代品感到好奇。 (我在这里通过使用运算符进行比较和Int作为类型来保持简单;我想在深入研究它的泛型之前先查看算法)

基本的递归版本(由@Luigi Plinge撰写):

def isOrdered(l:List[Int]): Boolean = l match {
  case Nil => true
  case x :: Nil => true
  case x :: xs => x <= xs.head && isOrdered(xs)
}

表现不佳的惯用方法是:

def isOrdered(l: List[Int]) = l == l.sorted

使用fold的替代算法:

def isOrdered(l: List[Int]) =
  l.foldLeft((true, None:Option[Int]))((x,y) =>
    (x._1 && x._2.map(_ <= y).getOrElse(true), Some(y)))._1

它的缺点是它将比较列表中的所有n个元素,即使它在找到第一个无序元素之后可以提前停止。有没有办法“停止”折叠,从而使其成为更好的解决方案?

任何其他(优雅)替代品?

9 个答案:

答案 0 :(得分:63)

这将在第一个无序的元素之后退出。因此它应该表现良好,但我还没有测试过。在我看来,它也更加优雅。 :)

def sorted(l:List[Int]) = l.view.zip(l.tail).forall(x => x._1 <= x._2)

答案 1 :(得分:38)

通过“惯用语”,我假设你在他们的论文Applicative Programming With Effects中谈论麦克布赖德和帕特森的“成语”。 :O)

以下是如何使用他们的习语来检查是否订购了一个集合:

import scalaz._
import Scalaz._

case class Lte[A](v: A, b: Boolean)

implicit def lteSemigroup[A:Order] = new Semigroup[Lte[A]] {
  def append(a1: Lte[A], a2: => Lte[A]) = {
    lazy val b = a1.v lte a2.v
    Lte(if (!a1.b || b) a1.v else a2.v, a1.b && b && a2.b)
  }
}

def isOrdered[T[_]:Traverse, A:Order](ta: T[A]) =
  ta.foldMapDefault(x => some(Lte(x, true))).fold(_.b, true)

以下是其工作原理:

存在T[A]实现的任何数据结构Traverse[T]都可以使用Applicative仿函数或“惯用法”或“强疏松的monoidal仿函数”遍历。事实恰恰相反,每个Monoid都会免费引入这样一个成语(见本文第4节)。

monoid只是某种类型的关联二元运算,以及该运算的标识元素。我正在定义Semigroup[Lte[A]](半群与monoid相同,除了没有identity元素),其关联操作跟踪两个值中的较小值以及左值是否小于正确值。当然Option[Lte[A]]只是我们半群自由生成的幺半群。

最后,foldMapDefault遍历由monoid引起的习语中的集合类型T。如果每个值小于以下所有值(表示集合已订购),则结果b将包含true;如果None没有元素,则结果T将包含T。由于空true按惯例排序,因此我们将fold作为第二个参数传递给Option的最终scala> val b = isOrdered(List(1,3,5,7,123)) b: Boolean = true scala> val b = isOrdered(Seq(5,7,2,3,6)) b: Boolean = false scala> val b = isOrdered(Map((2 -> 22, 33 -> 3))) b: Boolean = true scala> val b = isOrdered(some("hello")) b: Boolean = true

作为奖励,这适用于所有可遍历的集合。演示:

import org.scalacheck._

scala> val p = forAll((xs: List[Int]) => (xs /== xs.sorted) ==> !isOrdered(xs))
p:org.scalacheck.Prop = Prop

scala> val q = forAll((xs: List[Int]) => isOrdered(xs.sorted))
q: org.scalacheck.Prop = Prop

scala> p && q check
+ OK, passed 100 tests.

测试:

{{1}}

并且 如何执行惯用遍历以检测集合是否已订购。

答案 2 :(得分:8)

我正在使用这个,这与Kim Stebel非常相似,事实上。

def isOrdered(list: List[Int]): Boolean = (
  list 
  sliding 2 
  map { 
    case List(a, b) => () => a < b 
  } 
  forall (_())
)

答案 3 :(得分:5)

如果您在评论above中错过了missfaktor的优雅解决方案:

(l, l.tail).zipped.forall(_ <= _)

此解决方案非常易读,将在第一个无序元素上退出。

答案 4 :(得分:2)

递归版本很好,但仅限于List(如果更改有限,它会在LinearSeq上运行良好。)

如果它是在标准库中实现的(有意义的话),它可能会在IterableLike中完成并且具有完全必要的实现(参见例如方法find

您可以使用foldLeft中断return(在这种情况下,您只需要前一个元素而不是布尔值)

import Ordering.Implicits._
def isOrdered[A: Ordering](seq: Seq[A]): Boolean = {
  if (!seq.isEmpty)
    seq.tail.foldLeft(seq.head){(previous, current) => 
      if (previous > current) return false; current
    }
  true
}

但是我没有看到它比命令式实现更好或甚至是惯用语。我不确定实际上我不会称之为必要。

另一种解决方案可能是

def isOrdered[A: Ordering](seq: Seq[A]): Boolean = 
  ! seq.sliding(2).exists{s => s.length == 2 && s(0) > s(1)}

相当简洁,也许这可以被称为惯用,我不确定。但我认为这不太清楚。而且,所有这些方法可能会比命令式或尾部递归版本执行得更糟,我认为它们没有任何额外的清晰度可以购买。

另外,您应该查看this question

答案 5 :(得分:1)

要停止迭代,您可以使用Iteratee

import scalaz._
import Scalaz._
import IterV._
import math.Ordering
import Ordering.Implicits._

implicit val ListEnumerator = new Enumerator[List] {
  def apply[E, A](e: List[E], i: IterV[E, A]): IterV[E, A] = e match {
    case List() => i
    case x :: xs => i.fold(done = (_, _) => i,
                           cont = k => apply(xs, k(El(x))))
  }
}

def sorted[E: Ordering] : IterV[E, Boolean] = {
  def step(is: Boolean, e: E)(s: Input[E]): IterV[E, Boolean] = 
    s(el = e2 => if (is && e < e2)
                   Cont(step(is, e2))
                 else
                   Done(false, EOF[E]),
      empty = Cont(step(is, e)),
      eof = Done(is, EOF[E]))

  def first(s: Input[E]): IterV[E, Boolean] = 
    s(el = e1 => Cont(step(true, e1)),
      empty = Cont(first),
      eof = Done(true, EOF[E]))

  Cont(first)
}


scala> val s = sorted[Int]
s: scalaz.IterV[Int,Boolean] = scalaz.IterV$Cont$$anon$2@5e9132b3

scala> s(List(1,2,3)).run
res11: Boolean = true

scala> s(List(1,2,3,0)).run
res12: Boolean = false

答案 6 :(得分:0)

如果将List拆分为两部分,并检查第一部分的最后一部分是否低于第二部分的第一部分。如果是这样,您可以检查两个部分的并行。这里是原理图,首先是不平行的:

def isOrdered (l: List [Int]): Boolean = l.size/2 match {
  case 0 => true 
  case m => {
    val  low = l.take (m)
    val high = l.drop (m)
    low.last <= high.head && isOrdered (low) && isOrdered (high) 
  }
}

现在使用并行,并使用splitAt代替take/drop

def isOrdered (l: List[Int]): Boolean = l.size/2 match {
  case 0 => true 
  case m => {
    val (low, high) = l.splitAt (m)
    low.last <= high.head && ! List (low, high).par.exists (x => isOrdered (x) == false) 
  }
}

答案 7 :(得分:0)

def isSorted[A <: Ordered[A]](sequence: List[A]): Boolean = {
  sequence match {
    case Nil        => true
    case x::Nil     => true
    case x::y::rest => (x < y) && isSorted(y::rest)
  }
}

Explain how it works. 

答案 8 :(得分:0)

我的解决方案结合了missingfaktor的解决方案和订购

def isSorted[T](l: Seq[T])(implicit ord: Ordering[T]) = (l, l.tail).zipped.forall(ord.lt(_, _))

您可以使用自己的比较方法。 E.g。

isSorted(dataList)(Ordering.by[Post, Date](_.lastUpdateTime))