I have a collection of the following object:
public class TestObject
{
public string Id { get; set;}
public int Value1 { get; set; }
public int Value2 { get; set; }
}
As you can see, it contains two integer values. I'd like to sort the collection by Value1
descending and then Value2
ascending.
For clarity, in SQL:
SELECT *
FROM testobjects
ORDER BY Value1 DESC
ORDER BY Value2 ASC
Or if you prefer, in c# linq:
testObjects
.OrderByDescending(o => o.Value1)
.ThenBy(o => o.Value2);
But here's the catch... I need to do this by combining the two numbers into a single numeric value that can then be sorted. (Why? CosmosDB, but that's aside the point!)
Off the top of my head it occurs to me that if we take (Value1 x 1000) - Value2
and then ordered by descending, provided Value2
doesn't exceed our arbitrary 1000 then this will probably work.
Will it? Surely there's a better way...?
So here's how you can make it compare Value1
and Value2
, both ascending.
Value1
into the high 32 bits of a 64-bit integer.Value2
, and place that into the low 32 bits.For example:
public static long getKey(int val1, int val2)
{
long key = val1;
key <<= 32;
uint flipped = (uint)val2 ^ 0x80000000;
key |= flipped;
return key;
}
That would sort Value1
and Value2
ascending. But you want Value1
to sort descending. So just flip all the bits in the first value. Change the first line to:
long key = (uint)~val1;
So how does this work? I'm going to explain using 4-bit signed integers, which have the range -8 .. 7
. The high bit is the sign bit. So the representation from low to high is:
Bit Unsigned
Binary Inverted Value Flipped Value
-8 1000 0111 7 0000 0
-7 1001 0110 6 0001 1
-6 1010 0101 5 0010 2
-5 1011 0100 4 0011 3
-4 1100 0011 3 0100 4
-3 1101 0010 2 0101 5
-2 1110 0001 1 0110 6
-1 1111 0000 0 0111 7
0 0000 1111 -1 1000 8
1 0001 1110 -2 1001 9
2 0010 1101 -3 1010 10
3 0011 1100 -4 1011 11
4 0100 1011 -5 1100 12
5 0101 1010 -6 1101 13
6 0110 1001 -7 1110 14
7 0111 1000 -8 1111 15
The first column is the number. Next column shows the two's complement binary representation. The "Inverted" column shows the binary result of inverting the bits, and the next column shows the decimal value that represents. This is the value that we place in the high bits of the result. As you can see, the numbers have switched places, with the lowest (-8) becoming the highest. That will give us a descending sort of the values.
The lower bits don't matter unless the high bits are equal. The "Bit Flipped" column shows the binary representation of the value after we flip the high bit. What this does is map the numbers -8 through 7 to the unsigned range 0 through 15, thus ensuring that the lower bits of the number are compared correctly.