Search Unity

C# having trouble using .find on a List

Discussion in 'Scripting' started by SpyridonZ, May 26, 2011.

  1. SpyridonZ

    SpyridonZ

    Joined:
    Jun 7, 2009
    Posts:
    97
    I have a list and I've tried using list.Find, and list.FindIndex, and neither work.

    Here is an example of my code.

    Code (csharp):
    1. int result = NPCStringList.FindIndex(NPCname + "_" + varName);
    I get the following error...

    Code (csharp):
    1. error CS1503: Argument `#1' cannot convert `string' expression to type `System.Predicate<string>'
    I'm not sure how to handle this error? Any advice plz?
     
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    you can not use find by name, find on .NET (as you can read on the MSDN documentation, including an example of how it has to look) bases on a predicate that is used to find the appropriate object.
    Thats cause lists store Objects / Generics, they don't store strings, as such searching and sorting predicated need to be implemented by you to work according the data you store in
     
  3. SpyridonZ

    SpyridonZ

    Joined:
    Jun 7, 2009
    Posts:
    97
    So what would be the best way to do this...

    Converting the List in to an Array and finding the desired index that way?

    Or would it just be faster to foreach through the list to search manually?
     
    Last edited: May 26, 2011
  4. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    You need to use a predicate.

    They are quite easy:

    Code (csharp):
    1.  
    2.  
    3.  
    4. using UnityEngine;
    5. using System.Collections;
    6. using System.Collections.Generic;
    7. public class PredicateExample : MonoBehaviour {
    8.    
    9.     List<string> listExample =  new List<string>();
    10.    
    11.     private string findName = "";
    12.    
    13.     // Use this for initialization
    14.     void Start () {
    15.        
    16.         for (int i =0; i<10; i++)   listExample.Add("Cheese" + i.ToString());
    17.        
    18.         findName = "Cheese8";
    19.        
    20.         print(listExample.FindIndex(isName));
    21.    
    22.     }
    23.    
    24.     private bool isName(string name)
    25.     {
    26.        
    27.         return (name==findName);
    28.     }
    29. }
    30.  
    31.  
     
    Static4242 likes this.
  5. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Code (csharp):
    1. int result = NPCStringList.FindIndex(npcString => npcString == NPCname + "_" + varName);
     
  6. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    Nice.

    I need to learn the lambda.
     
    Last edited: May 26, 2011
  7. dilbertian

    dilbertian

    Joined:
    Aug 13, 2010
    Posts:
    74
    With the example below, if I want to find where myItems.ID == 2 within SysManager, how do I do that with a predicate?

    Obviously I can just do this, but I might feel cheap and used ...as I really want to learn this predicate thing!
    Code (csharp):
    1.  
    2. int findID=2;
    3. foreach (_MyClass mc in myItems) {
    4.     if (mc.ID == findID) {
    5.         return mc;
    6.     }
    7. }
    8.  
    Thanks,

    -Tim

    ------

    Here is my hypothetical code example

    For MyClass.cs I have something like this:
    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. namespace _MyCodeStuff
    7. {
    8.     public class MyClass {
    9.        
    10.         public int  ID {
    11.             get {return _id;}
    12.         }
    13.         public string Name {
    14.             get {return _name;}
    15.         }
    16.        
    17.         private int             _id;
    18.         private string          _name=string.Empty;
    19.  
    20.                 // constructor
    21.         public MyClass(int id, string name)
    22.         {
    23.             try
    24.             {
    25.                 _id=ID
    26.                 _name=name;
    27.             }
    28.             catch (Exception e) {
    29.                 Debug.LogError("Error in MyClass Constructor1:" + e);
    30.             }
    31.         }
    32.     }
    33. }
    34.  
    35.  
    And then I have another Manager type entity which has a list of these items called SysManager.cs I have something like this:
    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using _MyCodeStuff;
    6.  
    7. public class SysManager : MonoBehaviour {
    8.     public List<MyClass> myItems=new List<MyClass>();
    9.  
    10.     public void Start() {
    11.               myItems.Add(new MyClass(1, "Name1"));
    12.               myItems.Add(new MyClass(2, "Name2"));
    13.               // etc...
    14.     }
    15. }
    16.  
     
  8. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    You can find out on the List.Find page.

    Again, I recommend lambda notation for brevity, as long as it won't be confusing somehow:
    Code (csharp):
    1. myItems.Find(myItem => myItem.ID == 2)
     
  9. dilbertian

    dilbertian

    Joined:
    Aug 13, 2010
    Posts:
    74
    Thanks for the info! I can't get that to work. When I get more time over the weekend, maybe I'll post a more comprehensive example and you can give it a try. I guess maybe this is one of those Java'esqe wanna-be C#isms, but it makes you want to say WTF were they thinking when they came up with this Find routine - and it had better be like 5000 times faster than my 'foreach' (which works perfectly now), or I'm going to be way pissed!

    Thanks,

    -Tim

     
  10. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    Wtf, nothing in this sentence makes sense, .Find is not Java-esque at all, as java doesn't really have lambda functions. And .Find is likely to be slower then a simple for or foreach loop.

    Also, this code makes no sense at all (the part inside the constructor):

    Code (csharp):
    1.  
    2.     public class MyClass {
    3.        
    4.         public int  ID {
    5.             get {return _id;}
    6.         }
    7.         public string Name {
    8.             get {return _name;}
    9.         }
    10.        
    11.         private int             _id;
    12.         private string          _name=string.Empty;
    13.  
    14.                 // constructor
    15.         public MyClass(int id, string name)
    16.         {
    17.             try
    18.             {
    19.                 _id=ID
    20.                 _name=name;
    21.             }
    22.             catch (Exception e) {
    23.                 Debug.LogError("Error in MyClass Constructor1:" + e);
    24.             }
    25.         }
    26.     }
    27.  
    It's completely ass-backwards, it can never throw an exception (why is there a try/catch there, assignment never throws exceptions). And you're assigning from the same variable (through a property) to the same variable and ignoring the parameters passed to the constructor...

    I do not want to be mean, but I would recommend you to read a basic C# book like "Programming C#" by O'Reilly as you seem to have miss-understood a couple of very basic concepts. Anyway, this is how an experienced C# developer would write this piece of code:

    Code (csharp):
    1.  
    2. public class MyClass {
    3.        
    4.     public int Id { get; private set; }
    5.     public string Name { get; private set; }
    6.  
    7.     public MyClass(int id, string name)
    8.     {
    9.         Id = id;
    10.         Name = name;
    11.     }
    12. }
    13.  
     
    Last edited: Nov 26, 2011
  11. andorov

    andorov

    Joined:
    Feb 10, 2011
    Posts:
    1,061
    As fholm has pointed out, predicates are going to be slower than the foreach. Probably a lot slower since a predicate is like a virtual function and requires many more calls to do... The compiler might optimize a bit, but it will never be faster than the foreach.

    If you're looking for performance when you're looking up items by a key, use the Dictionary<,> storage structure. Be careful though, sometimes for small lists, the dictionary's overhead makes it slower than list.
     
  12. dilbertian

    dilbertian

    Joined:
    Aug 13, 2010
    Posts:
    74
    Thanks guys for the updates and to the code that I had provided, it was just a simple hypothetical example that I typed up quickly, yes I see the typo - (id being passed into constructor and ID being used for assignment). The try-catch was just there because I do that out of habit, as most of my constructors are much more complex and most ARE absolutely needed.

    My data subset is guaranteed (in this situation) to always be a collection of about 100-200 or less items, so with that in mind and the info on the predicates execution complications, I may indeed continue to stick with the foreach as I am now using. I was excited about using the .Find as I am new to the List<> and thought this might have potential to increase performance over my current methodology but as you can tell from my incoherence in a previous post, I had grown quite frustrated attempting its implementation.
     
  13. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    You do not need to worry about performance when you're searching as few elements as ~200, unless you are doing it several hundred times per frame.

    If you want a version with better performance, you should look up HashSet<T> on the MSDN docs for C#/.NET, which is made for checking if some value X is contained in some set of items Y.
     
  14. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    Wow... old thread!

    Lambda expressions, once you know them make code like this simple to read and quick to do. Contrary to popular opinion, I did a quick micro benchmark and found that List.For was significantly faster than the trivial for solution.

    Code (csharp):
    1.  
    2. Rough Results ('000 Its/Second)                
    3.                                        
    4. Iterations   List.For   For    
    5. 1       180 100    
    6. 100     750 120-180    
    7. 10000       1000    120-180    
    8. 1000000     1000    180     [Very consistant]
    9.  
    http://pastebin.com/SbPjifkd


    So basically this once again comes down to readability, not performance. While it took me a wee bit to get my head around them, I find lambda's very easy to read, so that's my preference.
     
  15. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    NPSF3000: The danger of doing micro benchmarks is when you do them wrong ;)

    Here's a corrected piece of code which proves that a normal for loop with a == comparison is faster then doing List.Find:

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.Linq;
    6. using System.Diagnostics;
    7.  
    8. namespace ConsoleApplication1
    9. {
    10.     class Program
    11.     {
    12.         static void Main(string[] args)
    13.         {
    14.             const int iterations = 1000000;
    15.  
    16.             var data = new List<MyClass>(200);
    17.  
    18.             for (var i = 0; i < 200; i++)
    19.                 data.Add(new MyClass(i, "Guest " + i));
    20.  
    21.             var rnd = new Random();
    22.  
    23.             while (true)
    24.             {
    25.                 var dataToFind = new List<int>();
    26.                 for (var i = 0; i < iterations; ++i)
    27.                 {
    28.                     dataToFind.Add(rnd.Next(200));
    29.                 }
    30.  
    31.                 var sw1 = Stopwatch.StartNew();
    32.  
    33.                 for (var i = 0; i < iterations; i++)
    34.                 {
    35.                     var findID = dataToFind[i];
    36.                     data.Find(x => x.ID == findID);
    37.                 }
    38.  
    39.                 sw1.Stop();
    40.  
    41.                 var sw2 = Stopwatch.StartNew();
    42.  
    43.                 for (var i = 0; i < iterations; i++)
    44.                 {
    45.                     var count = data.Count;
    46.                     var findID = dataToFind[i];
    47.  
    48.                     for (var x = 0; x < count; x++)
    49.                         if (data[x].ID == findID) break;
    50.                 }
    51.  
    52.                 sw2.Stop();
    53.  
    54.                 Console.WriteLine();
    55.                 Console.WriteLine("{0} iterations:", iterations);
    56.                 Console.WriteLine("List.Find: {0}/s", (iterations * Stopwatch.Frequency / sw1.ElapsedTicks).ToString("N0"));
    57.                 Console.WriteLine("For      : {0}/s", (iterations * Stopwatch.Frequency / sw2.ElapsedTicks).ToString("N0"));
    58.                 Console.WriteLine();
    59.                 Console.WriteLine();
    60.                 Console.ReadLine();
    61.             }
    62.         }
    63.     }
    64.  
    65.     public class MyClass
    66.     {
    67.  
    68.         public int ID
    69.         {
    70.             get { return _id; }
    71.         }
    72.         public string Name
    73.         {
    74.             get { return _name; }
    75.         }
    76.  
    77.         private int _id;
    78.         private string _name = string.Empty;
    79.  
    80.         // constructor
    81.         public MyClass(int id, string name)
    82.         {
    83.             try
    84.             {
    85.                 _id = ID;
    86.                 _name = name;
    87.             }
    88.             catch (Exception e)
    89.             {
    90.                 Console.WriteLine("Error in MyClass Constructor1:" + e);
    91.             }
    92.         }
    93.     }
    94. }
    95.  
     
  16. andorov

    andorov

    Joined:
    Feb 10, 2011
    Posts:
    1,061
    Furthermore, lambda functions have an implicit run-time allocation in them.

    I WILL NOT HAVE GARBAGE. NOT EVEN 4 BYTES.

    *froths at the mouth*
     
  17. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    I did a big rant in the PasteBin about caching the count. To quote:

    So this doesn't prove that List.For is faster than For, but it does restate the need to either spend the LOC on performance [in this particular instance a waste], or go with the built in option. Going with for because it is faster without testing and optimisation can lead to poorer perfomance and maintainability!

    I like the caching of the rnd data - but it has a negligible impact on my results. If your worried about the 'randomness' it would have been quicker to simply reset the seed of the rnd before each test.
     
    Last edited: Nov 27, 2011