【问题标题】:How to use fold to do boolean testing如何使用 fold 进行布尔测试
【发布时间】:2017-10-20 03:14:37
【问题描述】:

我想知道在 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 参数,这样我就可以在 fold 遍历 between 集合时增加它的值。或者,折叠可能根本不是我应该使用的。

任何帮助将不胜感激!

【问题讨论】:

  • 如果您需要在遍历整个集合之前停止循环,折叠将无济于事(您可以抛出异常以停止进一步处理,但是,恕我直言,这是不好的方法)。你应该使用尾递归。
  • @ArtavazdBalayan 对。 Scala 对Exception 很不利;我很高兴看到的一个特性不是从 Java 继承来的。
  • 您可以将 return status 替换为 statusreturn 仅在需要进行非本地退出时才需要,并且在 Scala 中很少使用。许多 Scala 程序员从不使用return

标签: scala datetime fold


【解决方案1】:

您可以在累加器中传递上一个 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())

【讨论】:

  • IndexedSeq 是不可变的。呃。在 OP 中修复了该问题。谢谢!
【解决方案2】:

我认为弃牌没有必要,这让事情变得太难了。

假设你有以下函数:

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)

【讨论】:

    【解决方案3】:

    我会使用过滤器然后 zip 并获取差异,日期应该总是相隔一天,所以检查它们都是 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
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-25
        • 2012-07-28
        • 2011-01-04
        • 1970-01-01
        • 2023-03-27
        • 2017-04-19
        • 1970-01-01
        • 2017-06-30
        相关资源
        最近更新 更多