Aller au contenu

Traiter une liste de listes en stockage dynamique


krunch

Messages recommandés

Bonjour

J'essaie de transposer une sous-fonction Lisp en .Net, et le plus compliqué est le traitement de l'argument (ResultBuffer) ..

 

En l'occurrence j'ai un argument qui est une liste de liste dont aucun élément n'est en nombre connu.

 

A priori j'exclus les Array pour cette raison, le meilleur type de données semble être la List (grace à la méthode Add), mais le problème est qu'apparemment on ne peut pas faire une liste de liste (?) :

 

        	List<string> sub = new List<string>();
       	sub.Add("truc");

       	List<List> subs = new List<List>();
       	subs.Add(sub); // NON :-(

 

Par contre il semble qu'on puisse faire une liste de tableaux :

        	string[] subs;
       	subs = new string[] { "truc" };

       	List<Array> sub = new List<Array>();
       	sub.Add(subs);

Mais le stockage dynamique dans un tableau n'est vraiment pas pratique.

 

J'ai vu qu'il y avait une classe ArrayList mais qui semble beaucoup plus lente (et dans ce cas pour une fois la performance compte).

 

Bref quelle est la meilleure solution sachant que j'ai 3 arguments qui sont : entname + ((entname ..) ..) / nil + 0/1

Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

La majeure différence entre une liste générique (List) et un tableau à une dimension (T[]) est que la taille du tableau est fixe(le type List fournit aussi quelques méthodes supplémentaire par rapport aux tableaux).

 

