Author Topic: C# System.Collections.Generic lists  (Read 5281 times)

0 Members and 1 Guest are viewing this topic.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8661
  • AKA Daniel
C# System.Collections.Generic lists
« on: September 03, 2006, 04:49:12 AM »
I have been playing around with lists in C# and for the life of me I can’t figure out how to use list.Contains() with special types. Has anyone played around with this? Please see the line that says ***I need Help Here***

Thanks
Dan

Code: [Select]
using System;
using System.Collections;
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
namespace lisptest
{
  //Setup a list type containing (short)LispDataType (object)value
  public class LispList
  {
    public Int16 iType;
    public object oValue;
    public LispList() { }
    public LispList(Int16 iType, object oValue)
    {
      this.iType = iType;
      this.oValue = oValue;
    }
  }
  //
  public class lstfunc
  {
    // (lst:RemoveDublicateItems '("a" "a" "b" 1 1 2 2.1 2.1 "a"))
    // (lst:RemoveDublicateItems "a" "a" "b" 1 1 2 2 "a")
    // (lst:RemoveDublicateItems (list "a" "a" "b" 1 1 2 2 "a"))
    [LispFunction("lst:RemoveDublicateItems")]
    public ResultBuffer lstRemoveDublicateItems(ResultBuffer args)
    {
      ResultBuffer result = new ResultBuffer();
      try
      {
        TypedValue[] valueArray = args.AsArray();
        List<LispList> Mylist = new List<LispList>();
        for (int i = 0; i < valueArray.Length; i++)
        {
          Int16 iType = valueArray[i].TypeCode;
          if (!(iType.Equals((Int16)LispDataType.ListBegin) |
                iType.Equals((Int16)LispDataType.ListEnd)) &&
               !memberValue(Mylist, valueArray[i].Value))// This works but***
              //Mylist.Contains() ? how does this work *********I need Help Here**********
          {
            Mylist.Add(new LispList(valueArray[i].TypeCode, valueArray[i].Value));
          }
        }
        Mylist.ForEach(delegate(LispList v)
           {
             result.Add(new TypedValue(v.iType, v.oValue));
           }
           );
        return result;
      }
      catch
      {
        result.Add(new TypedValue((int)LispDataType.Nil));
        return result;
      }
    }
    //(lst:merge '("a" "a" "b") '(1 1 2 2 "a"))
    [LispFunction("lst:merge")]
    public ResultBuffer lstmerge(ResultBuffer args)
    {
      ResultBuffer result = new ResultBuffer();
      try
      {
        TypedValue[] valueArray = args.AsArray();
        List<LispList> Mylist = new List<LispList>();
        for (int i = 0; i < valueArray.Length; i++)
        {
          Int16 iType = valueArray[i].TypeCode;
          if (!(iType.Equals((Int16)LispDataType.ListBegin) |
                iType.Equals((Int16)LispDataType.ListEnd)))
          {
            Mylist.Add(new LispList(valueArray[i].TypeCode, valueArray[i].Value));
          }
        }
        Mylist.ForEach(delegate(LispList v)
           {
             result.Add(new TypedValue(v.iType, v.oValue));
           }
           );
        return result;
      }
      catch
      {
        result.Add(new TypedValue((int)LispDataType.Nil));
        return result;
      }
    }
    //replacement for lisplist.contains
    public bool memberValue(List<LispList> arg1, object arg2)
    {
      ArrayList arList = new ArrayList();
      arg1.ForEach(delegate(LispList p) { arList.Add(p.oValue); });
      if (arList.Contains(arg2))
      {
        return true;
      }
      return false;
    }
  }
}

TonyT

  • Guest
Re: C# System.Collections.Generic lists
« Reply #1 on: September 03, 2006, 03:15:39 PM »
Hi.

The problem you have is that your class (which essentially
is re-inventing the TypedValue class) doesn't know how to
compare itself to another instance of itself.

Ignoring for a moment, the larger question of why you are
implementing a class that is essentially the same as TypedValue
(which already knows how to compare itself to itself), if you
wanted to do it (which you shouldln't), here's a simple example
that shows how it is done, and also shows why you don't need
to do it :-)

