Идиоматическая конструкция, чтобы проверить, упорядочена ли коллекция
С целью изучения и дальнейшего использования этого 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 элементов списка, даже если он мог остановиться раньше, после обнаружения первого элемента вне порядка. Есть ли способ "остановить" сгиб и, следовательно, сделать это лучшим решением?
Любые другие (изящные) альтернативы?
Ответы
Ответ 1
Под "идиоматическим", я полагаю, вы говорите о Макбрайде и Патерсоне "Идиомы" в своей статье Аппликативное программирование с эффектами.: О)
Здесь вы можете использовать свои идиомы, чтобы проверить, упорядочена ли коллекция:
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
, или "идиомы", или "сильного слабого моноидального функтора". Так получилось, что каждый Monoid
вызывает такую идиому бесплатно (см. Раздел 4 статьи).
Моноид - это всего лишь ассоциативная двоичная операция над некоторым типом и элемент идентификации для этой операции. Я определяю Semigroup[Lte[A]]
(полугруппа такая же, как моноид, за исключением без элемента идентификации), ассоциативная операция которой отслеживает меньшее из двух значений и является ли левое значение меньше правого значения. И, конечно, Option[Lte[A]]
- это просто моноид, свободный от нашей полугруппы.
Наконец, foldMapDefault
пересекает тип коллекции 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.
И так вы совершаете идиоматический обход, чтобы определить, упорядочена ли коллекция.
Ответ 2
Это выйдет после первого элемента, который вышел из строя. Поэтому он должен хорошо работать, но я не проверял это. На мой взгляд, это также намного элегантнее.:)
def sorted(l:List[Int]) = l.view.zip(l.tail).forall(x => x._1 <= x._2)
Ответ 3
Я собираюсь с этим, что на самом деле похоже на Ким Стебеля.
def isOrdered(list: List[Int]): Boolean = (
list
sliding 2
map {
case List(a, b) => () => a < b
}
forall (_())
)
Ответ 4
Если вы пропустили элегантное решение missingfaktor в комментариях выше:
(l, l.tail).zipped.forall(_ <= _)
Это решение очень читаемо и выйдет из первого элемента вне порядка.
Ответ 5
Рекурсивная версия в порядке, но ограничена List
(с ограниченными изменениями она будет хорошо работать на LinearSeq
).
Если бы он был реализован в стандартной библиотеке (имел бы смысл), это, вероятно, было бы сделано в IterableLike
и иметь полностью императивную реализацию (см. пример метода find
)
Вы можете прервать foldLeft
с помощью return
(в этом случае вам нужен только предыдущий элемент, а не boolean).
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)}
Скорее лаконичный, и, возможно, это можно назвать идиоматическим, я не уверен. Но я думаю, что это не слишком ясно. Более того, все эти методы, вероятно, будут работать намного хуже, чем настоятельная или хвостовая рекурсивная версия, и я не думаю, что у них есть дополнительная ясность, которая купила бы это.
Также вы должны взглянуть на этот вопрос.
Ответ 6
Чтобы остановить итерацию, вы можете использовать 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] = [email protected]
scala> s(List(1,2,3)).run
res11: Boolean = true
scala> s(List(1,2,3,0)).run
res12: Boolean = false
Ответ 7
Если вы разделите список на две части и проверьте, является ли последняя из первой части ниже первой из второй части. Если это так, вы можете проверить параллельный для обеих частей. Здесь схематическая идея, сначала без параллели:
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)
}
}
Ответ 8
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.
Ответ 9
мое решение объединяется с решением missingfaktor и Ordering
def isSorted[T](l: Seq[T])(implicit ord: Ordering[T]) = (l, l.tail).zipped.forall(ord.lt(_, _))
и вы можете использовать свой собственный метод сравнения. Например.
isSorted(dataList)(Ordering.by[Post, Date](_.lastUpdateTime))