[Résolu] Array, Map, Reduce avec l'élément suivant

iLandesiLandes Membre
octobre 2016 modifié dans API UIKit #1

Je bloque sur un problème que je visualise bien dans un boucle repeat/while mais que j'aimerais faire plus swifty.


 


J'ai un tableau ordonné de (from: NSDate, to: NSDate?). Au départ je n'ai que les from. Mais une fois trié j'aimerai que faire un mapping qui permette de remplacer la valeur to par le from de l'élément suivant. Et ne rien faire pour le dernier.


 


Petit exemple mon tableau de départ (une fois trié) :



[(from: 01/01/16, to: nil), 
(from: 02/01/16, to: nil), 
(from: 01/04/16, to: nil), 
(from: 01/09/16, to: nil)]

J'aimerai obtenir : 



[(from: 01/01/16, to: 02/01/16), 
(from: 02/01/16, to: 01/04/16), 
(from: 01/04/16, to: 01/09/16),
(from: 01/09/16, to: nil)]

 

Je ne sais pas si c'est clair ? Mais merci d'avance pour votre aide.

Mots clés:

Réponses

  • CéroceCéroce Membre, Modérateur
    octobre 2016 modifié #2
    Je me suis attaqué à  ton problème pour le plaisir.
    Déjà , je trouve étrange l'énoncé: pourquoi ne pas avoir simplement une liste de dates en entrées ???

    Mais voici ce que j'ai produit:


    struct Period {
    let from : String
    let to: String?
    init(from: String, to: String?) {
    self.from = from
    self.to = to
    }

    }


    let periods = [Period(from: "01/01/16", to: nil),
    Period(from: "02/01/16", to: nil),
    Period(from: "01/04/16", to: nil),
    Period(from: "01/09/16", to: nil)]

    let beginPeriods = periods[0..<periods.count-1]
    let endPeriods = periods[1..<periods.count]
    let periodTuples = zip(beginPeriods, endPeriods)

    func combinePeriods(beginPeriod: Period, endPeriod: Period) -> Period {
    return Period(from: beginPeriod.from, to: endPeriod.from)
    }

    var completePeriods = periodTuples.map(combinePeriods)
    completePeriods.append(periods[periods.count-1])

    for p in completePeriods {
    print("period: \(p.from) \(p.to)")
    }
    On peut simplifier, mais je manque d'expérience avec Swift. Notamment, je ne connais pas la syntaxe pour passer une fonction anonyme dans le map(), à  la place de combinePeriods(). On doit également pouvoir conserver un array immutable au lieu que completePeriods soit mutable. Enfin, définir beginPeriods n'est pas absolument nécessaire, la fonction zip renvoyant une liste dont la longueur est égale à  celle de la plus courte des listes.
    Je donne ma solution pour l'idée... j'apprends Haskell actuellement.
  • NSDate contient une méthode "compare". A ta place, je sortirais les date, je les classerai avec les méthodes de comparaisons de NSDate, puis je créerais les chaines de caractère.


    https://developer.apple.com/reference/foundation/nsdate


  • zoczoc Membre
    octobre 2016 modifié #4
    En utilisant reduce (de façon non intuitive pour les débutants en programmation fonctionnelle, l'accumulateur est ici un tableau). Malheureusement on ne peut pas se passer d'un état (var state dans la fonction combine), et donc dans un langage où la mutabilité est proscrite, on utiliserait sans doute la Monad State ;)
     
    struct Period {
    let from : String
    let to: String?
    init(from: String, to: String?) {
    self.from = from
    self.to = to
    }

    }


    let periods = [Period(from: "01/01/16", to: nil),
    Period(from: "02/01/16", to: nil),
    Period(from: "01/04/16", to: nil),
    Period(from: "01/09/16", to: nil)]


    func combine(periods:[Period]) -> [Period] {
    guard periods.count > 0 else { return periods }

    var state = periods[0]
    let periods1 = periods[1..<periods.count]
    let combined = periods1.reduce([Period]()) {
    acc, p in

    let newP = Period(from: state.from, to: p.from)
    state = p
    return acc + [newP]
    }

    return combined + [periods.last!]
    }

    for p in combine(periods: periods) {
    print("period: \(p.from) \(p.to)\n")
    }
    Comme Céroce j'ai utilisé des strings pour les dates, mais en les remplaçant par NSDate le résultat devrait être le même..
  • CéroceCéroce Membre, Modérateur
    @zoc: intéressant. De ce que je comprends, reduce() est l'équivalente de foldl en Haskell. Effectivement, je n'avais pas pensé que l'accumulateur pouvait être une liste.
  • zoczoc Membre
    octobre 2016 modifié #6
    Oui, reduce est l'équivalent du fold dans d'autres langages. Par ailleurs, avec l'astuce de l'accumulateur de type array, on peut implémenter map à  partir de reduce ;)
  • Merci à  tous pour vos réponses fortement instructives.


     


    Pour info j'ai décris les NSDate dans mon exemple sour la forme jj/mm/aa pour plus de lisibilité mais je souhaite conserver un tableau de NSDate.


  • CéroceCéroce Membre, Modérateur
    octobre 2016 modifié #8
    J'ai pris des Strings par facilité (plus faciles à  créer que des NSDates), mais comme l'indique zoc, ça devrait fonctionner tout aussi bien avec des NSDates.

    zoc et moi utilisons des approches différentes, mais le résultat est le même.
    Je ne suis pas certain de laquelle des deux versions est plus la rapide; il faudrait mesurer. A priori je parierais plutôt sur celle de zoc " du moins en Swift " parce que certes, elle utilise une liste mutable, mais elle évite de créer des listes supplémentaires, ce qui provoque des copies en Swift.
  • LexxisLexxis Membre
    octobre 2016 modifié #9

    Vu le postulat de départ et si ton tableau est un tableau de tuples, un simple for..in devrait suffire non ?



    //: Playground - noun: a place where people can play

    import Cocoa

    var tuples : [(from:String, to:String?)] =
    [(from: "01/01/16", to: nil), (from: "02/01/16", to: nil), (from: "01/04/16", to: nil), (from: "01/09/16", to: nil)]

    for index in 0..<(tuples.count - 1) {
    tuples[index].to = tuples[index + 1].from
    }

    for tuple in tuples {
    print("period: \(tuple.from) \(tuple.to == nil ? "End" : tuple.to!)\n")
    }


  • Le postulat de départ, c'est de faire un truc plus "swifty". Je considère que celà  veut dire favoriser un style de programmation "fonctionnelle", et par conséquent favoriser l'immutabilité...
  • LexxisLexxis Membre
    octobre 2016 modifié #11

    Effectivement je pensai proposer une manière plus orienté programmation fonctionnelle j'ai cependant trouvé que pour cet exemple bien précis ce n'était pas vraiment lisible à  côté d'une boucle for..in.


     


    Celant étant dit voici une solution que j'utiliserai (swift 3)



    typealias FromTo = (from:String, to:String?)

    var tuples : [FromTo] =
    [(from: "01/01/16", to: nil),
    (from: "02/01/16", to: nil),
    (from: "01/04/16", to: nil),
    (from: "01/09/16", to: nil)]

    let f = tuples.reversed().reduce([FromTo]()) {
    return [$0.count > 0 ? FromTo(from: $1.from, to: $0.first!.from) : $1] + $0
    }
  • Je découvre grâce à  vous la programmation fonctionnelle. Merci! (d'où ma réponse un peu à  côté ... )


     


    J'ai lu l'article Wikipedia. Pouvez-vous nous en dire davantage?


  • Le reduce est une très bonne solution en effet. 


     


    J'aurais préféré utilisé map personnellement. Plus intuitif.



    struct Period {
    let from : String
    let to: String?
    init(from: String, to: String?) {
    self.from = from
    self.to = to
    }

    }


    let periods = [Period(from: "01/01/16", to: nil),
    Period(from: "02/01/16", to: nil),
    Period(from: "01/04/16", to: nil),
    Period(from: "01/09/16", to: nil)]

    let count = periods.count
    let newPeriods = periods.enumerated().map { (offset, period) -> Period in
    if offset < count-1 {
    return = Period(from: period.from, to: periods[offset+1].from)
    } else {
    return Period(from: period.from, to: nil)
    }
    }

    print(newPeriods)

    J'ai récupéré ce qu'à  fait Céroce pour la structure.


  • Personnellement c'est la fonction enumerated() de array que je ne connaissais pas et qui m'empêchait d'avancer.


     


    Merci à  tous. Ce forum est vraiment génial. Je continuerais à  le citer dans les remerciements de toutes mes applications car la communauté est au TOP. Chacun apprends et fait découvrir tout en consolidant ses acquis. meeeeerrrrrccccciiiiii.


     


     


    PS : Je suis en train d'apprendre androà¯de, je ne sais pas s'il existe un forum de cet qualité pour cette plateforme de surcroà®t en français.


  • iLandesiLandes Membre
    octobre 2016 modifié #15

    Un grand merci à  tous pour votre participation à  ce sujet.


     


    Voici le code que j'ai finalement utilisé en m'inspirant de vos idées



    //: Playground - Sébastien REMY as iLandes
    import Foundation

    // Dates compare
    func < (lhs: NSDate, rhs: NSDate) -> Bool {
        return lhs.compare(rhs) == NSComparisonResult.OrderedAscending && !lhs.isEqualToDate(rhs)
    }


    // Period Struct
    struct Period {
        let from : NSDate
        let to: NSDate?
    }


    // Vars
    var periodDateFormater: NSDateFormatter {
    get {
        let formatter = NSDateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:00"
        formatter.timeZone = NSTimeZone(name:"Europe/Paris")
        return formatter
    }
    }
    var periods: [Period] = []


    // Create valide dates func
    func addPeriodFromStringDate (s:String) {
    guard let dayFrom = periodDateFormater.dateFromString(s) else {
        print ("Incorrect date format :\(s)")
        return
    }
        periods.append(Period(from: dayFrom, to: nil))
    }

    // Print perdiod func
    func printPeriods(periods: [Period], headline: String) {
        print ("\(headline) :")
        print (" ")
        for p in periods {
            let f = periodDateFormater.stringFromDate(p.from)
            if let to = p.to {
            let t = periodDateFormater.stringFromDate(to)
            print ("From : \(f) to \(t)")
        }
            else {
                
                print ("From : \(f) to \(p.to)")

            }
        }
        print()
    }

    // Main process

    // Append Array
    addPeriodFromStringDate("2016-01-01 18:22:00")
    addPeriodFromStringDate("2016-01-02 02:00:00")
    addPeriodFromStringDate("2016-01-04 00:30:00")
    addPeriodFromStringDate("2016-01-01 08:22:00")
    printPeriods(periods, headline: "Initial array")

    // Sort array
    periods.sortInPlace({$0.from < $1.from})
    printPeriods(periods, headline: "Sorted array")


    // Map array
    let count = periods.count
    let newPeriods = periods.enumerate().map { (offset, period) -> Period in
        if offset < count-1 {
            return Period(from: period.from, to: periods[offset+1].from)
        } else {
            return Period(from: period.from, to: nil)
        }
    }
    printPeriods(newPeriods, headline: "Mapped array")

    Voici la sortie de console


     


     


    Initial array :



    From : 2016-01-01 18:22:00 to nil


    From : 2016-01-02 02:00:00 to nil


    From : 2016-01-04 00:30:00 to nil


    From : 2016-01-01 08:22:00 to nil


     


    Sorted array :



    From : 2016-01-01 08:22:00 to nil


    From : 2016-01-01 18:22:00 to nil


    From : 2016-01-02 02:00:00 to nil


    From : 2016-01-04 00:30:00 to nil


     


    Mapped array :



    From : 2016-01-01 08:22:00 to 2016-01-01 18:22:00


    From : 2016-01-01 18:22:00 to 2016-01-02 02:00:00


    From : 2016-01-02 02:00:00 to 2016-01-04 00:30:00


    From : 2016-01-04 00:30:00 to nil


Connectez-vous ou Inscrivez-vous pour répondre.