简体   繁体   中英

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 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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM