Usando R para resolver el juego Lucky 26


15

Estoy tratando de mostrarle a mi hijo cómo se puede usar la codificación para resolver un problema planteado por un juego, así como ver cómo R maneja los grandes datos. El juego en cuestión se llama "Lucky 26". En este juego, los números (1-12 sin duplicados) se colocan en 12 puntos en una estrella de David (6 vértices, 6 intersecciones) y las 6 líneas de 4 números deben sumar 26. De las aproximadamente 479 millones de posibilidades (12P12 ) aparentemente hay 144 soluciones. Traté de codificar esto en R de la siguiente manera, pero la memoria es un problema que parece. Agradecería mucho cualquier consejo para avanzar la respuesta si los miembros tienen tiempo. Agradeciendo a los miembros de antemano.

library(gtools)

x=c()
elements <- 12
for (i in 1:elements)
{ 
    x[i]<-i
}

soln=c()            

y<-permutations(n=elements,r=elements,v=x)  
j<-nrow(y)
for (i in 1:j) 
{
L1 <- y[i,1] + y[i,3] + y[i,6] + y[i,8]
L2 <- y[i,1] + y[i,4] + y[i,7] + y[i,11]
L3 <- y[i,8] + y[i,9] + y[i,10] + y[i,11]
L4 <- y[i,2] + y[i,3] + y[i,4] + y[i,5]
L5 <- y[i,2] + y[i,6] + y[i,9] + y[i,12]
L6 <- y[i,5] + y[i,7] + y[i,10] + y[i,12]
soln[i] <- (L1 == 26)&(L2 == 26)&(L3 == 26)&(L4 == 26)&(L5 == 26)&(L6 == 26) 
}

z<-which(soln)
z

3
No entiendo la lógica, pero deberías vectorizar tu enfoque. x<- 1:elementsy lo más importante L1 <- y[,1] + y[,3] + y[,6] + y[,8]. Esto realmente no ayudaría a su problema de memoria, por lo que siempre puede buscar en rcpp
Cole

44
por favor no ponga rm(list=ls())en su ERM. Si alguien copia y pega en una sesión activa, podría perder sus propios datos.
dww

Disculpas en rm (list = ls ()) ..
DesertProject

¿Confía en que solo hay 144? Todavía estoy trabajando en ello y obtengo 480 pero estoy un poco inseguro sobre mi enfoque actual.
Cole

1
@Cole, estoy obteniendo 960 soluciones.
Joseph Wood

Respuestas:


3

Aquí hay otro enfoque. Se basa en una publicación de blog de MathWorks de Cleve Moler , autor del primer MATLAB.

En la publicación del blog, para ahorrar memoria, el autor permuta solo 10 elementos, manteniendo el primer elemento como el elemento principal y el séptimo como el elemento base. Por lo tanto, solo las 10! == 3628800permutaciones necesitan ser probadas.
En el siguiente código,

  1. Generar las permutaciones de elementos 1a 10. Hay un total 10! == 3628800de ellos.
  2. Elija 11como elemento vértice y manténgalo fijo. Realmente no importa dónde comiencen las tareas, los otros elementos estarán en las posiciones relativas correctas .
  3. Luego asigne el 12º elemento a la 2da posición, 3ra posición, etc., en un forbucle.

Esto debería producir la mayoría de las soluciones, dar o tomar rotaciones y reflexiones. Pero no garantiza que las soluciones sean únicas. También es razonablemente rápido.

elements <- 12
x <- seq_len(elements)
p <- gtools::permutations(n = elements - 2, r = elements - 2, v = x[1:10])  

i1 <- c(1, 3, 6, 8)
i2 <- c(1, 4, 7, 11)
i3 <- c(8, 9, 10, 11)
i4 <- c(2, 3, 4, 5)
i5 <- c(2, 6, 9, 12)
i6 <- c(5, 7, 10, 12)

result <- vector("list", elements - 1)
for(i in 0:10){
  if(i < 1){
    p2 <- cbind(11, 12, p)
  }else if(i == 10){
    p2 <- cbind(11, p, 12)
  }else{
    p2 <- cbind(11, p[, 1:i], 12, p[, (i + 1):10])
  }
  L1 <- rowSums(p2[, i1]) == 26
  L2 <- rowSums(p2[, i2]) == 26
  L3 <- rowSums(p2[, i3]) == 26
  L4 <- rowSums(p2[, i4]) == 26
  L5 <- rowSums(p2[, i5]) == 26
  L6 <- rowSums(p2[, i6]) == 26

  i_sol <- which(L1 & L2 & L3 & L4 & L5 & L6)
  result[[i + 1]] <- if(length(i_sol) > 0) p2[i_sol, ] else NA
}
result <- do.call(rbind, result)
dim(result)
#[1] 82 12

