web-dev-qa-db-ja.com

コレクションが注文されているかどうかを確認するための慣用的な構造

学習の意図とこれに加えて 質問 、私はリスト(またはコレクション)が順序付けられているかどうかをチェックするアルゴリズムの明示的な再帰の慣用的な代替案に興味を持っていました。 (ここでは、演算子を使用して比較し、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個の要素すべてを比較するという欠点があります。フォールドを「停止」して、これをより良いソリューションにする方法はありますか?

他の(エレガントな)選択肢はありますか?

37
maasg

「イディオマティック」とは、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.

そしてそれがコレクションが注文されているかどうかを検出するための慣用的なトラバーサルの方法です。

39
Apocalisp

これは、最初の要素が故障した後に終了します。したがって、それはうまく機能するはずですが、私はそれをテストしていません。私の意見では、それははるかにエレガントでもあります。 :)

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

実際のところ、これはキム・ステベルのものと非常によく似ています。

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

コメントでmissingfaktorのエレガントな解決策を見逃した場合 上記

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

このソリューションは非常に読みやすく、最初の順序が正しくない要素で終了します。

5
Cory Klein

再帰バージョンは問題ありませんが、Listに制限されています(変更が制限されているため、LinearSeqでうまく機能します)。

標準ライブラリに実装されている場合(意味があります)、おそらくIterableLikeで実行され、完全に命令型の実装があります(たとえばメソッドfindを参照)。

foldLeftreturnで中断できます(この場合、必要なのは前の要素のみで、ブール値は必要ありません)

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

むしろ簡潔で、おそらくそれは慣用的なものと言えるかもしれませんが、私にはわかりません。しかし、それはあまり明確ではないと思います。さらに、これらの方法はすべて、命令型または末尾再帰型よりもはるかにパフォーマンスが悪い可能性があり、それを購入するような追加の明確さはないと思います。

また、 この質問 もご覧ください。

2
Didier Dupont

反復を停止するには、 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
1
IttayD
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. 
0
james

リストを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) 
  }
}
0
user unknown

私のソリューションは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))
0
clockrun