[Résolu] Split Array of NSDate
iLandes
Membre
Bonjour,
Mon petit problème du jour. Je souhaite séparé un tableau qui contient des NSDate en plusieurs petit tableaux (1 par mois).
Je cherche je test un peu tout ce que je trouve sur le net mais pour le moment je n'ai rien de satisfaisant part des méthodes bourinnes rempli de for et de if bien loin de l'élégance de swift...
Voici un petit bout de code pour expliquer ce que je veux.
import Foundation
func formatedDate(aString: String) -> NSDate?{
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd hh:mm zzzz"
return dateFormatter.dateFromString(aString)
}
var arrayOfDates = [NSDate]()
arrayOfDates.append(formatedDate("2015-12-24 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-11-04 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-09-01 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-09-02 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-09-03 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-09-10 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-10-05 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-10-06 00:00 GMT")!)
arrayOfDates.append(formatedDate("2015-11-03 00:00 GMT")!)
print ("Unsorted : \(arrayOfDates)")
// Sort result
arrayOfDates.sortInPlace({$0.timeIntervalSince1970 < $1.timeIntervalSince1970})
print ("Sorted : \(arrayOfDates)")
// Slice
J'aimerais donc découper mon tableau en 4 tableaux, un pour chaque mois 09, 10, 11 et 12 avec à l'intérieur les dates concernées.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Avec cette extension, tu peux maintenant par exemple découper un tableau de chaà®nes en sous-tableaux selon la première lettre.
Bon le code n'est peut-être pas très simple à comprendre, d'autant que j'utilise reduce de façon un peu détournée, avec un accumulateur qui est en fait un tuple de 2 valeurs... mais là faut que je file donc je te laisse méditer sur le code et essayer de regarder ça et me poser les questions plus tard
Le test utilise un tableau de chaà®nes, que je groupe selon leur première lettre : je trie d'abord mon tableau, puis avec ma nouvelle méthode split je dis "découpe à chaque fois que le premier caractère de l'élément précédent est différent du premier caractère de l'élément courant", comme ça il commence un autre sous-tableau à chaque fois que la première lettre change (d'où l'intérêt de commencer par trier).
Vu que j'ai utilisé des Generics, tu dois pouvoir l'utiliser tout pareil avec des NSDate, en les triant par ordre chronologique d'abord si c'est pas déjà le cas, puis en passant comme fonction de découpage à split() une méthode qui va regarder si les 2 dates à comparer ont un mois différent (via NSDateComponents par exemple).
A mon avis il y a moyen d'améliorer mon implémentation de split(), sans doute en n'utilisant comme accumulateur que directement le tableau de tableau qu'on veut construire au fur et à mesure des itérations dans reduce, plutôt que d'utiliser cette astuce pas forcément très lisible de prime abord d'avoir un accumulateur qui est en fait composé de ce tableau + du tableau de la dernière liste en cours de construction... mais bon, "c'est un exercice laissé au lecteur" comme on dit
Merci beaucoup Ali pour tes réponses toujours aussi efficaces.
Pour le moment j'avoue avoir du mal à comprendre ton implémentation de split(), je me suis rendu au plus urgent et j'ai adapté ton code à NSDate et cela fonctionne
Voici ce que cela donne :
Ce qui donne exactement ce que souhaitais dans la console :
Pas sûr d'être à la hauteur pour améliorer ton code.
Normalement, la fonction reduce() prend en premier paramètre un point de départ A, puis itère sur tous les éléments du tableau pour te permettre d'accumuler les valeurs du tableau dans cette accumulateur A, en enrichissant l'accumulateur A à chaque itération.
Par exemple :
Part du point de départ 0, puis itère sur chaque nombre du tableau et pour chaque nombre, il l'ajoute tout simplement à l'accumulateur (qui ici est un simple entier).
Autre exemple :
Celui-là va commencer par mettre ">" dans l'accumulateur (qui pour cet exemple est une chaà®ne), puis à chaque itération va remplacer l'accumulateur par "\(accum),\(item)", soit l'ancienne valeur de l'accumulateur, suivi d'une virgule puis de la valeur de l'item. Donc au début tu pars de ">", puis à la première itération tu as accumulé ">,Hel", à la suivante tu rajoutes "lo" et tu as donc accumulé ">,Hel,lo", etc.
Ici dans mon cas j'ai utilisé reduce() de façon un peu plus avancée. Au lieu d'avoir un simple accumulateur de type par exemple Array<Array<NSDate>> (enfin "Array<Array<Element>>" puisque ma fonction est générique et pas spécifique à NSDate) et d'essayer d'accumuler directement dedans, mon accumulateur est en fait un tuple, c'est à dire un couple de variables.
La première de ces variables à l'intérieur du tuple de l'accumulateur est de type Array<Array<Element>> et c'est ce qu'on compte construire au final.
Mais au fur et à mesure qu'on va itérer sur tes éléments, c'est pas très pratique d'extraire à chaque itération le dernier tableau de ce tableau, pour regarder dedans son dernier élément, pour enfin pouvoir le tester et le comparer avec l'élément en cours d'itération pour savoir si tu arrives à un élément où tu dois séparer ton tableau ou pas. Ou plutôt, ça encore ça va, on pourrait demander tableauDeTableau.last?.last et s'il existe le comparer à item, en demandant à la fonction condition() passée en paramètre de split de nous dire s'il faut découper ici ou pas.
Mais après, construire la nouvelle valeur de l'accumulateur en fonction de la décision n'est pas forcément très simple. S'il n'est pas temps de découper, il faut insérer l'élément en cours d'itération à la fin du dernier tableau de ton "tableau de tableaux". S'il est temps de découper, il faut insérer un nouveau tableau (avec pour l'instant juste l'élément en cours d'itération dedans) et ajouter ce nouveau tableau à la fin de ton tableau de tableaux.
C'est comme ça que j'avais commencé au début, mais j'arrivais pas à ce que je voulais et je voulais te répondre vite. Du coup j'ai un peu triché, avec ce 2ème élément de mon tuple (mais justement, à mon avis y'a moyen d'améliorer mon code en n'utilisant pas ce 2ème élément du tuple et en faisant directement comme je viens de te décrire au dessus sans tricher).
Du coup, pour tricher, en plus d'avoir ce tableau de tableaux dans mon accumulateur et de l'enrichir à chaque itération, je me balade aussi avec un Array<Element>, qui est le tableau en cours de construction.
- Pour tes NSDate, ça va être le tableau qui est en cours d'être rempli pendant que tu parcoures toutes les dates du même mois. Tant que j'ai une date qui est du même mois que le dernier élément de ce tableau, j'ajoute l'élément au tableau et je continue. Dans ce premier cas, je ne touches donc pas au Array<Array<NSDate>> (premier élément de mon tuple de mon accumulateur), et je remplis le Array<NSDate> en cours de construction (2ème élément du tuple de mon accumulateur).
- Par contre si je change de mois entre l'item en cours et le précédent item que j'avais (le dernier inséré dans mon Array<NSDate>), alors c'est que j'en ai fini avec le mois précédent et que je m'apprête à en commencer un autre, du coup ce Array<NSDate> (2ème élément du tuple) que j'étais en train de remplir pour le mois en cours est fini, donc je l'ajoute à mon Array<Array<NSDate>> (1er élément du tuple), et je repars avec un 2ème élément de tuple tout neuf, qui démarre quasi vide avec juste la nouvelle date qu'on est en train de traiter et qui est d'un mois différent d'avant.
Sur le moment j'ai réussi à faire ce que je voulais plus rapidement en utilisant cet élément intermédiaire pour moins me prendre la tête avec la logique de double-profondeur du "tableau de tableau" pendant l'itération. J'ai ainsi une sorte de Array<NSDate> temporaire qui ne me sers que pendant l'itération. A la toute fin de la boucle, je me retrouve avec, comme résultat de reduce(), le contenu de l'accumulateur à la fin de la boucle, c'est à dire un tuple contenant (1) le Array<Array<NSDate>> avec les dates réparties par mois pour tous les mois complets que j'ai pu accumuler car j'ai détecté un changement de mois avec la date précédente et (2) le Array<NSDate> contenant les dates du dernier mois, que je n'ai pas encore basculé dans le Array<Array<NSDate>> car à la fin de la boucle je n'ai pas détecté un changement de mois (mais bon comme on est à la fin, faut bien quand même le benner dans le tableau de tableaux avec les autres). C'est pour ça qu'à la fin je finis avec r.0 + [r.1] pour ajouter le 2ème élément du tuple aux autres tableaux du premier élément du tuple et retourner le résultat final.
Si je prends quelques minutes, je suis sûr que je peux me passer de ce tuple et de ce Array<NSDate> intermédiaire pour faire encore plus joli et surtout compréhensible...
Tiens, voilà une très bonne occasion d'utiliser map() (cf mon dernier article concernant le sujet sur mon blog). Plutôt que de créer un "var Array" (donc "mutable" puisque "var") pour lui ajouter des éléments, il est plus "Swift" de créer un tableau de tes chaà®nes représentant tes dates, puis d'utiliser "map" pour transformer ce tableau de String en tableau de NSDate :
Avantage c'est qu'en plus d'être + "Swifty", tu n'as plus que des "let" et donc plus de variables "mutables" avec risque de les modifier après coup par erreur. Tu ne construis plus ton tableau de dates en créant une variable mutable à laquelle tu ajoutes des éléments un par un, mais tu ne manipules que des variables figés ("let") et des tableaux déjà complets. Et puis en plus, du coup tu n'as plus besoin de créer une fonction "formatedDate" juste pour ça, qui t'était utile parce que tu avais à l'appeler autant de fois que tu avais de date à ajouter. Là tu peux directement utiliser le code de cette fonction comme transformation à passer à map().
Du coup je n'ai plus qu'un seul accumulateur, qui est directement le Array<Array<Element>>
- S'il y avait un dernier élément dans le dernier tableau (acc.last?last) et qu'en comparant ce dernier élément à l'item en cours, la closure "condition()" nous dit qu'elle veut splitter, alors on passe dans le "if".
Dans ce cas, on veut donc démarrer un nouveau tableau à la fin de notre tableau de tableaux. Ainsi, si on avait "01/01/1970","05/01/1970"],["02/02/1970" dans notre accumulateur, et que là on est en train d'étudier le cas de 07/03/1970, on ne veut pas ajouter cette date dans le tableau qui contient déjà 02/02/1970, mais on veut l'ajouter dans un nouveau Array<Element> qu'on va rajouter à la suite des autres. Donc notre nouvel accumulateur, c'est le même que l'ancien Array<Array<NSDate>>, mais avec un *tableau* ajouté en plus à la fin, ce tableau ne contenant pour le moment qu'un seul élément, ce nouveau "07/03/1970", car on vient tout juste de commencer un nouveau tableau pour ce mois de mars.
- Par contre si on ne veut pas splitter mais que l'élément item qu'on est en train d'analyser doit continuer la série des dates d'avant, donc que cette date est du même mois que la dernière date insérée en somme, alors on ne veut pas ajouter nouveau un Array<NSDate> à la fin de notre accumulateur, mais plutôt insérer cette nouvelle date à la fin du dernier tableau de l'accumulateur.
Du coup, je prend "acc.last", qui est le dernier Array<NSDate> (donc le tableau du dernier mois qu'on a construit, dans mon exemple précédent c'est donc le tableau ["02/02/1970"]), je lui ajoutes mon item (acc.last + [item]), et j'ai ainsi le nouveau Array<NSDate> (qui est maintenant ["02/02/1970", "07/03/1970"]) à utiliser comme dernier élément de mon accumulateur. Et du coup je peux lui dire que mon nouvel accumulateur, c'est maintenant l'ancien, auquel j'enlève le dernier élément/tableau (dropLast), et auquel je rajoute itemAddedToLastArray, qui est ce même dernier élément/tableau, mais modifié pour avoir cet item d'ajouté à la fin...
C'est limite ça la partie la plus tricky, car on est obligé d'enlever l'ancien élément, en dériver un nouveau qui a ton item en + dedans, puis le rajouter. Je suis sûr que c'est encore améliorable, en passant par un accumulateur "var" pour éviter ça et pour le modifier directement "in-place"... ça sera pour la version suivante
Bon en pratique je fais pas "acc.last + [item]" car "acc.last" est optional et peut être nil (si jamais je viens juste de commencer et que mon accumulateur est totalement vide, il n'a même pas de dernier élément), donc faut que je me protège de ce cas. J'utilise "?? []" pour dire "si acc.last est nil, utilise un tableau vide à la place", comme ça je retombe sur mes pieds sans planter quand l'accumulateur n'a encore aucun élément. J'aurais pu utiliser un "if let" plutôt que "??" mais pour le coup ici "??" est plus concis.
Du coup le code devrait de nouveau être compréhensible :
Merci beaucoup Ali
Le code est plus compréhensible, c'st pas pour cela que j'aurais été capable de le pondre
Petit update préventif pour Swift 3
J'ai implanté le code
En effet depuis peu j'ai un warning sur la dernière version : 'var' parameters are deprecated and will be removed in Swift 3