Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Languages / C# / March 2008

Tip: Looking for answers? Try searching our database.

Can you do a BinarySearch with an anonymous method?

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
tshad - 11 Mar 2008 06:38 GMT
I can do the following with Find:

Racer theRacer2 = racers.Find(delegate(Racer racer) { return racer.Car ==
"Ferrari"; });

But I can't seem to do the same with BinarySearch, this:

int iktr = racers.BinarySearch(delegate(Racer racer) { return racer.Car ==
"Ferrari"; });

gives me these errors:

The best overloaded method match for
'System.Collections.Generic.List<Generics.Racer>.BinarySearch(Generics.Racer)'
has some invalid arguments

and

Argument '1': cannot convert from 'anonymous method' to 'Generics.Racer'

Can I make this work with an anonymous method or do I need to create another
function to use BinarySearch?

Thanks,

Tom
Marc Gravell - 11 Mar 2008 08:08 GMT
List<T>.BinarySearch accepts an instance (T) and (optionally) an
IComparer<T> to use to test "higher/lower/equal". Unfortunately it
does not accept a Comparison<T> which would be suitable for an
anonymous delegate.So yes, "as is" you will have to write a class that
implements IComparer<Racer> to do this.
I have a property-oriented comparer up my sleeve if it would help,
which means you wouldn't have to write one for every property you need
to search on - although actually I would be more tempted to come up
with an extension method that took either a projection [i.e.
BinarySearch(r=>r.Car, "Ferrari")] or a composite comparison function
[i.e. BinarySearch(r=>r.Car.CompareTo("Ferrari"))].

Marc
Marc Gravell - 11 Mar 2008 08:30 GMT
Here you go - a C# 3 version amenable to binary-search on a sub-
property; actually it should work in C# 2 given a few minor changes,
but anonymous methods aren't as appealing as lambdas, and the
extension methods and improved type inference really help us here...

using System;
using System.Collections.Generic;

class Racer
{
   public string Car { get; set; }
}
static class Program
{
   static void Main(string[] args)
   {
       List<Racer> racers = new List<Racer>();
       Random rand = new Random(123456);
       for (int i = 0; i < 10000; i++)
       { // invent some random data
           racers.Add(new Racer { Car = rand.Next().ToString() });
       }
       // add some known data to look for and sort
       racers.Add(new Racer { Car = "12345" });
       racers.Sort(r => r.Car);

       int index = racers.BinarySearch(r => r.Car, "12345");
   }
}
public static class ListExt
{
   public static void Sort<TSource, TValue>(this List<TSource> list,
Func<TSource, TValue> selector)
   {
       list.Sort(new SelectorComparer<TSource, TValue>(selector,
null, false));
   }
   public static int BinarySearch<TSource, TValue>(this
IList<TSource> list,
       Func<TSource, TValue> selector, TValue value)
   {
       return BinarySearch(list, 0, list.Count, selector, value,
null);
   }
   public static int BinarySearch<TSource, TValue>(this
IList<TSource> list,
       Func<TSource, TValue> selector, TValue value,
IComparer<TValue> comparer)
   {
       return BinarySearch(list, 0, list.Count, selector, value,
comparer);
   }
   public static int BinarySearch<TSource, TValue>(this
IList<TSource> list, int index, int length,
       Func<TSource, TValue> selector, TValue value)
   {
       return BinarySearch(list, index, length, selector, value,
null);
   }
   public static int BinarySearch<TSource, TValue>(this
IList<TSource> list, int index, int length,
       Func<TSource, TValue> selector, TValue value,
IComparer<TValue> comparer)
   {
       if (comparer == null)
       {
           comparer = Comparer<TValue>.Default;
       }
       int lowerBound = index;
       int upperBound = (index + length) - 1;
       while (lowerBound <= upperBound)
       {
           int compareResult;
           int testIndex = lowerBound + ((upperBound - lowerBound) >>
1);
           try
           {
               compareResult =
comparer.Compare(selector(list[testIndex]), value);
           }
           catch (Exception exception)
           {
               throw new InvalidOperationException("Comparer failed",
exception);
           }
           if (compareResult == 0)
           {
               return testIndex;
           }
           if (compareResult < 0)
           {
               lowerBound = testIndex + 1;
           }
           else
           {
               upperBound = testIndex - 1;
           }
       }
       return int.MinValue;
   }
}

/// <summary>
/// Comparer to sort elements of T based on a projection
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <typeparam name="TKey">The projected type (usually a property)</
typeparam>
internal class SelectorComparer<T, TKey> : IComparer<T>
{
   private readonly Func<T, TKey> selector;
   private readonly IComparer<TKey> comparer;
   private readonly bool descending;

