37

With the intention of learning and further to this question, I've remained curious of the idiomatic alternatives to explicit recursion for an algorithm that checks whether a list (or collection) is ordered. (I'm keeping things simple here by using an operator to compare and Int as type; I'd like to look at the algorithm before delving into the generics of it)

The basic recursive version would be (by @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)
}

A poor performing idiomatic way would be:

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

An alternative algorithm using 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

It has the drawback that it will compare for all n elements of the list even if it could stop earlier after finding the first out-of-order element. Is there a way to "stop" fold and therefore making this a better solution?

Any other (elegant) alternatives?

Community
  • 1
  • 1
maasg
  • 37,100
  • 11
  • 88
  • 115
  • Using Booleans as values from `if` statements makes me sad, and Luigi's version was slightly off (detecting reverse order). Fixed it for you. – Apocalisp Oct 22 '11 at 08:14
  • @Apocalisp your "corrected" version returns true on `isOrdered(List(1,2,1,2))`, which is why I rolled it back, and is why I'm changing it back again... – Luigi Plinge Oct 22 '11 at 09:52
  • 3
    It should be noted that `l == l.sorted` might not work for lists of objects other than ints if the sorting algorithm used isn't stable. – Kim Stebel Jul 03 '12 at 14:31

9 Answers9

63

This will exit after the first element that is out of order. It should thus perform well, but I haven't tested that. It's also a lot more elegant in my opinion. :)

def sorted(l:List[Int]) = l.view.zip(l.tail).forall(x => x._1 <= x._2)
thSoft
  • 21,755
  • 5
  • 88
  • 103
Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
  • @missingfaktor: won't that always go through the whole list? – Kim Stebel Oct 21 '11 at 17:57
  • No, it won't. See the implementation of `forall` [here](https://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_1_final/src//library/scala/Tuple2.scala#L119). – missingfaktor Oct 21 '11 at 18:15
  • 2
    this will fail on an empty list with `Exception: java.lang.UnsupportedOperationException: tail of empty list`, but the fix is pretty simple: `def sorted(l:List[Int]) = l.isEmpty || l.view.zip(l.tail).forall(x => x._1 <= x._2)` – Mortimer Nov 04 '13 at 20:17
  • This comment is added a couple of years later using Scala 10.2 . For efficiency, we should use a stream:`l.view.zip(l.tail).toStream.forall(x => x._1 <= x._2)`. Then the zip operation won't be carried out on the full list if it fails early. – toto2 Nov 14 '13 at 16:16
  • 1
    Here is a broader signature of this method: `def isSorted[T <% Ordered[T]](l: Iterable[T])` – Ivan Balashov Jul 15 '16 at 06:21
39

By "idiomatic", I assume you're talking about McBride and Paterson's "Idioms" in their paper Applicative Programming With Effects. :o)

Here's how you would use their idioms to check if a collection is ordered:

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)

Here's how this works:

Any data structure T[A] where there exists an implementation of Traverse[T], can be traversed with an Applicative functor, or "idiom", or "strong lax monoidal functor". It just so happens that every Monoid induces such an idiom for free (see section 4 of the paper).

A monoid is just an associative binary operation over some type, and an identity element for that operation. I'm defining a Semigroup[Lte[A]] (a semigroup is the same as a monoid, except without the identity element) whose associative operation tracks the lesser of two values and whether the left value is less than the right value. And of course Option[Lte[A]] is just the monoid generated freely by our semigroup.

Finally, foldMapDefault traverses the collection type T in the idiom induced by the monoid. The result b will contain true if each value was less than all the following ones (meaning the collection was ordered), or None if the T had no elements. Since an empty T is sorted by convention, we pass true as the second argument to the final fold of the Option.

As a bonus, this works for all traversable collections. A demo:

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

A test:

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.

And that's how you do idiomatic traversal to detect if a collection is ordered.

Apocalisp
  • 34,834
  • 8
  • 106
  • 155
  • 14
    Any sufficiently abstract code is indistinguishable from magic. – Tom Crockett Oct 22 '11 at 00:33
  • 1
    Whether or not the whole structure is traversed depends on the strictness of the implementation of `Traverse` and `Applicative`. – Apocalisp Oct 22 '11 at 08:34
  • 2
    +1 for the paper and applying its knowledge to answer this question – maasg Oct 25 '11 at 08:47
  • @Apocalisp & everyone - I would welcome your comments, positive or negative, on [this Meta question on "advanced" SO answers](http://meta.stackexchange.com/questions/134333/how-to-avoid-scaring-off-beginners-with-advanced-answers), which uses this answer as an example. Please note that I do recognise that this answer is useful, and I did upvote it, so I hope no-one is offended. – Robin Green Jun 03 '12 at 07:51
  • 3
    @RobinGreen Sure, no problem. I have downvoted your question. :) – Apocalisp Jun 03 '12 at 21:26
8

I'm going with this, which is pretty similar to Kim Stebel's, as a matter of fact.

def isOrdered(list: List[Int]): Boolean = (
  list 
  sliding 2 
  map { 
    case List(a, b) => () => a < b 
  } 
  forall (_())
)
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
6

In case you missed missingfaktor's elegant solution in the comments above:

Scala < 2.13.0

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

Scala 2.13.x+

l.lazyZip(l.tail).forall(_ <= _)

This solution is very readable and will exit on the first out-of-order element.

Cory Klein
  • 51,188
  • 43
  • 183
  • 243
2

The recursive version is fine, but limited to List (with limited changes, it would work well on LinearSeq).

If it was implemented in the standard library (would make sense) it would probably be done in IterableLike and have a completely imperative implementation (see for instance method find)

You can interrupt the foldLeft with a return (in which case you need only the previous element and not boolean all along)

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
}

but I don't see how it is any better or even idiomatic than an imperative implementation. I'm not sure I would not call it imperative actually.

Another solution could be

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

Rather concise, and maybe that could be called idiomatic, I'm not sure. But I think it is not too clear. Moreover, all of those methods would probably perform much worse than the imperative or tail recursive version, and I do not think they have any added clarity that would buy that.

Also you should have a look at this question.

Community
  • 1
  • 1
Didier Dupont
  • 29,398
  • 7
  • 71
  • 90
1

To stop iteration, you can use 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
Apocalisp
  • 34,834
  • 8
  • 106
  • 155
IttayD
  • 28,271
  • 28
  • 124
  • 178
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. 
james
  • 1
  • Won't compile on anything beyond an empty `List`. – jwvh Dec 08 '16 at 22:56
  • Can you explain your answer instead of just posting code, so that others can learn from it? – Robert Dec 08 '16 at 23:38
  • Welcome to Stack Overflow! While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. Code-only answers are discouraged. – Ajean Dec 09 '16 at 16:43
0

my solution combine with missingfaktor's solution and Ordering

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

and you can use your own comparison method. E.g.

isSorted(dataList)(Ordering.by[Post, Date](_.lastUpdateTime))
clockrun
  • 21
  • 2
0

If you split the List into two parts, and check whether the last of the first part is lower than the first of the second part. If so, you could check in parallel for both parts. Here the schematic idea, first without parallel:

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) 
  }
}

And now with parallel, and using splitAt instead of 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) 
  }
}
user unknown
  • 35,537
  • 11
  • 75
  • 121