2010-12-16 12 views
18

Estoy buscando una manera elegante de calcular el puntaje de una conjetura en el juego MasterMind en C#, preferiblemente usando LINQ.Algoritmo de puntuación de MasterMind en C# usando LINQ

En MasterMind, el generador de códigos genera un código secreto de 4 dígitos usando los dígitos del 1 al 6. Un dígito puede usarse más de una vez. Como un ejemplo, el código secreto es:

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

El descifrador intenta romper el código secreto presentando una conjetura. En este ejemplo, la suposición es:

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

(tanto el código y adivina ahora se almacenan en una matriz, pero otros tipos de colección están bien también).

El creador de código entonces "puntúa" esta suposición al anunciar el número de "negros" y "blancos". Se concede un negro por cada dígito de la conjetura que es correcto tanto en valor como en posición. Se otorga un blanco por cada dígito correcto colocado en la posición incorrecta. En este ejemplo, el puntaje es 1 negro (para el "1" en la posición 1) y 2 blancos (para el "1" y "2" en las posiciones 2 y 3).

Volver a la pregunta: Estoy buscando una forma elegante de calcular el puntaje de una conjetura en C#, preferentemente utilizando LINQ. Hasta ahora, yo he llegado con una declaración que calcula el número de negros:

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

que iba a proceder a lo largo de las líneas que el número de blancos es el número total de partidos (3) menos el número de negros Así que he intentado:

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

Pero, por desgracia, IEnumerable.Intersect() produce {1, 2} en lugar de {1, 1, 2}, porque se ve a sólo cifras distintas. Por lo tanto, calcula blancos = 1 en lugar de 2.

No se me ocurre otra forma de calcular "blancos", excepto el uso de bucles anidados estilo "C". ¿Puedes? Preferiblemente usando LINQ - Me gusta la forma en que un algoritmo puede expresarse en código usando LINQ. La velocidad de ejecución no es realmente un problema.

+0

¿Qué es exactamente lo que está mal con "C style nested loops", parece una tarea de tarea. –

+0

¿Cuál sería la puntuación de su valor secreto, si la conjetura fue "1111"? –

+2

@Ramhound: mi solución de estilo C toma 15 líneas. Estoy dominando LINQ y estoy convencido de que LINQ puede hacer esto en menos líneas que sean más legibles al mismo tiempo. Por cierto cuando estaba en la escuela, C era un lenguaje moderno :-) –

Respuesta

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

Dado:

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

continuación:

black == 1 && white == 2 
+0

Le doy a esta respuesta 4 negros. La forma en que se calcula el blanco es exactamente lo que tenía en mente, pero no podía expresar en código. Además, parece que el código se trata de colecciones, y no como una consulta de base de datos. Gracias, Enigmatividad! (Por cierto, mi código de "estilo C" es el doble de rápido :-) –

+0

Una solución muy elegante. ¿Hay algún recurso en alguna parte para algoritmos para este juego en particular? Estoy buscando una solución similar, pero en Objective-C –

3

Ésta es una forma (suponiendo que he entendido correctamente el problema):

  1. Encuentra la puntuación negro - Esto es bastante fácil; simplemente es cuestión de comprimir las secuencias y contar el número de elementos correspondientes que coincidan.

  2. Encuentra el número de "elementos comunes" entre ambas secuencias - Esta debe ser la suma de las puntuaciones blancas y negras.

  3. Encuentra la puntuación blanco - simplemente la diferencia entre 2. y 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: Mezclada blanco y negro.

EDITAR: (El OP no está en .NET 4) En .NET 3.5, se puede calcular con negro:

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

nice one @Ani, ojalá mis habilidades linq fueran este l337 –

+0

IEnumerable.Zip() debe ser un método .NET 4? VS2008 no se compilará ... –

+0

@Fred K: Sí. Pondrá un equivalente .NEt 3.5. – Ani

0

respuesta de Ani es buena. Aquí hay una manera más agradable (más clara) de hacer esa agrupación y unir.

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;