head(result)
#     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
#[1,]   11   12    1    3   10    5    8    9    7     6     4     2
#[2,]   11   12    1    3   10    8    5    6    4     9     7     2
#[3,]   11   12    1    7    6    4    3   10    2     9     5     8
#[4,]   11   12    3    2    9    8    6    4    5    10     7     1
#[5,]   11   12    3    5    6    2    9   10    8     7     1     4
#[6,]   11   12    3    6    5    4    2    8    1    10     7     9

6

En realidad hay 960 soluciones. A continuación hacemos uso de Rcpp, RcppAlgos* , y el parallelpaquete para obtener la solución en poco más de 6 secondsmedio de 4 núcleos. Incluso si elige utilizar un enfoque de un solo subproceso con base R lapply, la solución se devuelve en unos 25 segundos.

Primero, escribimos un algoritmo simple C++que verifica una permutación particular. Notarás que usamos una matriz para almacenar las seis líneas. Esto es para el rendimiento, ya que utilizamos la memoria caché de manera más efectiva que el uso de 6 matrices individuales. También tendrá que tener en cuenta que C++usa indexación basada en cero.

#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::plugins(cpp11)]]

constexpr int index26[24] = {0, 2, 5, 7,
                             0, 3, 6, 10,
                             7, 8, 9, 10,
                             1, 2, 3, 4,
                             1, 5, 8, 11,
                             4, 6, 9, 11};

// [[Rcpp::export]]
IntegerVector DavidIndex(IntegerMatrix mat) {
    const int nRows = mat.nrow();
    std::vector<int> res;

    for (int i = 0; i < nRows; ++i) {
        int lucky = 0;

        for (int j = 0, s = 0, e = 4;
             j < 6 && j == lucky; ++j, s += 4, e += 4) {

            int sum = 0;

            for (int k = s; k < e; ++k)
                sum += mat(i, index26[k]);

            lucky += (sum == 26);
        }

        if (lucky == 6) res.push_back(i);
    }

    return wrap(res);
}

Ahora, usando los argumentos lowery upperen permuteGeneral, podemos generar fragmentos de permutaciones y probarlos individualmente para mantener la memoria bajo control. A continuación, he elegido probar alrededor de 4.7 millones de permutaciones a la vez. ¡La salida da los índices lexicográficos de las permutaciones de 12! tal que se cumpla la condición Lucky 26.

library(RcppAlgos)
## N.B. 4790016L evenly divides 12!, so there is no need to check
## the upper bound on the last iteration below

system.time(solution <- do.call(c, parallel::mclapply(seq(1L, factorial(12), 4790016L), function(x) {
    perms <- permuteGeneral(12, 12, lower = x, upper = x + 4790015)
    ind <- DavidIndex(perms)
    ind + x
}, mc.cores = 4)))

  user  system elapsed 
13.005   6.258   6.644

## Foregoing the parallel package and simply using lapply,
## we obtain the solution in about 25 seconds:
##   user  system elapsed 
## 18.495   6.221  24.729

Ahora, verificamos el uso permuteSampley el argumento sampleVecque le permite generar permutaciones específicas (por ejemplo, si pasa 1, le dará la primera permutación (es decir 1:12)).

system.time(Lucky26 <- permuteSample(12, 12, sampleVec=solution))
 user  system elapsed 
0.001   0.000   0.001

head(Lucky26)
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
[1,]    1    2    4   12    8   10    6   11    5     3     7     9
[2,]    1    2    6   10    8   12    4    7    3     5    11     9
[3,]    1    2    7   11    6    8    5   10    4     3     9    12
[4,]    1    2    7   12    5   10    4    8    3     6     9    11
[5,]    1    2    8    9    7   11    4    6    3     5    12    10
[6,]    1    2    8   10    6   12    4    5    3     7    11     9

tail(Lucky26)
       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
[955,]   12   11    5    3    7    1    9    8   10     6     2     4
[956,]   12   11    5    4    6    2    9    7   10     8     1     3
[957,]   12   11    6    1    8    3    9    5   10     7     4     2
[958,]   12   11    6    2    7    5    8    3    9    10     4     1
[959,]   12   11    7    3    5    1    9    6   10     8     2     4
[960,]   12   11    9    1    5    3    7    2    8    10     6     4