Code: [Select]
namespace ComplexComparisonDemo
{

   // essentially, a re-invention of TypedValue,
   // which also knows how to perform a value
   // comparison to other instances of itself:

   public struct MyTypedValue 
   {
      public MyTypedValue( int type, object value )
      {
         m_type = type;
         m_value = value;
      }
      private int m_type;
      private object m_value;

      public static bool operator ==( MyTypedValue a, MyTypedValue b )
      {
         return a.m_type == b.m_type && a.m_value.Equals(b.m_value);
      }

      public static bool operator !=( MyTypedValue a, MyTypedValue b )
      {
         return !( a == b );
      }

      public override bool Equals( object obj )
      {
         if( obj is MyTypedValue )
            return this == (MyTypedValue) obj;
         else
            return false;
      }

      public override int GetHashCode()
      {
         return base.GetHashCode();
      }
   }

   public static class CompareDemo
   {
      [CommandMethod("COMPARE_MYTYPEDVALUES")]
      public static void Compare_MyTypedValue()
      {
         Editor e = AcadApp.DocumentManager.MdiActiveDocument.Editor;

         List<MyTypedValue> list = new List<MyTypedValue>();

         list.Add( new MyTypedValue( (int) DataType.String, "a string" ) );
         list.Add( new MyTypedValue( (int) DataType.Long, 99 ) );
         list.Add( new MyTypedValue( (int) DataType.Double, 44.02 ) );

         // The data we'll search for:
         MyTypedValue itemToFind = new MyTypedValue( (int) DataType.Long, 99 );

         if( list.Contains( itemToFind ) )
            e.WriteMessage( "\nitem found." );
         else
            e.WriteMessage( "\nitem not found." );
      }

      // And this shows why you really don't need
      // a TypedValue-lookalike class like the above,
      // to start with:

      [CommandMethod("COMPARE_TYPEDVALUES")]
      public static void Compare_TypedValue()
      {
         Editor e = AcadApp.DocumentManager.MdiActiveDocument.Editor;

         // An array of typed values to search in:

         TypedValue[] values = new TypedValue[] {
            new TypedValue((int) DataType.String, "a string"),
            new TypedValue((int) DataType.Long, 99),
            new TypedValue((int) DataType.Double, 44.02) };

         // The data we'll search for:

         TypedValue itemToFind = new TypedValue( (int) DataType.Long, 99 );

         // Here's a way to get a stongly typed list of
         // TypedValues from an array, which you can
         // then call .Contains on:

         List<TypedValue> list = new List<TypedValue>( values );

         if( list.Contains( itemToFind ) )
            e.WriteMessage( "\nitem found in List<TypedValue>" );
         else
            e.WriteMessage( "\nitem not found in List<TypedValue>" );

         // Here's another way, which is cool because it doesn't
         // make a copy of the input array, rather it just wraps
         // an ArrayList around the existing array:

         ArrayList arrayList = ArrayList.Adapter( values );

         if( arrayList.Contains( itemToFind ) )
            e.WriteMessage( "\nitem found in ArrayList" );
         else
            e.WriteMessage( "\nitem not found in ArrayList" );
      }
   }
}

I'm assuming that you're mostly doing this for the sake
of learning, which is fine, but looking at your endeavour
from a more practicle perspective, you are probably not
going to gain much by implementing a 'remove duplicate'
function for LISP, in .NET, unless you do it right.

IMHO, 'do it right' , means:

1. Eliminate your class, and use the array of TypedValue[]
returned by ResultBuffer.AsArray(), directly (you can use
either of the techniques shown in the code example, to
convert to a List<TypedValue> or ArrayList, that you can
then call Contians() or BinarySearch() on).

2. Sort the input array of TypedValues (by the datatype,
and the datavalue).

To do that, you must implement an IComparer that will be
able to compare two TypedValues. The comparer would
first compare the datatype member of 2 TypedValues, and
if they're not equal, return the reuslt of the comparison,
else if the datatype members are equal, then it would just
return the result of comparing the 2 datavalue members. 