   /// <summary>
   /// Create a new comparer based on a projection
   /// </summary>
   /// <param name="selector">The projection function to apply to
obtain the key to compare</param>
   /// <param name="comparer">The comparer used to compare keys
(defaults if null)</param>
   /// <param name="descending">Should the order be considered
ascending or descending?</param>
   public SelectorComparer(
       Func<T, TKey> selector,
       IComparer<TKey> comparer,
       bool descending)
   {
       if (selector == null) throw new
ArgumentNullException("selector");
       this.selector = selector;
       this.comparer = comparer ?? Comparer<TKey>.Default;
       this.descending = descending;
   }

   int IComparer<T>.Compare(T x, T y)
   {
       return descending
           ? comparer.Compare(selector(y), selector(x))
           : comparer.Compare(selector(x), selector(y));
   }
}
Roger Frost - 11 Mar 2008 09:30 GMT
Gotta love full code samples...complete with XML Documentation Summaries!

Just giving you a hard time Marc, good job. :)

Signature

Roger Frost
"Logic Is Syntax Independent"

> Here you go - a C# 3 version amenable to binary-search on a sub-
> property; actually it should work in C# 2 given a few minor changes,
[quoted text clipped - 138 lines]
>    }
> }
Marc Gravell - 11 Mar 2008 10:22 GMT
> Gotta love full code samples...complete with XML Documentation Summaries!

The xml comments that /do/ exist are there from copy/paste from
something a bit more pucka (MiscUtil in this case). I don't normally
worry about such nicities in usenet posts... just the terse "invent
some random data" and "add some known data to look for and sort"...

Still, it demonstrates an alternative BinarySearch implementation
reasonably well ;-p

Marc
Ben Voigt [C++ MVP] - 11 Mar 2008 14:47 GMT
> Here you go - a C# 3 version amenable to binary-search on a sub-
> property; actually it should work in C# 2 given a few minor changes,
> but anonymous methods aren't as appealing as lambdas, and the
> extension methods and improved type inference really help us here...

There's a problem using BinarySearch like that... it's fragile.  The list
has to be sorted by the same comparer used for binary search.

Instead, use a SortedList, where the comparer is kept with the list and
can't become inconsistent between sorting and searching.
Marc Gravell - 11 Mar 2008 15:11 GMT
> There's a problem using BinarySearch like that... it's fragile.  The list
> has to be sorted by the same comparer used for binary search.

Yes, but it is hard to get around that and keep with a simple List<T>;
changing to SortedList<TKey,TValue> isn't necessarily trivial,
especially if you to allow different sorting (which may necessitate
different TKey).

Or put another way - it doesn't make List<T>.BinarySearch any more or
less brittle - it just makes it possible to call it without having to
write your own IComparer<T> each time.
KWienhold - 11 Mar 2008 08:13 GMT
> I can do the following with Find:
>
[quoted text clipped - 22 lines]
>
> Tom

BinarySearch is intended to be used to search for the index of a
specific instance of an object within the list, or the index of the
next greater item in the list if the original one isn't found, it
doesn't support anonymous methods.
You can specify a custom IComparer<T>, so you could create a comparer
that returns equality if the condition you are looking for is met,
ignoring the actual object you pass into the comparer, but I'm not
sure that is what you are looking for.

Why exactly do you want to use BinarySearch here? What would you
expect it to do?

hth,
Kevin Wienhold
tshad - 11 Mar 2008 15:29 GMT
>> I can do the following with Find:
>>
[quoted text clipped - 36 lines]
>Why exactly do you want to use BinarySearch here? What would you
>expect it to do?

I already have a list of about 200-300 items that I am doing about 100
searches on.  I am already doing a .Find on it - which works fine.  If I
were only doing 3 or 4 search, I might leave it as the time it took to sort
the list would more than nullify the advantage of the BinarySearch.

But with this many, a BinarySearch seems a pretty good way to go.  In my
list, I have a string which I am sorting and searching on with a couple of
other pieces of information that I use when I find the correct row.  I could
use a dataset (and maybe that is the better way to go) but I decided to use
a List instead.

Thanks,

Tom
>hth,
>Kevin Wienhold
jehugaleahsa@gmail.com - 16 Mar 2008 21:45 GMT
> I can do the following with Find:
>
[quoted text clipped - 22 lines]
>
> Tom

This is one of my frustrations. It is not unusual to want to search
for only part of the object stored in the list. For instance, you have
a Customer class sorted by their name. Given just a name, you have to
write an inconvenient class.