Finalmente, verificamos nuestra solución con la base R rowSums:

all(rowSums(Lucky26[, c(1, 3, 6, 8]) == 26)
[1] TRUE

all(rowSums(Lucky26[, c(1, 4, 7, 11)]) == 26)
[1] TRUE

all(rowSums(Lucky26[, c(8, 9, 10, 11)]) == 26)
[1] TRUE

all(rowSums(Lucky26[, c(2, 3, 4, 5)]) == 26)
[1] TRUE

all(rowSums(Lucky26[, c(2, 6, 9, 12)]) == 26)
[1] TRUE

all(rowSums(Lucky26[, c(5, 7, 10, 12)]) == 26)
[1] TRUE

* Soy el autor deRcppAlgos


6

Para las permutaciones, es genial. Desafortunadamente, hay 479 millones de posibilidades con 12 campos, lo que significa que ocupa demasiada memoria para la mayoría de las personas:

library(RcppAlgos)
elements <- 12
permuteGeneral(elements, elements)
#> Error: cannot allocate vector of size 21.4 Gb

Hay algunas alternativas

  1. Tome una muestra de las permutaciones. Es decir, solo hacer 1 millón en lugar de 479 millones. Para hacer esto, puedes usar permuteSample(12, 12, n = 1e6). Vea la respuesta de @ JosephWood para un enfoque algo similar, excepto que muestra 479 millones de permutaciones;)

  2. Cree un bucle en para evaluar la permutación en la creación. Esto ahorra memoria porque terminaría creando la función para devolver solo los resultados correctos.

  3. Aborde el problema con un algoritmo diferente. Me enfocaré en esta opción.

Nuevo algoritmo con restricciones

Lucky Star 26 en r

Los segmentos deben ser 26

Sabemos que cada segmento de línea en la estrella de arriba necesita sumar hasta 26. Podemos agregar esa restricción para generar nuestras permutaciones; denos solo combinaciones que sumen 26:

# only certain combinations will add to 26
lucky_combo <- comboGeneral(12, 4, comparisonFun = '==', constraintFun = 'sum', limitConstraints = 26L)

Grupos ABCD y EFGH

En la estrella de arriba, he coloreado tres grupos de manera diferente: ABCD , EFGH e IJLK . Los dos primeros grupos tampoco tienen puntos en común y también están en línea segmentos de interés. Por lo tanto, podemos agregar otra restricción: para las combinaciones que suman 26, debemos asegurarnos de que ABCD y EFGH no tengan superposición de números. A IJLK se le asignarán los 4 números restantes.

library(RcppAlgos)
lucky_combo <- comboGeneral(12, 4, comparisonFun = '==', constraintFun = 'sum', limitConstraints = 26L)
two_combo <- comboGeneral(nrow(lucky_combo), 2)

unique_combos <- !apply(cbind(lucky_combo[two_combo[, 1], ], lucky_combo[two_combo[, 2], ]), 1, anyDuplicated)

grp1 <- lucky_combo[two_combo[unique_combos, 1],]
grp2 <- lucky_combo[two_combo[unique_combos, 2],]
grp3 <- t(apply(cbind(grp1, grp2), 1, function(x) setdiff(1:12, x)))

Permutar a través de los grupos.

Necesitamos encontrar todas las permutaciones de cada grupo. Es decir, solo tenemos combinaciones que suman 26. Por ejemplo, necesitamos tomar 1, 2, 11, 12y hacer 1, 2, 12, 11; 1, 12, 2, 11; ....

#create group perms (i.e., we need all permutations of grp1, grp2, and grp3)
n <- 4
grp_perms <- permuteGeneral(n, n)
n_perm <- nrow(grp_perms)

# We create all of the permutations of grp1. Then we have to repeat grp1 permutations
# for all grp2 permutations and then we need to repeat one more time for grp3 permutations.
stars <- cbind(do.call(rbind, lapply(asplit(grp1, 1), function(x) matrix(x[grp_perms], ncol = n)))[rep(seq_len(sum(unique_combos) * n_perm), each = n_perm^2), ],
           do.call(rbind, lapply(asplit(grp2, 1), function(x) matrix(x[grp_perms], ncol = n)[rep(1:n_perm, n_perm), ]))[rep(seq_len(sum(unique_combos) * n_perm^2), each = n_perm), ],
           do.call(rbind, lapply(asplit(grp3, 1), function(x) matrix(x[grp_perms], ncol = n)[rep(1:n_perm, n_perm^2), ])))

