2017-10-20 13 views
2

이 문제를 스칼라로 접근하는 관용적 인 방법을 알고 싶습니다.폴드 할 부울 테스트를 사용하는 방법

시작 날짜와 종료 날짜와 그 사이에 날짜 모음이 있으면 지정된 날짜 모음에 간격 날짜가없는 시작 날짜부터 끝 날짜까지 필요한 모든 날짜가 포함되어 있는지 확인하십시오.

유형 서명 :

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 
} 

난 그냥 내가 수집 사이에 걸쳐 배 반복으로의 가치를 높일 수 있습니다 인덱스 시작 매개 변수를하는 방법을 알아낼 필요가있다. 또는 접는 것이 내가 사용하기로되어있는 것이 아닐 수도 있습니다.

도움이 될 것입니다.

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

, 당신의 결과를 ingnoring 참고 :

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

을하지만 이런 종류의 더 나은 슬라이딩 사용 및 조합 FORALL을 확인하십시오

+3

(추가 처리를 중지하도록 예외를 던질 수 있지만, 이럴, 나쁜 방법). 꼬리 재귀와 함께 가야합니다. –

+0

@ArtavazdBalayan 맞아. 스칼라는 매우 예외적이다. 내가보기에 기쁘다는 특성은 Java에서 상속받지 않았다. – franklin

+0

'return status'를 단지'status'로 바꿀 수 있습니다. 'return'은 비 - 지역 출구가 필요한 경우에만 필요하며 Scala에서는 거의 사용되지 않습니다. 많은 스칼라 프로그래머는 그대로'return'을 사용하지 않습니다. –

답변

3

당신은 축적에 이전 날짜 시간 항목을 전달할 수 있습니다 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] = ??? 

그런 다음 당신은 당신의 기능을 쓸 수있다 :이 기능은 betweens, 치료의 순서에 대해 어떤 요구 사항을 부과하지

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인지 확인하십시오.

@ 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입니다. Java 8에서 DateTime 표현을 위해 ZonedDateTime을 사용하고 있습니다. 여기 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)}") 

    } 
}