如何使用fold进行布尔测试

时间:2017-10-20 03:14:38

标签: scala datetime fold

我想知道在scala中解决此问题的惯用方法。

给定开始日期和结束日期以及两者之间的日期集合,确定给定的日期集合是否包含从开始日期到结束日期所需的所有日期,其间没有间隔日期。

类型签名:

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
}

我只需要弄清楚如何索引start参数,这样我可以在折叠迭代到集合之间时增加它的值。或者折叠不是我应该使用的东西。

任何帮助将不胜感激!

4 个答案:

答案 0 :(得分: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 ..
 }

另外,请注意,由于IndexedSeq是不可变的,因此您需要在排序之间进行输入。修复 - 使用另一个val:

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

答案 1 :(得分: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)

答案 2 :(得分: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

答案 3 :(得分:0)

tail recursion的解决方案。我使用Java 8中的ZonedDateTime进行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)}")

    }  
}