colnames(stars) <- LETTERS[1:12]

Cálculos finales

El último paso es hacer los cálculos. Utilizo lapply()y Reduce()aquí para hacer una programación más funcional; de lo contrario, se escribiría mucho código seis veces. Consulte la solución original para obtener una explicación más detallada del código matemático.

# creating a list will simplify our math as we can use Reduce()
col_ind <- list(c('A', 'B', 'C', 'D'), #these two will always be 26
                c('E', 'F', 'G', 'H'),  #these two will always be 26
                c('I', 'C', 'J', 'H'), 
                c('D', 'J', 'G', 'K'),
                c('K', 'F', 'L', 'A'),
                c('E', 'L', 'B', 'I'))

# Determine which permutations result in a lucky star
L <- lapply(col_ind, function(cols) rowSums(stars[, cols]) == 26)
soln <- Reduce(`&`, L)

# A couple of ways to analyze the result
rbind(stars[which(soln),], stars[which(soln), c(1,8, 9, 10, 11, 6, 7, 2, 3, 4, 5, 12)])
table(Reduce('+', L)) * 2

      2       3       4       6 
2090304  493824   69120     960 

Intercambiando ABCD y EFGH

Al final del código anterior, aproveché que podemos intercambiar ABCDy EFGHobtener las permutaciones restantes. Aquí está el código para confirmar que sí, podemos intercambiar los dos grupos y estar en lo correcto:

# swap grp1 and grp2
stars2 <- stars[, c('E', 'F', 'G', 'H', 'A', 'B', 'C', 'D', 'I', 'J', 'K', 'L')]

# do the calculations again
L2 <- lapply(col_ind, function(cols) rowSums(stars2[, cols]) == 26)
soln2 <- Reduce(`&`, L2)

identical(soln, soln2)
#[1] TRUE

#show that col_ind[1:2] always equal 26:
sapply(L, all)

[1]  TRUE  TRUE FALSE FALSE FALSE FALSE

Actuación

Al final, evaluamos solo 1.3 millones de las 479 permutaciones y solo barajamos 550 MB de RAM. Tarda alrededor de 0.7s en correr

# A tibble: 1 x 13
  expression   min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
  <bch:expr> <bch> <bch:>     <dbl> <bch:byt>    <dbl> <int> <dbl>
1 new_algo   688ms  688ms      1.45     550MB     7.27     1     5

Estadísticas de Lucky Star Solution R


Buena manera de pensar en esto. Gracias.
DesertProject

1
Ya hago +1, desearía poder dar más. Esta fue la idea que tenía originalmente, pero mi código se volvió muy complicado. Cosas hermosas!
Joseph Wood

1
Además, además de las particiones enteras (o composiciones en nuestro caso), me entretuve usando un enfoque gráfico / de red. Definitivamente hay un componente gráfico aquí, pero nuevamente, no pude avanzar con él. Creo que de alguna manera usar composiciones enteras junto con gráficos podría llevar su enfoque al siguiente nivel.
Joseph Wood

3

ingrese la descripción de la imagen aquí

Aquí está la solución para el pequeño:

numbersToDrawnFrom = 1:12
bling=0

while(T==T){

  bling=bling+1
  x=sample(numbersToDrawnFrom,12,replace = F)

  A<-x[1]+x[2]+x[3]+x[4] == 26
  B<-x[4]+x[5]+x[6]+x[7] == 26
  C<-x[7] + x[8] + x[9] + x[1] == 26
  D<-x[10] + x[2] + x[9] + x[11] == 26
  E<-x[10] + x[3] + x[5] + x[12] == 26
  F1<-x[12] + x[6] + x[8] + x[11] == 26

  vectorTrue <- c(A,B,C,D,E,F1)

  if(min(vectorTrue)==1){break}
  if(bling == 1000000){break}

}

x
vectorTrue

"Estoy tratando de mostrarle a mi hijo cómo se puede usar la codificación para resolver un problema planteado por un juego, así como ver cómo R maneja los grandes datos". -> si. Hay al menos 1 solución como se esperaba. Pero, se pueden encontrar más soluciones volviendo a ejecutar los datos.
Jorge López

Solución rápida para resolver esto, ¡muchas gracias!
DesertProject
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.