MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 2022
-
14 déc. 2017 à 15:33
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023
-
16 déc. 2017 à 01:05
Bonjour,
Je rencontre un problème pour trier une List<T> dont les membres sont des classes.
Ces classes contiennent elles-même des classes.
L'une de ces classes a entre autres une propriété que je souhaiterais utiliser comme critère de tri.
Après pas mal de recherches sur le net, j'ai vu qu'il fallait inclure dans la classe une surcharge de la méthode CompareTo. Mais je ne suis pas arrivé à faire fonctionner le schmilblick. Les exemples fournis ne concernent que des cas (très) simples.
Je vais essayer de synthétiser pour faire comprendre le problème. Il y a peut-être quelques erreurs puisque ce code n'a évidemment pas pu être testé. Mais le principe reste entier.
public class main
{
List<niveau1> maliste = new List<niveau1>();
....
maliste.Sort(); // Ça, c'est ce que je voudrais faire, en précisant "ascendant" ou "descendant"
}
public class niveau1
{
string prop11;
string prop12;
List<niveau2> ma_sous_classe = new List<niveau2>(); // classe incorporée à examiner
string prop13;
autre_classe mon_autre_classe = new autre_classe();
...
}
public class niveau2
{
string prop21;
int prop22;
string mon_critere_de_tri; // C'est ce qu'il faut comparer, en ascendant ou descendant
bool prop23;
...
}
Il faut bien noter qu'il faut trier le premier niveau de la liste en tenant compte du critère situé dans le second niveau. Il n'y a qu'une seule instance de niveau 2 dans chaque niveau 1.
Pourquoi faire simple quand on peut faire compliqué, n'est-ce pas ? Mais cette structure est bien adaptée à un autre usage, et il va falloir faire avec.
vb95
Messages postés3407Date d'inscriptionsamedi 11 janvier 2014StatutContributeurDernière intervention30 mars 2023165 14 déc. 2017 à 18:17
Bonjour
1)
List<niveau1> maliste = new List<niveau1>(); .... maliste.Sort(); // Ça, c'est ce que je voudrais faire, en précisant "ascendant" ou "descendant
Là tu tries ta liste dans le sens descendant ( du plus petit au plus grand alpha numériquement parlant ( Alain sera avant Marc )
Pour le trier dans l'autre sens il faut préciser en plus
)
List<niveau1> maliste = new List<niveau1>(); .... maliste.Sort(); // descendant maliste.Reverse(); // ascendant ( on inverse le sens )
2) si la classe Niveau2 n'a qu'une seule instance pourquoi dans la classe Niveau1 définir Niveau2 come une List
public class main { List<niveau1> maliste = new List<niveau1>(); .... maliste.Sort(); // Ça, c'est ce que je voudrais faire, en précisant "ascendant" ou "descendant" }
public class niveau1 { string prop11; string prop12; niveau2 ma_sous_classe = new niveau2(); // classe incorporée à examiner string prop13; autre_classe mon_autre_classe = new autre_classe(); ... }
public class niveau2 { string prop21; int prop22; string mon_critere_de_tri; // C'est ce qu'il faut comparer, en ascendant ou descendant bool prop23; ... }
La théorie, c'est quand on sait tout et que rien ne fonctionne. La pratique, c'est quand tout fonctionne et que personne ne sait pourquoi.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 14 déc. 2017 à 20:00
Bonsoir VB.
Pour que Sort fonctionne, la classe doit implémenter IComparable, donc posséder la méthode CompareTo, comme MGD l’a vu.
Cette implémentation est la façon de faire historique. Elle presente cependant le défaut d’imposer une seule façon de trier, par exemple pour des gens, ce sera toujours le nom d’abord, le prenom ensuite et la date de naissance. Et quand tu veux trier par prénom c’est compliqué.
Avec linq et la méthode OrderBy, il n’est plus nécessaire d’implémenter IComparable et on définit la logique de tri quand on en a besoin.
Je tacherai de faire un exemple de chaque dans la soiree
vb95
Messages postés3407Date d'inscriptionsamedi 11 janvier 2014StatutContributeurDernière intervention30 mars 2023165
>
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023 15 déc. 2017 à 14:51
Salut Whis
La méthode avec Linq et OrderBy est très intéressante à plus d'un point
1) plus court à écrire ( plus besoin de IComparable)
2) on peut changer le critère de tri quand on veut
3) plus besoin de .Reverse ( OrderByDescending fait le job )
Linq a encore de très beaux jours devant lui
Merci pour ce petit cours très instructif
Un bonjour à MGD Sofware en passant
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622
>
vb95
Messages postés3407Date d'inscriptionsamedi 11 janvier 2014StatutContributeurDernière intervention30 mars 2023 15 déc. 2017 à 15:00
C'est sûr je n'ai pas implémenter IComparable depuis que j'ai eu le droit de passer à la Framework 3.5 au boulot (on avait un système sou Windows2000, que l'on a remplacé qu'en 2013)
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 Modifié le 14 déc. 2017 à 22:30
Avant de montrer un exemple, j'ai une critique sur le bout de code présenté (même en tant qu'exemple), l'utilisation de champs public est très fortement déconseillé, EDIT suppression de mon erreur à la fin.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 14 déc. 2017 à 22:13
Exemple Linq
Tes classes réécrites avec des propriétés
public class niveau1
{
public string prop11 { get; set; }
public string prop12 { get; set; }
public List<niveau2> ma_sous_classe { get; set; } = new List<niveau2>(); // classe incorporée à examiner
public string prop13 { get; set; }
}
public class niveau2
{
public string prop21 { get; set; }
public int prop22 { get; set; }
public string mon_critere_de_tri { get; set; } // C'est ce qu'il faut comparer, en ascendant ou descendant
public bool prop23 { get; set; }
}
Exemples de tri, à noter que Linq crée une nouvelle collection que je caste en liste.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 14 déc. 2017 à 22:21
Exemples avec IComparable
Tes classes dont la première implémente IComparable
public class niveau1:IComparable
{
public string prop11 { get; set; }
public string prop12 { get; set; }
public List<niveau2> ma_sous_classe { get; set; } = new List<niveau2>(); // classe incorporée à examiner
public string prop13 { get; set; }
public int CompareTo(object obj)//compareTo retourne 1 ou -1 selon qui est le plus grand, et 0 en cas d'égalité, comme on ne sait jamais quand retourner 1 o =1, le plus simple est d'utilser le CompareTo de notre critère
{
return ma_sous_classe.First().mon_critere_de_tri.CompareTo(((niveau1)obj).ma_sous_classe.First().mon_critere_de_tri);
}
}
public class niveau2
{
public string prop21 { get; set; }
public int prop22 { get; set; }
public string mon_critere_de_tri { get; set; } // C'est ce qu'il faut comparer, en ascendant ou descendant
public bool prop23 { get; set; }
}
Exemples de tri, à noter cette fois, Sort trie la liste en cours.
source.Sort();//tri dans un sens
source.Reverse();//inversion de l'ordre de tri
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 14 déc. 2017 à 22:25
Autre surcharge de Sort
Sort dispose de 4 surcharges, les 3 autres permettent d'utiliser une comparaison personnalisable au travers d'objets Comparison<niveau1> ou IComparer<niveau1> qui sont plus compliqués à écrire que la requête Linq.
Je ne vais donc pas te le montrer là.
Cependant, il faut retenir que, même avec Linq, il est parfois nécessaire d'implémenter un IComparer<niveau1>
Vous n’avez pas trouvé la réponse que vous recherchez ?
MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 15 déc. 2017 à 11:44
Merci beaucoup pour ces exemples
En réalité, dans le vrai code, mes classes et propriétés sont bien sûr conformes à ce que tu as redonné, sinon ça ne fonctionnerait pas. J'ai juste été un peu vite pour écrire le code exemple dans un éditeur qui ne connaît pas le C#.
J'avais quasiment implémenté la version IComparable, mais là où je n'ai pas su faire, c'est le contenu de la méthode CompareTo.
A priori, ma préférence va plutôt vers cette méthode, car elle ne modifie pas la classe d'origine. Je suppose qu'en ajoutant une propriété Order à la la classe niveau1 on peut gérer le sens du tri.
Je n'ai pas bien compris l'histoire des champs publics. Les propriétés doivent être publiques pour pouvoir être utilisées dans des instances de la classe. Les classes doivent être publiques puisque à l'extérieur de la classe Main. Dans mon exemple, il est évident que cela ne peut pas marcher puisque sans modificateur, les propriétés sont vues comme privées. Mais j'ai déjà répondu sur ce point. Sinon quel est le problème ?
Le fait que la méthode Linq crée une copie de la classe 1 me gêne un peu, car cette classe fait partie d'une classe de niveau 0 (tant qu'à faire...) et cela m'obligerait à remplacer l'instance de la classe d'origine par la nouvelle créée par Linq. Je suppose que ce n'est pas important, mais j'ignore les conséquences dans les variables externes à la classe 0 qui utilisent des références à la classe 1 ou la classe 2 (genre classe0.classe1.classe2.prop22).
Je vais donc tenter la méthode iComparable. Je te tiendrai au courant du résultat, pas tout de suite car j'ai d'autres parties du code sur le feu, notamment le fichier d'aide en ligne en PHP/JQuery/CSS (mais là je maitrise plutôt bien).
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 15 déc. 2017 à 12:42
J'avais quasiment implémenté la version IComparable, mais là où je n'ai pas su faire, c'est le contenu de la méthode CompareTo.
A priori, ma préférence va plutôt vers cette méthode, car elle ne modifie pas la classe d'origine.
ha ben si, tu ajoutes bien héritage et la méthode à la classe.
Je suppose qu'en ajoutant une propriété Order à la la classe niveau1 on peut gérer le sens du tri.
oui, avec ce paramètre tu inverses la sortie de CompareTo
Je n'ai pas bien compris l'histoire des champs publics. Les propriétés doivent être publiques pour pouvoir être utilisées dans des instances de la classe. Les classes doivent être publiques puisque à l'extérieur de la classe Main. Dans mon exemple, il est évident que cela ne peut pas marcher puisque sans modificateur, les propriétés sont vues comme privées. Mais j'ai déjà répondu sur ce point. Sinon quel est le problème ?
Un champ n'est pas une propriété.
un champ c'est une variable, une propriété c'est un point d'accès à une variable. Par exemple, une personne a un nom, un prénom et une date de naissance, ce sont des paramètres qui ne change pas durant la vie de cette personne.
class Personne
{
string prenom;
string nom;
DateTime naissance
public Personne(string Prenom, string Nom, DateTime Naissance)
{
prenom = Prenom;
nom = Nom;
naissance = Naissance
}
}
Si je rends ces 3 champs publiques n'importe qui peut les modifier, pourtant j'ai besoin d'accéder au données.
class Personne
{
string prenom;//champ
DateTime naissance
public Personne(string Prenom, string Nom, DateTime Naissance)
{
prenom = Prenom;
this.Nom = Nom;
naissance = Naissance
}
public string Prenom//propriété
{
get {return prenom;}
}
public string NumeroTel {get; set;}//notation alternative à la propriété, quand on n'a pas besoin de la variable interne
public string Nom {get; private set}//notation alternative en lecture seule
}
Le fait que la méthode Linq crée une copie de la classe 1 me gêne un peu, car cette classe fait partie d'une classe de niveau 0 (tant qu'à faire...) et cela m'obligerait à remplacer l'instance de la classe d'origine par la nouvelle créée par Linq. Je suppose que ce n'est pas important, mais j'ignore les conséquences dans les variables externes à la classe 0 qui utilisent des références à la classe 1 ou la classe 2 (genre classe0.classe1.classe2.prop22).
Linq ne crée pas de nouvelle instance, il crée une autre collection avec les mêmes instances (et rien ne t'empêche de stocker le résultat dans la collection source).
Une collection c'est une liste de liens vers des références d'instance, créer une collection reviens juste à faire d'autres liens.
MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 15 déc. 2017 à 14:08
Ok pour champ/propriété. Déformation du PHP où get et set n'existent pas
Autre chose : lorsque j'écris :
public class album_class : IComparable
, le compilateur me dit que "'album_class' n'implémente pas le membre d'interface 'IComparable.CompareTo(object)'". Pourtant J'ai dans ma classe la méthode CompareTo :
public class album_class : IComparable
{
public bool SortDesc { get; set; }
public alb_common_class common = new alb_common_class();
public alb_styles_class styles = new alb_styles_class();
public List<alb_slide_class> slides = new List<alb_slide_class>();
public int CompareTo(album_class album)
{
if (album == null)
return 1;
else if (SortDesc)
return -(this.common.path.CompareTo(album.common.path));
else
return this.common.path.CompareTo(album.common.path);
}
(common.path est un String)
Qu'est-ce que lui convient pas ?
EDIT : Je viens de remplacer "album_class album" par "object album" et le compilateur ne dit plus rien (sauf qu'il faut caster album en album_class). Ah, ces surcharges... Il faut veiller à respecter les paramètres.
Je n'ai pas le temps de tester tout de suite. A suivre....
PS : Je vais corriger mes champs publics en propriétés en rajoutant { get; set; }. J'ai bien compris ? Si oui, y'a du boulot ! Notes que de toute façon il est prévu que n'importe qui (??? c'est MON programme) puisse modifier ces valeurs, autant en écriture qu'en lecture. Et pour l'instant je ne fais pas de code réutilisable dans cette partie du programme. Par contre j'en tiendrai compte dans mes classes de bibliothèque, qui sont utilisées par plusieurs de mes programmes. Et vu mon niveau, je n'en suis pas encore à oser partager mes sources avec la communauté.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 15 déc. 2017 à 14:19
EDIT : Je viens de remplacer "album_class album" par "object album" et le compilateur ne dit plus rien (sauf qu'il faut caster album en album_class). Ah, ces surcharges... Il faut veiller à respecter les paramètres.
et oui, la prochaine fois, clique droit sur IComparable => Implémenter l'interface, ça va t'écrire la méthode (les méthodes selon l'interface) avec thow NotImplementExecption dedans.
PS : Je vais corriger mes champs publics en propriétés en rajoutant { get; set; }. J'ai bien compris ? Si oui, y'a du boulot ! Notes que de toute façon il est prévu que n'importe qui (??? c'est MON programme) puisse modifier ces valeurs, autant en écriture qu'en lecture
Oui tu as bien compris, et OK c'est TON programme, cependant (véridique), j'ai un petit voisin qui dit s'appeler Maxime alors que ce n'est pas son prénom (du coup, je ne sais même pas comment il s'appelle...)
Ca a beau être ton programme, il faut bien le gérer
class Personne
{
string prenom;//champ
DateTime naissance
public Personne(string Prenom, string Nom, DateTime Naissance)
{
prenom = Prenom;
this.Nom = Nom;
naissance = Naissance
}
public string Prenom//propriété
{
get
{
if(prenom == "JeSaisPlus" && Nom == "JaiJamaisSu")
return "Maxime";
return prenom;
}
}
public string NumeroTel {get; set;}//notation alternative à la propriété, quand on n'a pas besoin de la variable interne
public string Nom {get; private set}//notation alternative en lecture seule
}
PHP est orienté objet, ça se permet des petits écarts, C# c'est (tout) objet.
MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 Modifié le 15 déc. 2017 à 14:36
En relisant mon code, je viens de me rendre compte que j'avais bien utilisé get et/ou set partout où la variable était un type élémentaire, mais jamais lorsque c'était un objet, surtout lorsqu'il était initialisé avec new.
D'ailleurs, je ne savais pas où placer le { get; set }.
Maintenant je sais grâce à tes exemples.
public Remerciements Merci { get; set; } = new Remerciements();
MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 15 déc. 2017 à 15:50
Ça marche terrible !
Excité par la curiosité, j'ai mis de côté mon code en cours et appliqué les conseils ci-dessus.
Le tri fonctionne, aussi bien ascendant que descendant.
Du coup, j'ai appliqué le tri à toutes mes classes contenues dans des listes, à quelque niveau que ce soit.
Voici par exemple le code d'une petite classe du dernier niveau : WebAlbum.collection.albums.slides
Ça pourra peut-être servir à d'autres ayant les mêmes problèmes que moi.
/// <summary>
/// Paramètres des vignettes : titre, nom de fichier et ratio
/// </summary>
public class alb_slide_class : IComparable
{
public alb_slide_class() { }
public alb_slide_class(string slide_title, string slide_path, string slide_ratio)
{ mPath = slide_path; mTitle = slide_title; mRatio = slide_ratio; }
private string mPath = "", mTitle = "", mRatio = "1.5";
[XmlIgnore]
public bool Modified { get; set; }
[XmlIgnore]
public bool sortdesc { get; set; }
public string file { get { return mPath; } set { if (value != mPath) Modified = true; mPath = value; } }
public string title { get { return mTitle; } set { if (value != mTitle) Modified = true; mTitle = value; } }
public string ratio { get { return mRatio; } set { if (value != mRatio) Modified = true; mRatio = value; } }
public int CompareTo(object obj)
{
alb_slide_class slide = obj as alb_slide_class;
if (obj == null)
return 1;
if (sortdesc)
return -(this.mPath.CompareTo(slide.mPath));
else
return this.mPath.CompareTo(slide.mPath);
}
}
Conclusion finale : Je n'étais pas très loin. Mon échec était dû à deux choses :
- Je n'avais pas déclaré la classe comme IComparable.
- Le paramètre de CompareTo n'était pas du type object mais du type de la classe à comparer.
Heureusement, il y a Internet et des geeks sympas.
Merci.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 15 déc. 2017 à 18:24
De rien, mais je persiste à penser que Linq est plus simple, moins long à écrire et plus souple.
MGD Software
Messages postés193Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 15 déc. 2017 à 19:52
Linq m'est encore étranger, cela diffère notablement de ce que j'ai connu jusqu'à présent. La syntaxe est curieuse et assez hermétique. Je n'ai pas eu le temps jusqu'à présent de me pencher sur les méthodes de Linq.
Il faut d'abord que je me rôde avec les expressions lambda pour comprendre la syntaxe de Linq, qui semble les utiliser systématiquement.
Dès que j'ai un moment, je vais prendre mon bouquin de C#, lire et relire les chapitres sur les expressions lambda (1/2 page !) et sur Linq (42 pages), et on en reparlera ensuite.
Amitiés.
Whismeril
Messages postés18302Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention30 mars 2023622 16 déc. 2017 à 01:05
les expressions lambda c'est très simple (je ne suis pas surpris que tu n'aies que 1/2 page de cours), tu choisis un nom de variable et tu te sers d'elle pour générer un critère.
Exemple, toutes les personnes dont le prénom commence par un P
lesPersonnes.Where(p => p.Prenom.StartWith("P"))
Ou encore triées par le Nom
lesPersonnes.OrderBy(p => p.Nom)
Toutes les clauses Linq peuvent s'écrire avec des expressions lambdas.
La plupart s'écrivent aussi en "spaghetti", cette syntaxe est inspirée de sql, mais dans l'autre sens pour permettre à Intellisense d'aider au développement.
Par exemple, avec sql (que je ne maitrise pas) si tu veux récupérer les date de naissance des personnes qui s'appellent Jean, tu vas écrire quelque chose qui ressemble à
Select Naissance From Personnes Where Prenom = "Jean"
Avec linq, en spaghetti, tu commences par définir une variable, puis tu détermine la source de données. Cela faisant Intellisense sait quel est le type de ta variable, donc te donne accès à tous ses attributs
Un point fort de Linq est de pouvoir créer des objets qui n'ont pas été décrits, à l'instar de sql qui crée des dataset dont les colonnes dépendent de la requête.
var lesJean = (from p in lesPersonnes
where p.Prenom == "Jean"
select new { p.Nom, p.DateNaissance }
).ToList();
Ce code génère une liste d'un type inconnu (on dit anonyme) de gens qui s'appellent Jean (lol), et ce type a 2 propriétés Nom et DateNaissance.
Avec linq, tu peux faire des recherches "basiques", des groupements, des tris, des jointures, tu peux faire des calculs intermédiaires etc... tout ça dans une seule requêtes et avec à peu près tout ce que tu peux manipuler en C#. Tu peux aussi lire ou écrire du xml (j'ai fait un tuto là dessus).
Un autre point fort de linq, mais aussi son plus grand piège, est l'exécution différée.
En gros, tu écris ta requête à un moment, sans la caster en List ou tableau.
Ensuite tu changes des enregistrements sur la source de données.
Et enfin tu énumères la collection (foreach, cast, élément n°x...), la requête n'est réellement exécutée qu'à ce moment là, donc le résultat dépend des changement intervenus entre la rédaction de la requête et l'énumération.
List<string> nombres = new List<string> { "un", "deux", "trois" };
IEnumerable<string> tri = nombres.OrderBy(n => n);
nombres.Add("quatre");
nombres.Add("cinq");
foreach (string n in tri)
Console.WriteLine(n);
Perso, quand la requête est compliquée, je préfère utiliser les spaghettis, c'est plus aéré.
14 déc. 2017 à 20:00
Pour que Sort fonctionne, la classe doit implémenter IComparable, donc posséder la méthode CompareTo, comme MGD l’a vu.
Cette implémentation est la façon de faire historique. Elle presente cependant le défaut d’imposer une seule façon de trier, par exemple pour des gens, ce sera toujours le nom d’abord, le prenom ensuite et la date de naissance. Et quand tu veux trier par prénom c’est compliqué.
Avec linq et la méthode OrderBy, il n’est plus nécessaire d’implémenter IComparable et on définit la logique de tri quand on en a besoin.
Je tacherai de faire un exemple de chaque dans la soiree
15 déc. 2017 à 14:51
La méthode avec Linq et OrderBy est très intéressante à plus d'un point
1) plus court à écrire ( plus besoin de IComparable)
2) on peut changer le critère de tri quand on veut
3) plus besoin de .Reverse ( OrderByDescending fait le job )
Linq a encore de très beaux jours devant lui
Merci pour ce petit cours très instructif
Un bonjour à MGD Sofware en passant
15 déc. 2017 à 15:00
Modifié le 14 déc. 2017 à 22:30