Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq OrderBy(Byte[]) values

Tags:

c#

linq

public class foo {
  int ID { get; set; }
  byte[] sort { get; set; }
}

public class barMaster {
    public void FooSource() {
        return List<foo> FromDataSource;
    }
    public void display() {
        List<foo> sortedFoo = FooSource().OrderBy(f => f.sort);
        UIElement = sortedFoo;
    }

I have a set of objects that contain a byte[] property that I want to OrderBy, however, OrderBy(byte[]) throws an error:

System.ArgumentException: At least one object must implement IComparable.

What can I do to OrderBy byte[] values?

like image 229
StormRider01 Avatar asked Dec 27 '22 00:12

StormRider01


2 Answers

As you've indicated that the arrays are of variable length (as it's a SQL Server hierarchy ID), you absolutely need to create a custom IComparer<byte[]> implementation.

The logic is simple:

  • Compare the first n bytes of each array byte-for-byte, where n is the number of bytes in the smaller of the two arrays. When a difference is detected between any byte, return the result of the comparison of the different bytes.
  • If the first n bytes are equal, return the comparison of the lengths of the two arrays.

This way, given a set of data like so:

00 01 02
00 01
01

When sorted, the results you'll get are:

00 01
00 01 02
01

That said, this is what your IComparer<byte[]> implementation will look like:

// I could be wrong in that this is called natural order.
class NaturalOrderByteArrayComparer : IComparer<byte[]>
{
    public int Compare(byte[] x, byte[] y)
    {
        // Shortcuts: If both are null, they are the same.
        if (x == null && y == null) return 0;

        // If one is null and the other isn't, then the
        // one that is null is "lesser".
        if (x == null && y != null) return -1;
        if (x != null && y == null) return 1;

        // Both arrays are non-null.  Find the shorter
        // of the two lengths.
        int bytesToCompare = Math.Min(x.Length, y.Length);

        // Compare the bytes.
        for (int index = 0; index < bytesToCompare; ++index)
        {
            // The x and y bytes.
            byte xByte = x[index];
            byte yByte = y[index];

            // Compare result.
            int compareResult = Comparer<byte>.Default.Compare(xByte, yByte);

            // If not the same, then return the result of the
            // comparison of the bytes, as they were the same
            // up until now.
            if (compareResult != 0) return compareResult;

            // They are the same, continue.
        }

        // The first n bytes are the same.  Compare lengths.
        // If the lengths are the same, the arrays
        // are the same.
        if (x.Length == y.Length) return 0;

        // Compare lengths.
        return x.Length < y.Length ? -1 : 1;
    }
}

As an aside, if your byte arrays were guaranteed to be the same length, as an alternative you can dynamically create the order by clause, sorting by the first element, then the second, etc, etc, like so:

static IEnumerable<foo> OrderBySortField(this IEnumerable<foo> items, 
    int sortLength)
{
    // Validate parameters.
    if (items == null) throw new ArgumentNullException("items");
    if (sortLength < 0) throw 
        new ArgumentOutOfRangeException("sortLength", sortLength,
            "The sortLength parameter must be a non-negative value.");

    // Shortcut, if sortLength is zero, return the sequence, as-is.
    if (sortLength == 0) return items;

    // The ordered enumerable.
    IOrderedEnumerable<foo> ordered = items.OrderBy(i => i.sort[0]);

    // Cycle from the second index on.
    for (int index = 1; index < sortLength; index++)
    {
        // Copy the index.
        int indexCopy = index;

        // Sort by the next item in the array.
        ordered = ordered.ThenBy(i => i.sort[indexCopy]);
    }

    // Return the ordered enumerable.
    return ordered;
}

And then you can simply call it like so:

// You have to supply the length of the array you're sorting on.
List<foo> sortedFoo = FooSource().
    OrderBySortField(sortLength).ToList();
like image 131
casperOne Avatar answered Jan 12 '23 09:01

casperOne


You can't order by the byte[] directly, since arrays don't implement IComparable. You would need to either order by the first byte (ie: OrderBy(f => f.sort[0]) or something else appropriate), or write your own IComparer<byte[]> and use that in the appropriate overload of OrderBy.

like image 34
Reed Copsey Avatar answered Jan 12 '23 11:01

Reed Copsey