.NET Forum / Languages / C# / March 2008
Can you do a BinarySearch with an anonymous method?
|
|
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 MagazinesGet 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 ...
|
|
|