2017-10-20 14 views
2

スカラーでこの問題に近づく方法を知りたいのですが、fold to doブール・テストの使用方法

開始日と終了日とその間の日付の集合を指定すると、与えられた日付の集合に開始日から終了日までに必要なすべての日付がギャップ日付なしで含まれているかどうかを判断します。

タイプの署名:

def checkDate(start: DateTime, end: DateTime, between: IndexedSeq[DateTime]): Boolean

これを行うには、「正常」か「ない機能」の方法は、このようなものになるだろう:どのように私はフォールドを使用してこれを行うことができます

def checkDate(start: DateTime, end: DateTime, between: IndexedSeq[DateTime]): Boolean = { 
    i = 1 
    status = true 
    while(start != end) { 
    d = start.plusDays(i) 
    if (!between.contains(d) { 
     status = false 
     break 
    } 
    i += 1 
    } 
    return status 
} 

はここで、これまでの私の思考プロセスです:

def checkDate(start: DateTime, end: DateTime, between: IndexedSeq[DateTime]): Boolean = { 

    // A fold will assume the dates are in order and move left (or right) 
    // This means the dates must be sorted. 
    val sorted = between.sortBy(_.getMillis()) 

    val a = sorted.foldLeft(List[Boolean]) { 
    (acc, current) => { 
     // How do I access an iterable version of the start date? 
     if (current == ??) { 
     acc :: true 
     } else false 
    } 
    } 

    // If the foldLeft produced any values that could NOT be matched 
    // to the between list, then the start date does not have an 
    // uninterrupted path to the end date. 
    if (a.count(_ == false) > 0) false 
    else true 
} 

私はちょうどので、私はコレクションの間の上に折り目を反復処理としての価値を高めることができ、インデックスの開始パラメータをする方法を理解する必要があります。それとも、折り畳みが私がまったく使うべきものではない可能性があります。

助けていただけたら幸いです!

+3

、倍は(私見、それは悪い方法です、あなたはさらに処理を停止するために例外をスローすることができますが、)助けにはなりません。あなたは尾の再帰と一緒に行かなければなりません。 @ArtavazdBalayan右。 –

+0

Scalaは非常に「例外」です。私が見て嬉しいのはJavaから継承されたものではありません。 – franklin

+0

'return status'を' status'だけで置き換えることができます。 'return'は、非ローカル終了を行う必要がある場合にのみ必要で、Scalaではめったに使用されません。多くのScalaプログラマは文字通り 'return'を使用しません。 –

答えて

3

あなたは、アキュムレータ内の前のDateTime値を渡すことができます。

val a = sortedBetween.foldLeft((List[Boolean](), start)) { 
    case ((results, prev), current) => { 
    ... calculate res here ... 
    (results ++ List(res), current) 
    } 
} 

しかし、チェックのこの種のために、あなたはより良い使用スライディングと組み合わせFORALL:また

sortedBetween.sliding(2).forall { 
    case List(prev,cur) => ..do the check here .. 
} 

、あなたは結果をingnoringことに注意してくださいIndexedSeqは不変であるため、ソートの間。修正 - 別の値を使用:

val sortedBetween = between.sortBy(_.getMillis()) 
+0

IndexedSeqは不変です。ダー。 OPでそれを修正しました。ありがとう! – franklin

1

私は倍が必要ではないと思う、それはものを作りすぎている。

を使用すると、以下の機能を持っていたとします

private def normalizeDateTime(dt : DateTime) : DateMidnight = ??? 

private def requiredBetweens(start : DateMidnight, end : DateMidnight) : Seq[DateMidnight] = ??? 

次のように続いて、あなたの関数を書くことができます:この関数はビトウィーンズの順序について何ら要求を課さないこと

def checkDate(start: DateTime, end: DateTime, between: IndexedSeq[DateTime]): Boolean = { 
    val startDay = normalizeDateTime(start) 
    val endDay = normalizeDateTime(end) 
    val available = between.map(normalizeDateTime).toSet 
    val required = requiredBetweens(startDay, endDay).toSet 
    val unavailable = (required -- available) 
    unavailable.isEmpty 
} 

注意、お菓子をセットとしての要素で、毎日どこかで利用できるようにするだけです。

normalizeDateTime(...)を実装するには、dt.toDateMidnightという単純なものがありますが、少し具体的にはChronologyとタイムゾーンの問題について考える必要があります。日を表すことを意味するDateTimeオブジェクトは常に同じDateMidnightに正規化することが重要です。

requiredBetweens(...)を実装するには、StreamtakeWhile(...)を優雅なソリューションとして使用することを検討してください。その場合は(end isAfter start)が必要です。

1

私はフィルターを使い、差をつけて、日付は常に1日離れていなければならないので、すべてが1であることを確認してください。

@ val ls = Array(1, 2, 3, 4, 5, 6, 7) // can use dates in the same way 
ls: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7) 

