2017-10-20 7 views
2

Je voudrais connaître la manière idiomatique d'aborder ce problème dans scala. Déterminer si la collection de dates donnée contient toutes les dates nécessaires pour passer de la date de début à la date de fin sans dates intermédiaires entre une date de début, une date de fin et un ensemble de dates intermédiaires.Comment utiliser fold pour effectuer des tests booléens

signature Type:

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

Le "normal" ou "non fonctionnel" façon de le faire serait quelque chose comme ceci:

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 
} 

Comment puis-je faire cela en utilisant un pli ?

Voici mon processus de pensée à ce jour:

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 
} 

J'ai juste besoin de comprendre comment indexer le paramètre de démarrage, je peux augmenter la valeur de celui-ci comme le pli itère sur la entre la collecte. Ou il est possible que le pli ne soit pas du tout ce que je suis supposé utiliser.

Toute aide serait appréciée!

+3

Si vous devez arrêter votre boucle avant de passer par toute la collection, pli ne va pas aider (vous pouvez lancer une exception pour arrêter un traitement ultérieur, mais, à mon humble avis, il est mauvais état). Vous devriez aller avec la récursivité de la queue. –

+0

@ArtavazdBalayan Droite. Scala est très «Exception» adverse; une caractéristique que je suis heureux de voir n'a pas été héritée de Java. – franklin

+0

Vous pouvez remplacer 'return status' avec simplement' status'. 'return' est seulement nécessaire si vous avez besoin de faire une sortie non locale, et est rarement utilisé dans Scala. Beaucoup de programmeurs Scala n'utilisent littéralement jamais 'return'. –

Répondre

3

Vous pouvez passer l'élément précédent DateTime dans l'accumulateur:

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

Mais pour ce genre de vérifier que vous une meilleure utilisation de glissement et forall combinaison:

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

En outre, notez que vous ingnoring le résultat de entre le tri puisque IndexedSeq est immuable. Fix - utilisez un autre val:

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

IndexedSeq est immuable. Duh. Fixé cela dans l'OP. Merci! – franklin

1

Je pense qu'un pli n'est pas nécessaire, cela rend les choses trop difficiles.

Supposons que vous ayez les fonctions suivantes:

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

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

Ensuite, vous pouvez écrire votre fonction comme suit:

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 
} 

Notez que cette fonction impose aucune exigence quant à l'ordre des Betweens, traite les éléments comme un ensemble, nécessitant seulement que chaque jour soit disponible quelque part. Pour mettre en œuvre normalizeDateTime(...), vous pourriez obtenir quelque chose d'aussi simple que dt.toDateMidnight, mais vous devriez penser un peu à Chronology et les problèmes de fuseau horaire. Il est essentiel que DateTime objets que vous voulez représenter un jour normaliser toujours à la même DateMidnight. Pour mettre en œuvre requiredBetweens(...), vous pouvez utiliser un Stream et takeWhile(...) pour une solution élégante. Vous pourriez vouloir exiger que (end isAfter start).

1

Je voudrais utiliser un filtre, puis zip et prendre la différence, les dates devraient toujours être un jour d'intervalle, alors vérifiez qu'ils sont tous 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 

Vous devez également vérifier qu'aucune date sont manquants:

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

Vous peut aussi être en mesure de le faire plus simplement en utilisant l'objet Range:

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

Cela devrait fonctionner, mais peut nécessiter un léger ajustement pour 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

Solution avec tail recursion. J'utilise ZonedDateTime à partir de Java 8 pour la représentation DateTime. Voici 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)}") 

    } 
}