开发者

MasterMind scoring algorithm in C# using LINQ

I'm looking for an elegant way to compute the score of a guess in the MasterMind game in C#, preferably using LINQ.

In MasterMind, the codemaker generates a secret code of 4 digits using the digits 1 through 6. A digit may be used more than once. As an example, the secret code is:

int[] secret = { 1, 2, 3, 1 };

The codebreaker tries to break the secret code by presenting a guess. In this example, the guess is:

int[] guess  = { 1, 1, 2, 2 };

(Both code开发者_如何学C and guess are now stored in an array, but other collection types are okay too).

The codemaker then "scores" this guess by announcing the number of "blacks" and "whites". A black is awarded for each digit from the guess which is correct in both value and position. A white is awarded for each correct digit placed in the wrong position. In this example, the score is 1 black (for the "1" in position 1) and 2 whites (for the "1" and "2" in positions 2 and 3).

Back to the question: I'm looking for an elegant way to compute the score of a guess in C#, preferably using LINQ. So far, I've come up with a statement that computes the number of blacks:

int blacks = new int[] { 0, 1, 2, 3 }.Count(i => (guess[i] == secret[i]));

I was going to proceed along the lines that the number of whites is the total number of matches (3) minus the number of blacks. So I tried:

int whites = guess.Intersect(secret).Count() - blacks;

But, alas, IEnumerable.Intersect() produces { 1, 2 } instead of { 1, 1, 2 }, because it looks at distinct digits only. So it computes whites = 1 instead of 2.

I cannot come up with another way of computing "whites", except from using "C" style nested loops. Can you? Preferably using LINQ - I like the way an algorithm can be expressed in code using LINQ. Execution speed is not really an issue.


var black = guess
        .Zip(secret, (g, s) => g == s)
        .Count(z => z);

var white = guess
        .Intersect(secret)
        .Sum(c =>
            System.Math.Min(
                secret.Count(x => x == c),
                guess.Count(x => x == c))) - black;

Given:

int[] secret = { 1, 2, 3, 1 };
int[] guess  = { 1, 1, 2, 2 };

Then:

black == 1 && white == 2


Here's one way (assuming I've understood the problem correctly):

  1. Find the black score - This is easy enough; it's simply a matter of zipping the sequences up and counting the number of corresponding elements that match.

  2. Find the number of "common elements" between both sequences - This must be the sum of the white and black scores.

  3. Find the white score - Simply the difference between 2. and 1.


// There must be a nicer way of doing this bit
int blackPlusWhite = secret.GroupBy(sNum => sNum)
                           .Join(guess.GroupBy(gNum => gNum),
                                 g => g.Key,
                                 g => g.Key,
                                (g1, g2) => Math.Min(g1.Count(), g2.Count()))
                           .Sum();   

int black = guess.Zip(secret, (gNum, sNum) => gNum == sNum)
                 .Count(correct => correct); 

int white = blackPlusWhite - black;

EDIT: Mixed up black and white.

EDIT: (The OP is not on .NET 4) In .NET 3.5, you can calculate black with:

int black = Enumerable.Range(0, secret.Count)
                      .Count(i => secret[i] == guess[i]); 


Ani's answer is good. Here's a nicer (clearer) way to do that grouping and joining.

ILookup<int, int> guessLookup = guess.ToLookup(i => i);

int blackPlusWhite
(
  from secretNumber in secret.GroupBy(i => i)
  let secretCount = secretNumber.Count()
  let guessCount = guessLookup[secretNumber.Key].Count()
  select Math.Min(secretCount, guessCount)
).Sum()

int black = Enumerable.Range(0, secret.Count).Count(i => guess[i] == secret[i]);

int white = blackPlusWhite - black;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