class CustomerNameSearcher : IComparer<Customer>
{
   private readonly string name;
   public CustomerNameSearcher(string name)
   {
       this.name = name;
   }

   public int Compare(Customer x, Customer y)
   {
       string rhs = x == null ? y : x;
       return Comparer<string>.Default.Compare(name, rhs.Name)
   }
}

I am not sure how one could write it to take a Comparison<T>, since T
would be a customer. Here, again, it is forcing your parameters to be
a customer. Perhaps it should provide a method with two generic
arguments, T and U, where T is customer and U is open. Of course, this
would mean the method wouldn't use a built-in delegate, and it
probably boils down to internal, MS politics.

Func<T, U, int> wah, wah!
Jon Skeet [C# MVP] - 16 Mar 2008 22:02 GMT
> This is one of my frustrations. It is not unusual to want to search
> for only part of the object stored in the list. For instance, you have
[quoted text clipped - 12 lines]
>     {
>         string rhs = x == null ? y : x;

Did you mean that to be a Customer variable declaration rather than
string? Note that it will still blow up in the next line if x and y are
both null.

>         return Comparer<string>.Default.Compare(name, rhs.Name)
>     }
[quoted text clipped - 4 lines]
> a customer. Perhaps it should provide a method with two generic
> arguments, T and U, where T is customer and U is open.

Any IComparer<T> can represent the logic of a Comparison<T> and vice
versa. In this case you'd just need a captured variable to represent
the name.

Now, what would be nicer would be to have a Func<T,U> which could be
applied for both ordering and binary searches, using the default
comparer of U. The first part is basically what OrderBy does in LINQ
(although an IComparer<U> can also be specified).

So, there'd be (on List<T>):

public void Sort<TKey>(Func<T,TKey> keySelector)

public int BinarySearch<TKey>(TKey key,
                             Func<T,TKey keySelector)

Neither of these would be hugely hard to write, and both can reasonably
be added as extension methods. Heck, you could even make your Sort
stable if you wanted to, which would be nice in some situations.

> Of course, this would mean the method wouldn't use a built-in
> delegate, and it probably boils down to internal, MS politics.

I see no reason whatsoever to think that politics is involved here.
"API isn't quite as nice as it could be" isn't exactly a news story.

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk

Peter Duniho - 16 Mar 2008 22:55 GMT
> This is one of my frustrations. It is not unusual to want to search
> for only part of the object stored in the list. For instance, you have
> a Customer class sorted by their name. Given just a name, you have to
> write an inconvenient class.

I guess "inconvenient" is a matter of opinion.  Sure, it'd be nice if  
BinarySearch took a Comparison<T> delegate rather than requiring an  
IComparer<T>.  And I'm not really sure why it doesn't (could be some  
legacy thing, or it could be that the designers didn't want people writing  
"brittle" implementations, as Ben describes it).

But Marc already posted one work-around that you should feel free to use,  
and which would avoid you ever having to write another "inconvenient  
class" again.  Just use his class where you need an IComparer<T> for  
specific properties.

While I understand your disappointment that the BinarySearch() method  
doesn't work with a Comparison<T>, I'm confused by your other comments:

> [...]
> I am not sure how one could write it to take a Comparison<T>, since T
> would be a customer.

What is "it" here?  Are you talking about the IComparer<T> class you  
posted?  Couldn't you just do something like this:

    class ComparisonComparer<T> : Comparer<T>
    {
        private Comparison<T> _comparison;

        public Comparer(Comparison<T> comparison)
        {
            _comparison = comparison;
        }

        public int Compare(T x, T y)
        {
            return _comparison(x, y);
        }
    }

Then you could just write your code like this:

    ComparisonComparer<Customer> comparerName =
        new ComparisonComparer<Customer>(delegate(Customer x, Customer y)
            {
                string rhs = x == null ? y : x;
                return Comparer<string>.Default.Compare(name, rhs.Name);
            });

Then just use that comparer instance for sorting and search as necessary.

(Note that I've just copied and pasted the comparison you posted...at the  
very least I'd just use String.CompareTo() instead of all that business  
with Comparer<string>, etc. and you've got at least two syntax errors and  
what looks to me to be a logic error.  But hopefully the general idea is  
clear, even if the comparison itself is not :) )

Again, as with Marc's code, you write the generic class once and you never  
have to write another Comparer implementation again if you don't want to.  
Maybe I'm missing something, but I really don't think things are as bad as  
your post makes them out to be.

Pete

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.