Can you do a BinarySearch with an anonymous method?

T

tshad

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
 
M

Marc Gravell

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
 
K

KWienhold

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.Race­r)'
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

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
 
M

Marc Gravell

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));
}
}
 
R

Roger Frost

Gotta love full code samples...complete with XML Documentation Summaries!


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

--
Roger Frost
"Logic Is Syntax Independent"



Marc Gravell said:
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));
}
}
 
M

Marc Gravell

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
 
B

Ben Voigt [C++ MVP]

Marc said:
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.
 
M

Marc Gravell

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.
 
T

tshad

I can do the following with Find:
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?

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
 
J

jehugaleahsa

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.Race­r)'
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

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!
 
J

Jon Skeet [C# MVP]

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;

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)
}
}

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.

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.
 
P

Peter Duniho

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
[...]
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top