Reputation: 131
I'm learning about Linq and how you can group things and I was looking through my previous solutions and I found this:
public IList<IGrouping<int, int>> GroupEvenAndOddNumbers(int[] numbers)
{
return numbers.ToLookup(x => x & 1).ToList();
}
This goes against the way I've been doing groupings, but I couldn't find a way to do it with the normal way. The problem is that I don't understand why this works, which shouldn't be a problem, but it would still be nice to know why this works. The part that I don't understand is the "x & 1" part. What logic is applied here?
Upvotes: 1
Views: 495
Reputation: 13813
There is an interesting interaction between parity and binary numbers. All odd numbers end in 1
, all even numbers end in 0
.
Think of the decimal system, base 10. How do you know something is divisible by 10? Because it ends in a 0.
This works for every base. In base 8 (octal), numbers divisible by 8 end in a 0. In base 16 (hexadecimal), numbers divisible by 16 end in a 0. In base 12345, all numbers divisible by 12345 end in a 0. Pick any number, it always works.
And in base 2 (binary), numbers that are divisible by 2 (= even numbers, by definition) end in a 0.
&
is the bitwise AND operator. Very simply put, you pass it two binary values, and for each digit in the same place, it does a digitA AND digitB
evaluation. The outcome is the value from these AND
evaluations.
First of all, an AND
evaluation only returns true when BOTH inputs are true. If at least one is false, the outcome is definitely also false.
Input A | Input B | Result |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
Now let's look at two random binary numbers, and for each digit position, figure out what the AND result would be.
10110100
10001011
-[AND]------------
10000000
Notice how only in the leftmost position, both bits are 1 (true), and therefore the result is also 1 (true). In all other positions, there is always a 0 (false) in one of the two input numbers, and therefore the outcome is 0 (false)
Let's now look at a special case. I'm going to hide the first number, and only show you the second number. Try to figure out how much of the result you can already guess.
????????
00000001
-[AND]------------
????????
Seems impossible, right? Well, let's think about it. In any position, if there is at least one 0, the result is definitely 0. Even though we don't know the first number, the second number already reveals that the first 7 digit positions will definitely result in a 0, because the second number already forces this by having 0s in those positions.
So now we know this:
????????
00000001
-[AND]------------
0000000?
Let's think about that last digit. When will it be 1, when will it be 0? Think about this before reading the answer below.
If the first number ends on a 0, the result will also end on a 0, because
0 AND 1 = 0
.
If the first number ends on a 1, the result will also end on a 1, because1 AND 1 = 1
Remember what we said before? In binary, all odd numbers end in 1
, all even numbers end in 0
. So we could rewrite our earlier conclusion to say the following:
If the first number is even, the result will also end on a 0, because
0 AND 1 = 0
.
If the first number is odd, the result will also end on a 1, because1 AND 1 = 1
We can invert that logic:
If the result ends on a 0, then the first number is even.
If the result ends on a 1, then the first number is odd.
numbers.ToLookup(x => x & 1).ToList()
ToLookup is essentially a group by statement. It groups all elements which have the same calculated value in the same group. These groups are being made based on the calculated value x & 1
.
There are only two possible groups here. Using our demo values, the result is either 00000000
or 00000001
(because the first 7 digits are always 0, and the last digit could be different).
In the group labeled 00000000
, you will find all numbers which ended on a 0
, which is all even numbers.
In the group labeled 00000001
, you will find all numbers which ended on a 1
, which is all odd numbers.
The labels of the groups are of no importance. What is important here is that you've now correctly separated the evens and the odds.
There are other possible solutions here, but they all boil down to the same principle. One other example would be
var evenNumbers = numbers.Where(x => x % 2 == 0).ToList();
var oddNumbers = numbers.Where(x => x % 2 == 1).ToList();
However, bit logic tends to be slightly more performant. This comes at the cost of bit logic being rather difficult, but in such a simple case as looking for number parity (even/odd), the performance gain is worth the minor complexity.
Upvotes: 5
Reputation: 169370
The & operator computes the logical AND of its operands and an odd number & 1 is always true
(1
) and an even number & 1 is always false
(2
).
So you're getting a lookup with two keys, 0
and 1
, where the first groups contains all even numbers in numbers
and the second group contains all odd numbers. It's as simple as that.
Upvotes: 0
Reputation: 19111
ToLookup
groups numbers
by the result of x & 1
.
& is bitwise logical AND operator.
x & 1
equals to 0
when x
is even and equals to 1
when it is odd. So ToLookup
creates exactly 2 groups of elements: One with key 0
for even numbers, the other with key 1
for odd numbers.
Upvotes: 0