@ val ls2 = ls.filter { i => (2 < i) && (i < 6) } 
ls2: Array[Int] = Array(3, 4, 5) 

@ ls2.zip(ls2.drop(1)) 
res21: Array[(Int, Int)] = Array((3, 4), (4, 5)) 

@ ls2.zip(ls2.drop(1)).map { case (x, y) => y-x } 
res22: Array[Int] = Array(1, 1) 

@ ls2.zip(ls2.drop(1)).map { case (x, y) => y-x }.forall { _ == 1 } 
res23: Boolean = true 

あなたはまた何の日付が不足していないことを確認する必要があります:

@ ls2.length == 6 - 2 - 1 // beware off-by-one errors 
res25: Boolean = true 

も、Rangeオブジェクトを使用して、より簡単にこれを行うことができるかもしれません:

@ ls2.zipAll(3 to 5 by 1, 0, 0).forall { case (x, y) => x == y } 
res46: Boolean = true 

これはうまくいくはずですが、DateTimeの設定を微調整する必要があります...

@ val today = LocalDate.now 
today: LocalDate = 2017-10-19 

@ val a = (0 to 9).reverse.map { today.minusDays(_) } 
a: collection.immutable.IndexedSeq[LocalDate] = Vector(2017-10-10, 2017-10-11, 2017-10-12, 2017-10-13, 2017-10-14, 2017-10-15, 2017-10-16, 2017-10-17, 2017-10-18, 2017-10-19) 

@ a.zip(a.drop(1)).map { case (x, y) => x.until(y) }.forall { _ == Period.ofDays(1) } 
res71: Boolean = true 
0

解決策tail recursionで。私はZonedDateTimeをJava 8からDateTime表現に使用しています。ここでonline version on codepad.remoteinterview.ioです:あなたは、コレクション全体を通過する前にループを停止する必要がある場合

import scala.annotation.tailrec 
import java.time.ZonedDateTime 

object TailRecursionExample { 
    def checkDate(start: ZonedDateTime, end: ZonedDateTime, 
        between: Seq[ZonedDateTime]): Boolean = { 

     // We have dates in range (inclusive) [start, end] with step = 1 day 
     // All these days should be in between collection 

     // set for fast lookup 
     val set = between.toSet 

     @tailrec 
     def checkDate(curr: ZonedDateTime, iterations: Int): (Int, Boolean) = { 
      if (curr.isAfter(end)) (iterations, true) 
      else if (set.contains(curr)) checkDate(curr.plusDays(1), iterations + 1) 
      else (iterations, false) 

     } 

     val (iterations, result) = if (start.isAfter(end)) 
      (0, false) 
     else 
      checkDate(start, 0) 

     println(s"\tNum of iterations: $iterations") 
     result 
    } 

    def main(args: Array[String]): Unit = { 
     testWhenStartIsAfterEnd() 
     println 

     testWhenStartIsBeforeEnd() 
     println 

     testWhenStartIsBeforeEndButBetweenSkipOneDay() 
     println 
     () 
    } 

    def testWhenStartIsAfterEnd(): Unit = { 
     val start = ZonedDateTime.now().plusDays(5) 
     val end = ZonedDateTime.now() 
     val between = (0 to 5).map(i => start.plusDays(i)) 

     verboseTest("testWhenStartIsAfterEnd", start, end, between) 
    } 

    def testWhenStartIsBeforeEnd(): Unit = { 
     val start = ZonedDateTime.now().minusDays(5) 
     val end = ZonedDateTime.now() 
     val between = (0 to 5).map(i => start.plusDays(i)) 

     verboseTest("testWhenStartIsBeforeEnd", start, end, between) 
    } 

    def testWhenStartIsBeforeEndButBetweenSkipOneDay(): Unit = { 
     val start = ZonedDateTime.now().minusDays(5) 
     val end = ZonedDateTime.now() 
     val between = (1 to 5).map(i => start.plusDays(i)) 

     verboseTest("testWhenStartIsBeforeEndButBetweenSkipOneDay", start, end, between) 
    } 

    def verboseTest(name: String, start: ZonedDateTime, end: ZonedDateTime, 
        between: Seq[ZonedDateTime]): Unit = { 
     println(s"$name:") 
     println(s"\tStart: $start") 
     println(s"\tEnd: $end") 
     println(s"\tBetween: ") 
     between.foreach(t => println(s"\t\t$t")) 
     println(s"\tcheckDate: ${checkDate(start, end, between)}") 

    } 
}