Le type ArrayList est un peu l’ancêtre du type List, il s'agit d'un tableau de taille dynamique mais ne contenant que des éléments de type Object. Depuis le Framework 2 et les collections génériques, le type ArrayList n'offre plus beaucoup d'intérêt sauf à vouloir y stocker des objets de types différents et dans ce cas les opérations de boxing (conversion de type valeur en object) et unboxing (conversion d'object en type valeur sous-jascent) peuvent s'avérer couteuses.

Il est donc préférable d'utiliser les collections génériques fortement typées.

 

Pour ce concerne précisément ta question, il me semble qu'il suffit de préciser le type d'éléments des sous-listes (subs est une liste de listes de chaines):

               List<string> sub = new List<string>();
               sub.Add("truc");

               List<List<string>> subs = new List<List<string>>();
               subs.Add(sub);

  • Upvote 1

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Ok, donc on peut faire une liste de listes en déclarant l'imbrication des types de données. Effectivement ça marche avec les string (pas essayé avec des ObjectId mais ça devrait aussi).

Quant au ArrayList ils sont souvent déconseillés en effet.

 

Merci bien pour ces précisions, ça devrait aller maintenant.

 

Sinon, de façon générale (toujours pour le traitement du ResultBuffer) et d'après ce que j'ai compris en analysant des exemples (notamment tes routines), on utilise de préférence :

- un tableau donné directement par resbuf.AsArray() quand on a un nombre déterminé de données

- une liste fabriquée avec la méthode Add par itération du ResultBuffer avec conditionnelles et analyse du type (beaucoup plus compliqué) quand on a un nombre indéterminé de données .. C'est bien ça ?

Lien vers le commentaire
Partager sur d’autres sites

De façon générale, quand il s'agit d'une fonction LISP, je convertis le ResultBuffer en tableau (AsArray()) pour pouvoir immédiatement compter les arguments et le parcourir pour évaluer la validité des arguments. Ça permet de générer des exceptions avant de commencer tout traitement. Pour le traitement des erreurs, l'ensemble du code est dans un groupe try, et deux groupes catch permettant de différentier les exceptions LISP (definies dans la classe LispException) des autres, ouvrent une boite d'alerte pour afficher le message d'erreur et relance l'exception pour arrêter l'exécution du code (je n'ai pas trouvé d'autres moyens de prévenir l'utilisateur d'une erreur dans le code LISP).

 

Pour illustrer le traitement d'un ResultBuffer d'arguments d'une fonction LISP, je te propose d'étudier le code de gc-VpLayerOverride dont les arguments peuvent avoir plusieurs forme (la couleur est soit un entier ; soit une liste de 3 entiers (vu comme un Point3d) ; soit une liste de deux chaines) ou être optionnels [type_de_ligne [épaisseur_de_ligne [type_de_traçé]]].

 

        /// <summary>
       /// Fonction LISP pour modifier les propriétés du calque (couleur, type de ligne, épaisseur de ligne, style de tracé)
       /// dans la fenêtre de présentation.
       /// Retourne T ou nil (suivant le succés de l'opération)
       /// Arguments : 
       /// le nom d'entité ou l'ObjectId de la fenêtre 
       /// le nom du calque
       /// la couleur
       /// le type de ligne (optionnel)
       /// l'épaisseur de ligne (optionnel)
       /// le style de tracé (optionnel)
       /// </summary>
       [LispFunction("gc-VpLayerOverride")]
       public TypedValue VpLayerOverride(ResultBuffer resbuf)
       {
           try
           {
               TypedValue nil = new TypedValue((short)LispDataType.Nil);
               int i = 0;
               Document doc = acadApp.DocumentManager.MdiActiveDocument;
               Database db = doc.Database;
               ObjectId vpId;
               string layer;

               // nombre d'arguments
               if (resbuf == null)
                   throw new TooFewArgsException();
               TypedValue[] args = resbuf.AsArray();
               if (args.Length < 3)
                   throw new TooFewArgsException();
               if (args.Length > 9)
                   throw new TooManyArgsException();

               // viewport
               if (args[i].TypeCode == (short)LispDataType.ObjectId)
                   vpId = (ObjectId)args[i++].Value;
               else
                   throw new ArgumentTypeException("lentityp", args[i]);

               // calque
               if (args[i].TypeCode == (short)LispDataType.Text)
                   layer = (string)args[i++].Value;
               else
                   throw new ArgumentTypeException("stringp", args[i]);

               using (Transaction tr = db.TransactionManager.StartTransaction())
               {
                   // viewport
                   Viewport vp = tr.GetObject(vpId, OpenMode.ForRead) as Viewport;
                   if (vp == null)
                       throw new LispException("type d'entité incorrect");
                   // calque
                   LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
                   if (!lt.Has(layer))
                       throw new LispException("calque introuvable");
                   LayerTableRecord ltr = (LayerTableRecord)tr.GetObject(lt[layer], OpenMode.ForWrite);
                   LayerViewportProperties lvp = ltr.GetViewportOverrides(vpId);
                   // couleur
                   if (args[i].TypeCode != (short)LispDataType.Nil)
                   {
                       // un entier (couleur de l'index)
                       if (args[i].TypeCode == (short)LispDataType.Int16)
                       {
                           short colorIndex = (short)args[i++].Value;
                           if (colorIndex < 1 || colorIndex > 255)
                               throw new LispException("index de couleur incorrect");
                           lvp.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);
                       }
                       // une liste de 3 entiers soit un Point3d (couleur RVB)
                       else if (args[i].TypeCode == (short)LispDataType.Point3d)
                       {
                           Point3d pt = (Point3d)args[i++].Value;
                           try
                           {
                               byte red = (byte)pt.X;
                               byte green = (byte)pt.Y;
                               byte blue = (byte)pt.Z;
                               lvp.Color = Color.FromRgb(red, green, blue);
                           }
                           catch
                           {
                               throw new LispException("valeurs RGB incorrectes");
                           }
                       }
                       // une liste de deux chaines (couleur nommée du carnet)
                       else if (args[i].TypeCode == (short)LispDataType.ListBegin)
                       {
                           if (args[i + 1].TypeCode == (short)LispDataType.Text)
                           {
                               string colorName = (string)args[++i].Value;
                               if (args[++i].TypeCode != (short)LispDataType.Text)
                                   throw new ArgumentTypeException("stringp", args[i]);
                               string bookName = (string)args[i].Value;
                               try
                               {
                                   lvp.Color = Color.FromNames(colorName, bookName);
                                   i += 2;
                               }
                               catch
                               {
                                   throw new LispException("nom de couleur incorrect");
                               }
                           }
                           else
                           {
                               throw new ArgumentTypeException("fixnump or stringp", args[i + 1]);
                           }
                       }
                       else
                       {
                           throw new ArgumentTypeException("fixnump or listp", args[i]);
                       }
                   }
                   if (args.Length > i + 3)
                       throw new TooManyArgsException();
                   // type de ligne
                   if (args.Length > i)
                   {
                       if (args[i].TypeCode != (short)LispDataType.Nil)
                       {
                           if (args[i].TypeCode != (short)LispDataType.Text)
                               throw new ArgumentTypeException("fixnump", args[i]);
                           string lType = (string)args[i++].Value;
                           LinetypeTable ltt = (LinetypeTable)tr.GetObject(db.LinetypeTableId, OpenMode.ForRead);
                           if (!ltt.Has(lType))
                               throw new LispException("type de ligne incorrect");
                           lvp.LinetypeObjectId = ltt[lType];
                       }
                       // epaisseur de ligne
                       if (args.Length > i)
                       {
                           if (args[i].TypeCode != (short)LispDataType.Nil)
                           {
                               if (args[i].TypeCode != (short)LispDataType.Int16)
                                   throw new ArgumentTypeException("fixnump", args[i]);
                               short lWeight = (short)args[i++].Value;
                               try
                               {
                                   lvp.LineWeight = (LineWeight)lWeight;
                               }
                               catch
                               {
                                   throw new LispException("épaisseur de ligne incorrecte");
                               }
                           }
                           // style de tracé
                           if (args.Length > i)
                           {
                               if (args[i].TypeCode != (short)LispDataType.Nil &&
                                   (short)acadApp.GetSystemVariable("pstylemode") == 0)
                               {
                                   if (args[i].TypeCode != (short)LispDataType.Text)
                                       throw new ArgumentTypeException("stringp", args[i]);
                                   string plotStyle = (string)args[i].Value;
                                   try
                                   {
                                       lvp.PlotStyleName = plotStyle;
                                   }
                                   catch
                                   {
                                       throw new LispException("style de tracé incorrect");
                                   }
                               }
                           }
                       }
                   }
                   tr.Commit();
               }
               return new TypedValue((short)LispDataType.T_atom);

           }
           catch (LispException ex)
           {
               acadApp.ShowAlertDialog(ex.Message);
               throw;
           }
           catch (System.Exception ex)
           {
               acadApp.ShowAlertDialog(ex.Message);
               throw;
           }
       }

 

