1

I have an array of structs, where the struct has three integer fields. It is sorted by one of the fields, say F, and I want a way to do a binary search with respect to this field, that is, a function of the form binarySearch(mystruct[] myarray, int val) which returns the index of the structure in which field F = val. I know that there is an existing Array.BinarySearch(T[] array, T value) function, but it only allows to search for a type T that is the same as the types in the array. This means that if I want to search with respect to a value, I need to create a new struct and set field F to that value just so that I can pass it to this function. I don't think there would be significant performance overhead but it seems ugly. The other way I can think is to implement the function myself, but this also seems inelegant when something so similar exists. Any suggestions for a better way or which way would be preferred?

3 Answers 3

4

You can either implement IComparable<T> for your struct to compare on the field (F), or you can create an IComparer<> for your struct that will compare based on that field and pass that into Array.BinarySearch().

So either:

// using IComparable<T>
public struct MyStruct : IComparable<MyStruct>
{
    public int F { get; set; }

    // other fields that should not affect "search"
    public int X { get; set; }

    public int CompareTo(MyStruct other)
    {
        return F.CompareTo(other.F);
    }
}

Which can be called as:

MyStruct target = new MyStruct { F = 13 };

Array.BinarySearch(arrayOfMyStruct, target);

Or a separate IComparer<MyStruct>:

public struct MyStruct 
{
    public int F { get; set; }

    // other non-sort/search affecting properties
    public int X { get; set; }
}

public struct MyStructComparer : IComparer<MyStruct>
{
    public int Compare(MyStruct x, MyStruct y)
    {
        return x.F.CompareTo(y.F);
    }
}

Which can be called like:

MyStruct target { F = 13; }

Array.BinarySearch(myArrayOfStruct, target, new MyStructComparer());

The first has less code, but it strongly couples ordering to the type, which if you want to alter ordering based on situation (that is, allow multiple sort orders), this isn't ideal. The latter gives more flexibility in that you can provide multiple different orders independent of the struct, but it does require an extra class.

UPDATE

If you don't want to create a dummy struct to compare against, you can implement IComparable like:

public struct MyStruct : IComparable
{
    public int F { get; set; }

    // other non-sort/search affecting properties
    public int X { get; set; }

    public int CompareTo(object other)
    {
        // if the type is NOT an int, you can decide whether you'd prefer
        // to throw, but the concept of comparing the struct to something
        // unknown shouldn't return a value, should probably throw.
        return F.CompareTo((int)other);
    }
}

Which could be called like:

Array.BinarySearch(arrayOfMyStruct, 13);

But again, this strongly ties your implementation of the class to a given comparison type, which I think is uglier than using a dummy search target, but that's my personal preference. Personally, especially with initializer syntax being as short as it is, I prefer to use the dummy target:

var target = new MyStruct { F = 13 };

UPDATE: You can support both int and MyStruct comparissons, but it gets messy quickly, which is why I personally, again, recommend using the dummy struct to avoid the headaches:

// implement IComparable<int> for the int search (w/o dummy), and IComparable<MyStruct> for sort
public struct MyStruct : IComparable, IComparable<MyStruct>, IComparable<int>
{
    public int F { get; set; }

    // other non-sort/search affecting properties
    public int X { get; set; }

    public int CompareTo(object other)
    {
        if (other is int) 
            return F.CompareTo((int)other);

        if (other is MyStruct)
            return F.CompareTo((MyStruct)other);

        throw new InvalidOperationException("Other must be int or MyStruct.");
    }

    public int CompareTo(MyStruct other)
    {
        return F.CompareTo(other.F);
    }

    public int CompareTo(int other)
    {
        return F.CompareTo(other);
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I almost answered like this, but then I reread the question. He/she does not want to create a new struct to compare to.
@kendfrey: Yep, the OP doesn't want to, but it is a cleaner solution. I threw in IComparable for completeness, but to me that's even uglier as it strongly ties the data to a particular ordering concept.
@kendfrey, James, Jon. Thanks for the responses guys. Its not that I don't want to do something, but I am asking what the most elegant way would be in your opinions. I guess James and Jon think a dummy struct is the best. Kendfrey, your solution looks good without dummy structs - but would you prefer dummy structs to it?
@Sanorita: The main concern with not using the dummy structs, for me, is you tie exactly one comparaisson to the class, which makes it brittle. If you want to implement IComparable<MyStruct> as well, for sorting, it begins to get weird because IComparable<MyStruct> and IComparable would expect different targets (updated example above to show what i mean). You could do some type checking in ComapreTo(object other) and switch to the appropriate call given context, but it begins to get convoluted fast. That's the main reason I personally say keep it simple and use the dummy struct.
1

If your struct implements IComparable, you can use:

// myValue is an the value of the field to compare to
Array.BinarySearch(myArray, myValue);

as described in http://msdn.microsoft.com/en-us/library/y15ef976.aspx

You can compare a struct to an object with IComparable, so you can pass in the value intead of a new struct. In your implementation of CompareTo, you can compare any value with the field value, allowing you to say 'My struct should be considered greater/less than this number'.

EDIT:

Here is an example of CompareTo for your struct:

public int CompareTo(object obj)
{
    if (obj is int)
    {
        return myIntField.CompareTo((int)obj);
    }
    return 0;
}

Comments

1

One way would be to create a custom IComparer<T> that compares instances of your struct based only on the value of that field and pass it to this overload of BinarySearch (you will also need to create a "dummy" struct instance to compare to). This is probably the purest solution.

However, as a practical matter you can use LINQ to project into a collection of field values and binary search into that; the resulting index will be the same as if you had searched the struct collection itself. For example:

var structs = new MyStruct[n];
var index = structs.Select(i => i.F).ToList().BinarySearch(42);

In the code above, F is the vame of the field and 42 is the value you are searching for (its type would be the type of F). This is not going to be as fast, but you don't need to write any code and speed could very well be irrelevant in your case.

Update: To clarify: obviously, the code above will be O(n) due to the projection operation so using binary search once after projecting like that is silly (you can simply do a linear search instead). However, if you intend to make multiple searches then it might start making sense.

I would definitely not recommend overriding Equals in your struct unless comparisons between instances are meant to be reduced to comparing F members everywhere in your application.

2 Comments

@downvoter: Please help me improve this answer by leaving a comment.
Forget about IComparer<T>. The OP doesn't want to make a dummy struct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.