3. Use BinarySearch() to find existing elements in the list,
rather than Contains(), because the former is much faster
(at the cost of requiring the list to be sorted once).  If
the input list is large, this will result in a major performance
improvement over a linear search using Contains().

Taking that approach should give you an implemention for
removing list duplicates, that's faster than any LISP-based
solution. But, also remember that you can't pass it any type
of LISP data, and *never* pass it a selection set (you would
not believe what happens when you do :-).
« Last Edit: September 04, 2006, 01:45:31 AM by TonyT »

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8661
  • AKA Daniel
Re: C# System.Collections.Generic lists
« Reply #2 on: September 04, 2006, 04:08:15 AM »
Wow! Thanks Tony! Yes this is for educational purposes as you already guessed. When I cracked open my C# for dummies book and read the examples of using lists … well you have seen the results, it never occurred to use the already made TypedValue type in my list. Here’s what I have so far.

Code: [Select]
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
namespace lisptest
{
  public class lstfunc
  {
    // (lst:RemoveDuplicateItems (list "a" "a" "b" 1 1 2 2 "a")) //mod spelling
    [LispFunction("lst:RemoveDublicateItems")]
    public ResultBuffer lstRemoveDublicateItems(ResultBuffer args)
    {
      try
      {
        List<TypedValue> ValueList = new List<TypedValue>(args.AsArray());
        List<TypedValue> ResultList = new List<TypedValue>();
        foreach (TypedValue i in ValueList)
        {
          if (!ResultList.Contains(i))
          {
            ResultList.Add(i);
          }
        }
        return new ResultBuffer(ResultList.ToArray());
      }
      catch
      {
        TypedValue[] result = new TypedValue[] { new TypedValue((int)LispDataType.Nil) };// OOPs
        return new ResultBuffer(result);
      }
    }
    //(lst:merge '("a" "a" "b") '(1 1 2 (2) "a"))
    [LispFunction("lst:merge")]
    public ResultBuffer lstmerge(ResultBuffer args)
    {
      ResultBuffer result = new ResultBuffer();
      try
      {
        List<TypedValue> ValueList = new List<TypedValue>(args.AsArray());
        foreach (TypedValue i in ValueList)
        {

          if (!(i.TypeCode.Equals((int)LispDataType.ListBegin) |
               (i.TypeCode.Equals((int)LispDataType.ListEnd))))
          {
            result.Add(new TypedValue(i.TypeCode,i.Value));
          }
        }
        return result;
      }
      catch
      {
        result.Add(new TypedValue((int)LispDataType.Nil));
        return result;
      }
    }
  }
}


Ok I had to try 

Code: [Select]
(defun c:blowUpMyMachine ()
 (setq a (entlast))
 (setq b (ssadd a))
 (lst:RemoveDublicateItems (list a a a b b b "c" ))
)

Resulted in

Code: [Select]
(<Entity name: 7ef95048> <Selection set: c> <Selection set: d> <Selection set: e> "c")


Now that’s weird how the selection set increments!
Again thanks for your help  and thanks for your great examples Tony.

Dan

« Last Edit: September 06, 2006, 05:54:59 PM by Danielm103 »

mohnston

  • Bull Frog
  • Posts: 305
  • CAD Programmer
Re: C# System.Collections.Generic lists
« Reply #3 on: September 05, 2006, 12:09:11 PM »
For removing duplicates I have used the SortedList.
It throws an error when you try to add an element with a key that is already in the list.
It's amazing what you can do when you don't know what you can't do.
CAD Programming Solutions

TonyT

  • Guest
Re: C# System.Collections.Generic lists
« Reply #4 on: September 05, 2006, 03:07:49 PM »
Now that’s weird how the selection set increments!

Yes, and that's because what you get back is not the same
selection set that you passed in.  My advice is to try to avoid
passing selection sets between LISP and .NET, because the
runtime has to convert them to arrays of ObjectIds (going to
managed code), and back to a 'legacy' selection set when
going the other way.