La classe Lispexceptions

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

namespace LispExtension
{
   public class LispException : System.Exception
   {
       public LispException(string msg) : base(msg) { }
   }

   public class TooFewArgsException : LispException
   {
       public TooFewArgsException() : base("nombre d'arguments insuffisant") { }
   }

   public class TooManyArgsException : LispException
   {
       public TooManyArgsException() : base("nombre d'arguments trop important") { }
   }

   public class ArgumentTypeException : LispException
   {
       public ArgumentTypeException(string s, TypedValue tv)
           : base(string.Format(
           "type d'argument incorrect: {0} {1}",
           s, tv.TypeCode == (int)LispDataType.Nil ? "nil" : tv.Value))
       { }
   }
}

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Oui j'avais vu ça dans cet autre exemple. C'est la version sophistiquée (perso je fais de l'hybride : c'est un code Lisp qui appelle la fonction et non pas l'utilisateur, donc je m'arrange en amont pour qu'il n'y ait pas d'exception), mais c'est bien, ça permet de comprendre les protections à mettre en place en cas d'entrée utilisateur.

 

Et là dessus j'aurais juste 2 remarques :

- si un des arguments est une liste, par exemple d'entités, en nombre indéfini, dans ce cas le nombre d'arguments du tableau n'est pas pertinent (?)

- dans d'autres exemples pour arrêter le code en cas d'exception ils mettent juste un return, par exemple dans ton exemple ça donnerait quelque chose comme :

                ...
               if (args.Length < 3)
               {
                   acadApp.ShowAlertDialog("nombre d'arguments insuffisant");
                   return;
               }

C'est pas valable comme méthode ?

 

Sinon ma question de base est résolue, la liste de sous-listes marche avec les entités, merci.

Lien vers le commentaire
Partager sur d’autres sites

- si un des arguments est une liste, par exemple d'entités, en nombre indéfini, dans ce cas le nombre d'arguments du tableau n'est pas pertinent (?)

Non, le nombre d'arguments ne peut être évalué qu'après avoir parcouru toute la liste (jusqu'à LispDataType.ListEnd) ou les listes, s'il y en a plusieurs.

 

- dans d'autres exemples pour arrêter le code en cas d'exception ils mettent juste un return

Tout d'abord, une fonction LISP définie en NET doit avoir un type de retour différent de void et ne peut donc s'achever simplement par return (LISP est un langage fonctionnel et toute fonction retourne une valeur fusse-t-elle nil)

On pourrait donc faire return null, mais le danger avec ça, c'est que si la fonction LISP définie en .NET est appelée depuis une routine qui exécute encore du code derrière, malgré l'avertissement l'exécution du code LISP se poursuivra avec la valeur nil retournée par la fonction définie en .NET ce qui peut provoquer un autre erreur plus loin. Lancer une exception interrompt complètement l'exécution du code (y compris celui de la ou des fonctions appelantes).

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Non, le nombre d'arguments ne peut être évalué qu'après avoir parcouru la liste (jusqu'à LispDataType.ListEnd) ou les listes, s'il y en a plusieurs.

 

Tout d'abord, une fonction LISP définie en NET doit avoir un type de retour différent de void et ne peut donc s'achever par return (LISP est un langage fonctionnel et toute fonction retourne une valeur fusse-t-elle nil)

On pourrait donc faire return null, mais le danger avec ça, c'est que si la fonction LISP définie en .NET est appelée depuis une routine qui exécute encore du code derrière, malgré l'avertissement l'exécution du code LISP se poursuivra avec la valeur nil retournée par la fonction définie en .NET ce qui peut provoquer un autre erreur plus loin. Lancer une exception interrompt complètement l'exécution du code (y compris celui de la ou des fonctions appelantes).

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Lancer une exception interrompt complètement l'exécution du code (y compris celui de la ou des fonctions appelantes).

Ok c'est la différence .. Il faut donc faire ça (try/catch) ou alors faire un return new TypedValue(5019); en cas d'échec et arrêter l'exécution en Lisp si la fonction renvoie nil.Compris merci.

Lien vers le commentaire
Partager sur d’autres sites

Rebonjour

Malgré ces quelques réserves j'aimerais finir ma fonction, et je bute sur un problème de déclaration ou affectation de variable avec les List<>.

En fait il semble que l'ajout d'une variable (dans l'exemple la variable l) dans une liste se fasse en tant que référence ...

 

J'ai finis par faire l'extraction de ResultBuffer suivante, pour un appel de la forme ( fct A ((B C) (D E)) ).

Il est basé uniquement sur le niveau de profondeur des parenthèses (i), la structure a l'air ok (en tous cas le défilement i / entités est bon) :

 

    	[LispFunction("fct")]
   	public void function(ResultBuffer resbuf)
   	{
       	ObjectId ent = new ObjectId();                          	// ent : entité de niveau Objet
       	List<List<ObjectId>> sub = new List<List<ObjectId>>();  	// sub : liste des path d'entités imbriquées (optionnelle)
       	bool on = new bool();   			    			// on  : true/false (true si argument 1 est fourni / false sinon)

       	int i = -1;
       	List<ObjectId> l = new List<ObjectId>();
       	foreach (TypedValue tv in resbuf)
       	{
           	switch (tv.TypeCode)
           	{
               	case 5006: // ObjectId
                   	if (i == -1)
                       	ent = (ObjectId)tv.Value;
                   	else
                   	{
                       	l.Add((ObjectId)tv.Value);
                   	}
                   	break;
               	case 5016: // '('
                   	if (i == 0)
                       	l.Clear();
                   	i++;
                   	break;
               	case 5017: // ')'
                   	if (i == 1)
                       	sub.Add(l);
                   	i--;
                   	break;
               	case 5003: // int16
                   	on = ((Int16)tv.Value == 1);
                   	break;
           	}
       	}
       	// ensuite traitement exception ent absent (sub est optionnel) etc ...

Mais si j'appelle ( fct A ((B C) (D E)) ) je récupère sub = ((D E)(D E)) ... C'est à dire que la liste l est ajoutée dans sub en tant que référence. Je me dis qu'elle est peut être boxée (qu'une liste se comporte comme un Object) mais si je déboxe l au moment de l'ajouter ça ne change rien :

 

                	case 5017: // ')'
                   	if (i == 1)
                       	sub.Add((List<ObjectId>)l);

 

Est ce un problème de déclaration ?

Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

Oui, List est de type référence et dans ton code tu vides la liste (l.Clear()), puis tu la remplis à nouveau donc toutes les instances l dans sub contiennent les éléments les derniers éléments ajoutés dans la boucle.

 

essaye comme ça (pas testé)

       [LispFunction("fct")]
       public void function(ResultBuffer resbuf)
       {
               ObjectId ent = new ObjectId();                                  // ent : entité de niveau Objet
               List<List<ObjectId>> sub = new List<List<ObjectId>>();          // sub : liste des path d'entités imbriquées (optionnelle)
               bool on = new bool();                                                   // on  : true/false (true si argument 1 est fourni / false sinon)

               int i = -1;
               
               foreach (TypedValue tv in resbuf)
               {
               switch (tv.TypeCode)
               {
                       case 5006: // ObjectId
                       if (i == -1)
                               ent = (ObjectId)tv.Value;
                       else
                       {
                               sub[sub.Count - 1].Add((ObjectId)tv.Value); // ajoute l'ObjectId à la fin de la dernière sous-liste de sub
                       }
                       break;
                       case 5016: // '('
                       if (i == 0)
                               sub.Add(new List<ObjectId>()); // ajoute une sous-liste vide à la fin de sub
                       i++;
                       break;
                       case 5017: // ')'
                       i--;
                       break;
               }
               }

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Si tu veux, tu dois pouvoir utiliser une variable, ce qu'il faut, c'est créer une nouvelle instance de List pour chaque sous liste.

 

pas testé non plus :

        [LispFunction("fct")]
       public void function(ResultBuffer resbuf)
       {
               ObjectId ent = new ObjectId();                                  // ent : entité de niveau Objet
               List<List<ObjectId>> sub = new List<List<ObjectId>>();          // sub : liste des path d'entités imbriquées (optionnelle)
               bool on = new bool();                                                   // on  : true/false (true si argument 1 est fourni / false sinon)

               int i = -1;
               List<ObjectId> l = new List<ObjectId>();
               foreach (TypedValue tv in resbuf)
               {
               switch (tv.TypeCode)
               {
                       case 5006: // ObjectId
                       if (i == -1)
                               ent = (ObjectId)tv.Value;
                       else
                       {
                               l.Add((ObjectId)tv.Value);
                       }
                       break;
                       case 5016: // '('
                       if (i == 0)
                               l = new List<ObjectId>();
                       i++;
                       break;
                       case 5017: // ')'
                       if (i == 1)
                               sub.Add(l);
                       i--;
                       break;
                       case 5003: // int16
                       on = ((Int16)tv.Value == 1);
                       break;
               }
               }

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Lien vers le commentaire
Partager sur d’autres sites

Créer un compte ou se connecter pour commenter

Vous devez être membre afin de pouvoir déposer un commentaire

Créer un compte

Créez un compte sur notre communauté. C’est facile !

Créer un nouveau compte

Se connecter

Vous avez déjà un compte ? Connectez-vous ici.

Connectez-vous maintenant
×
×
  • Créer...

Information importante

Nous avons placé des cookies sur votre appareil pour aider à améliorer ce site. Vous pouvez choisir d’ajuster vos paramètres de cookie, sinon nous supposerons que vous êtes d’accord pour continuer. Politique de confidentialité