IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Problème insoluble à la vue de mes connaissances !


Sujet :

C#

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    110
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 110
    Points : 44
    Points
    44
    Par défaut Problème insoluble à la vue de mes connaissances !
    Bonjour,

    Je cherche depuis des jours et des jours mais je n'arrive pas à résoudre ce problème.

    Partant d'une liste maListe<objetPeriode>

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class objetPeriode
    {
        public DateTime? DateDebut { get; set; }
        public DateTime? DateFin { get; set; }
        public decimal Taux { get; set; }
    }
    Comment faire pour fusionner les périodes adjacentes dont le taux est identique en veillant à ce que les dates continuent à se suivre ?

    exemple : en partant de cette liste

    A partir du 21/07/2012 1,00%
    Du 20/07/2012 au 20/07/2012 2,00%
    Du 18/07/2012 au 19/07/2012 3,00%
    Du 15/07/2012 au 17/07/2012 3,00%
    Du 10/07/2012 au 14/07/2012 4,00%
    jusqu'au 09/07/2012 4,00%

    il faudrait arriver à celle-ci

    A partir du 21/07/2012 1,00%
    Du 20/07/2012 au 20/07/2012 2,00%
    Du 15/07/2012 au 19/07/2012 3,00%
    Du 09/07/2012 au 14/07/2012 4,00%

    Ceci si possible en LINQ ou alors autre chose...

    Quelqu'un de calé en LINQ ou une idée de génie pourrait-il me résoudre ce problème ?

    Merci d'avance

    Cordialement

  2. #2
    Membre éprouvé

    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2011
    Messages
    487
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2011
    Messages : 487
    Points : 945
    Points
    945
    Par défaut
    Hello,

    Juste pour info, il y a aussi un forum Linq ( http://www.developpez.net/forums/f11...-donnees/linq/ )

    Personnellement, je ne l'aurais pas fait en Linq mais plutôt "à la main" avec quelque chose du genre:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    List<objetPeriode> list = new List<objetPeriode>();
    List<objetPeriode> newList = new List<objetPeriode>();
     
    foreach(objetPeriode item in list)
    {
    	if(newList.Exists((i) => i.HasSameRate(item)))
    		continue;
     
    	IEnumerable<objetPeriode> sameRateItems = list.Where((i) => i.HasSameRate(item));
     
    	if(sameRateItems.Count() > 1)
    	{
    		item.DateDebut = sameRateItems.Min((i) => i.DateDebut);
    		item.DateFin = sameRateItems.Max((i) => i.DateFin);
    	}
     
    	newList.Add(item);
    }
    Il suffit d'ajouter la méthode :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    public bool HasSameRate(objetPeriode other)
    {
    	return this.Taux == other.Taux;
    }
    C'est loin d'être parfait mais je pense que ça fait le boulot que tu cherches Par contre ça implique que tous les taux égaux sont des dates qui se cotoient.
    Mon blog sur les technos .NET et Agile -> http://blog.developpez.com/maximepalmisano/

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    110
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 110
    Points : 44
    Points
    44
    Par défaut
    Bonjour,

    Merci pour cette réponse aussi rapide !!!

    Ça fonctionne très bien à l'exception de 2 cas : lorsqu'on rencontre des périodes dont il n'existe que la date de début ou la date de fin. Mais sur ces 2 points, je me suis débrouillé autrement.

    Un grand merci à toi.

  4. #4
    Membre éprouvé Avatar de sisqo60
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Février 2006
    Messages
    754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Indre et Loire (Centre)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 754
    Points : 1 188
    Points
    1 188
    Par défaut
    Bonjour,

    A mon avis tu aura certains bugs, voir les commentaires
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    List<objetPeriode> list = new List<objetPeriode>();
    List<objetPeriode> newList = new List<objetPeriode>();
    foreach(objetPeriode item in list) 
    {     
    if(newList.Exists((i) => i.HasSameRate(item)))
             continue;
    IEnumerable<objetPeriode> sameRateItems = list.Where((i) => i.HasSameRate(item));
    if(sameRateItems.Count() > 1)     
    {
    // A ce niveau là, si l'ensemble de tes dates n'est pas continu, tu auras des erreurs
    // Exemple : Taux 1% du 01/01/2000 au 31/10/2010
    //           Taux 1% depuis 01/01/2012 
    // Il y a un trou, mais toi tu diras : Taux 1% a partir du 01/01/2000
    // A prendre en compte si le cas peut se produire
    item.DateDebut = sameRateItems.Min((i) => i.DateDebut);
    item.DateFin = sameRateItems.Max((i) => i.DateFin);
         
    }       
    newList.Add(item); 
    }
    bon dév.
    Un âne se croit savant parce qu'on le charge de livres (proverbe américain)

    N'oubliez pas de avant de
    Pas de question techniques par MP, c'est contre la philosophie du forum

  5. #5
    Membre éprouvé

    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2011
    Messages
    487
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2011
    Messages : 487
    Points : 945
    Points
    945
    Par défaut
    Salut sisqo60,

    Le code que j'ai fourni n'est la qu'à titre d'exemple pour lui donner une idée de comment résoudre le problème. C'est fait sur un coin de table sans tests ni rien.

    C'est à celui qui a le problème d'écrire le vrai algorithme et de le tester pour être sur que tous les usecases sont bons

    (Btw j'ai précisé que c'était valable que si les taux égaux avaient des intervalles collés)
    Mon blog sur les technos .NET et Agile -> http://blog.developpez.com/maximepalmisano/

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Problème intéressant, je me suis bien amusé pour le résoudre

    Pour commencer, quelques mises au point

    • je pense qu'il y a une petite erreur concernant le résultat attendu :
      exemple : en partant de cette liste

      A partir du 21/07/2012 1,00%
      Du 20/07/2012 au 20/07/2012 2,00%
      Du 18/07/2012 au 19/07/2012 3,00%
      Du 15/07/2012 au 17/07/2012 3,00%
      Du 10/07/2012 au 14/07/2012 4,00%
      jusqu'au 09/07/2012 4,00%

      il faudrait arriver à celle-ci

      A partir du 21/07/2012 1,00%
      Du 20/07/2012 au 20/07/2012 2,00%
      Du 15/07/2012 au 19/07/2012 3,00%
      Du 09/07/2012 au 14/07/2012 4,00%
      Il me semble que la dernière ligne du résultat devrait plutôt être : "jusqu'au 14/04/2012 4,00%", vu que les données en entrée ne précisent pas de date de début pour la période "jusqu'au 09/07/2012". En tous cas je suis parti sur cette hypothèse...
    • Pour modéliser la date de fin, il faudrait ajouter un jour par rapport à la date de l'énoncé. En effet, si pour un humain il est clair que "du 20/07 au 20/07" signifie "toute la journée du 20/07", pour un ordinateur c'est une période de durée nulle (la date de début et la date de fin étant égales). En fait, la période qui représente la journée du 20/07 commence le 20/07 à 0:00 et se termine le 21/07 à 0:00. Donc il faut soit modifier les données en entrée, soit ajouter 1 jour à la date de fin dans le code. Vu que tu ne peux sans doute pas modifier facilement les données d'entrée, on va prendre la 2e option et corriger dans le code en ajoutant 1 jour.


    Vu que c'est un problème qui peut se retrouver dans différentes situations, autant faire une solution générique qui sera réutilisable.

    On va commencer par créer une classe pour représenter un intervalle entre deux valeurs :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
        public class Interval<T>
        {
            private readonly T _start;
            private readonly T _end;
     
            public Interval(T start, T end)
            {
                _start = start;
                _end = end;
            }
     
            public T Start
            {
                get { return _start; }
            }
     
            public T End
            {
                get { return _end; }
            }
     
            public bool OverlapsWith(Interval<T> other, IComparer<T> comparer = null)
            {
                comparer = comparer ?? Comparer<T>.Default;
                return this.Includes(other.Start, comparer)
                    || this.Includes(other.End, comparer)
                    || other.Includes(this.Start, comparer)
                    || other.Includes(this.End, comparer);
            }
     
            public Interval<T> Merge(Interval<T> other, IComparer<T> comparer = null)
            {
                comparer = comparer ?? Comparer<T>.Default;
                T start = comparer.Min(this.Start, other.Start);
                T end = comparer.Max(this.End, other.End);
                return Interval.Create(start, end);
            }
     
            public bool Includes(T value, IComparer<T> comparer = null)
            {
                comparer = comparer ?? Comparer<T>.Default;
                return (this.Start == null || comparer.Compare(this.Start, value) <= 0)
                    && (this.End == null || comparer.Compare(value, this.End) <= 0);
            }
        }

    Dans ton cas, T sera de type DateTime?La méthode OverlapsWith indiquent si deux intervalles se chevauchent
    La méthode Merge fusionne deux intervalles en un seul
    On laisse la possibilité de préciser un compateur spécifique pour comparer les valeurs, mais dans ton cas on ne l'utilisera pas, puisque le comparateur par défaut convient.
    Les méthodes Min et Max sont des méthodes d'extension définies comme suit :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
            public static T Min<T>(this IComparer<T> comparer, T x, T y)
            {
                if (comparer == null)
                    throw new ArgumentNullException("comparer");
                if (comparer.Compare(x, y) <= 0)
                    return x;
                return y;
            }
     
            public static T Max<T>(this IComparer<T> comparer, T x, T y)
            {
                if (comparer == null)
                    throw new ArgumentNullException("comparer");
                if (comparer.Compare(x, y) >= 0)
                    return x;
                return y;
            }
    (à placer dans une classe statique, nommée par exemple ExtensionMethods)

    Voyons maintenant la méthode qui fait tout le boulot, c'est à dire la consolidation des intervalles qui se chevauchent. C'est encore une méthode d'extension :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
            public static IEnumerable<Interval<T>> Consolidate<T>(this IEnumerable<Interval<T>> intervals, IComparer<T> comparer = null)
            {
                comparer = comparer ?? Comparer<T>.Default;
                bool first = true;
                Interval<T> prev = null;
                intervals = intervals.OrderBy(i => i.Start, comparer)
                                     .ThenBy(i => i.End, comparer);
                foreach (var item in intervals)
                {
                    if (first)
                    {
                        prev = item;
                        first = false;
                        continue;
                    }
     
                    if (item.OverlapsWith(prev))
                    {
                        prev = item.Merge(prev);
                    }
                    else
                    {
                        yield return prev;
                        prev = item;
                    }
                }
                if (!first)
                    yield return prev;
            }

    En résumé :
    - on trie les intervalles par date de début, puis par date de fin
    - on prend les intervalles un par un
    - si l'intervalle actuel et le précédent se chevauchent, on les fusionne
    - sinon, c'est le début d'un nouvel intervalle ; on renvoie le précédent puisqu'il est terminé


    Maintenant qu'on a une solution générique, voyons comment l'appliquer à ton problème. Il faut :
    • faire correspondre à chaque objet Periode un objet Interval<DateTime?> (puisque c'est la dessus que l'algorithme travaille)
    • grouper les intervalles par taux, puisqu'on ne doit fusionner que les périodes qui ont le même taux
    • dans chaque groupe, fusionner les intervalles qui peuvent l'être (en utilisant la méthode Consolidate ci-dessus)
    • retransformer chaque intervalle en un objet Periode

    Bref, que des choses faciles à faire avec Linq... ça donne quelque chose comme ça :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
                var results =
                    from p in periodes
                    let i = new Interval<DateTime?>(p.DateDebut, p.DateFin.AddDays(1))                     // création d'un intervalle pour chaque période
                    group i by p.Taux into g                                                               // groupement des intervalles par taux
                    from i in g.Consolidate()                                                              // consolidation des intervalles dans chaque groupe
                    select new Periode { DateDebut = i.Start, DateFin = i.End.AddDays(-1), Taux = g.Key }; // transformation des intervalles en objets Periode
     
                // affichage des résultats
                foreach (var periode in results)
                {
                    string debut = periode.DateDebut.HasValue ? periode.DateDebut.Value.ToShortDateString() : "          ";
                    string fin = periode.DateFin.HasValue ? periode.DateFin.Value.ToShortDateString() : "          ";
                    Console.WriteLine("{0} => {1} : {2}", debut, fin, periode.Taux);
                }

    AddDays permet de corriger les dates de fin (comme expliqué plus haut) ; on l'appelle à nouveau pour retrancher 1 jour à la fin pour que les résultats suivent la même logique que les données d'entrée. Par contre, la vraie méthode AddDays est une méthode de DateTime, on ne peut pas l'utiliser directement sur un objet DateTime? (nullable). Ici on utilise donc une méthode d'extension créée spécifiquement pour ça :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
            public static DateTime? AddDays(this DateTime? date, int days)
            {
                if (date.HasValue)
                    return date.Value.AddDays(days);
                return null;
            }

    Au final, le résultat est le suivant :

    21/07/2012 =>            : 1,0
    20/07/2012 => 20/07/2012 : 2,0
    15/07/2012 => 19/07/2012 : 3,0
               => 14/07/2012 : 4,0

  7. #7
    Membre éprouvé Avatar de sisqo60
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Février 2006
    Messages
    754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Indre et Loire (Centre)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 754
    Points : 1 188
    Points
    1 188
    Par défaut
    Citation Envoyé par MaximePalmisano Voir le message
    Salut sisqo60,

    Le code que j'ai fourni n'est la qu'à titre d'exemple pour lui donner une idée de comment résoudre le problème. C'est fait sur un coin de table sans tests ni rien.

    C'est à celui qui a le problème d'écrire le vrai algorithme et de le tester pour être sur que tous les usecases sont bons

    (Btw j'ai précisé que c'était valable que si les taux égaux avaient des intervalles collés)
    C'était pas un reproche, je voulais juste le prévenir qu'il y aurait des problèmes si il copiait collait le code tel qu'il est. C'est déjà pas mal que tu aies pris le temps d'écrire un début d'algo pour lui

    Bon dév.
    Un âne se croit savant parce qu'on le charge de livres (proverbe américain)

    N'oubliez pas de avant de
    Pas de question techniques par MP, c'est contre la philosophie du forum

Discussions similaires

  1. Problème résolution d'ecran dans mes vues
    Par Cyang dans le forum Développement Web en Java
    Réponses: 0
    Dernier message: 06/09/2011, 15h25
  2. Réponses: 1
    Dernier message: 19/04/2006, 16h32
  3. Problème avec l'initialisation de mes variables
    Par francois.delpierre dans le forum Langage
    Réponses: 4
    Dernier message: 18/10/2005, 02h18
  4. problème insoluble avec CHECK
    Par NiBicUs dans le forum SQL Procédural
    Réponses: 6
    Dernier message: 25/03/2004, 17h13
  5. Problèmes avec des vues
    Par dady dans le forum MFC
    Réponses: 22
    Dernier message: 09/01/2004, 16h26

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo