学習の意図とこれに加えて 質問 、私はリスト(またはコレクション)が順序付けられているかどうかをチェックするアルゴリズムの明示的な再帰の慣用的な代替案に興味を持っていました。 (ここでは、演算子を使用して比較し、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個の要素すべてを比較するという欠点があります。フォールドを「停止」して、これをより良いソリューションにする方法はありますか?
他の(エレガントな)選択肢はありますか?
「イディオマティック」とは、McBrideとPatersonの「イディオム」についての論文で話していると思います 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
ファンクター、または「イディオム」、または「強力な緩いモノイドファンクター」でトラバースできます。たまたま、すべてのMonoid
がそのようなイディオムを無料で誘発します(論文のセクション4を参照)。
モノイドは、あるタイプに対する単なる連想二項演算であり、その演算の単位元です。 Semigroup[Lte[A]]
(半群はモノイドと同じですが、単位元がない点が異なります)を定義しています。この連想演算は、2つの値のうち小さい方を追跡し、左側の値が右側の値よりも小さいかどうかを追跡します。そしてもちろん、Option[Lte[A]]
は、半群によって自由に生成されたモノイドです。
最後に、foldMapDefault
は、モノイドによって誘導されるイディオムでコレクションタイプT
をトラバースします。結果b
には、各値が次のすべての値よりも小さい場合(コレクションが順序付けられていることを意味します)はtrueが含まれ、None
に要素がない場合はT
が含まれます。空のT
は慣例によりソートされるため、true
の最後のfold
の2番目の引数として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.
そしてそれがコレクションが注文されているかどうかを検出するための慣用的なトラバーサルの方法です。
これは、最初の要素が故障した後に終了します。したがって、それはうまく機能するはずですが、私はそれをテストしていません。私の意見では、それははるかにエレガントでもあります。 :)
def sorted(l:List[Int]) = l.view.Zip(l.tail).forall(x => x._1 <= x._2)
実際のところ、これはキム・ステベルのものと非常によく似ています。
def isOrdered(list: List[Int]): Boolean = (
list
sliding 2
map {
case List(a, b) => () => a < b
}
forall (_())
)
コメントでmissingfaktorのエレガントな解決策を見逃した場合 上記 :
(l, l.tail).zipped.forall(_ <= _)
このソリューションは非常に読みやすく、最初の順序が正しくない要素で終了します。
再帰バージョンは問題ありませんが、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)}
むしろ簡潔で、おそらくそれは慣用的なものと言えるかもしれませんが、私にはわかりません。しかし、それはあまり明確ではないと思います。さらに、これらの方法はすべて、命令型または末尾再帰型よりもはるかにパフォーマンスが悪い可能性があり、それを購入するような追加の明確さはないと思います。
また、 この質問 もご覧ください。
反復を停止するには、 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
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.
リストを2つの部分に分割し、最初の部分の最後が2番目の部分の最初よりも低いかどうかを確認する場合。もしそうなら、あなたは両方の部分のために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)
}
}
そして今、並列で、take/drop
の代わりにsplitAt
を使用しています:
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)
}
}
私のソリューションはmissingfaktorのソリューションと注文と組み合わせます
def isSorted[T](l: Seq[T])(implicit ord: Ordering[T]) = (l, l.tail).zipped.forall(ord.lt(_, _))
独自の比較方法を使用できます。例えば。
isSorted(dataList)(Ordering.by[Post, Date](_.lastUpdateTime))