Sử dụng R để giải quyết trò chơi Lucky 26


15

Tôi đang cố gắng chỉ cho con trai tôi cách mã hóa có thể được sử dụng để giải quyết vấn đề do trò chơi gây ra cũng như xem cách R xử lý dữ liệu lớn. Trò chơi trong câu hỏi được gọi là "Lucky 26". Trong trò chơi này, các số (1-12 không có trùng lặp) được định vị trên 12 điểm trên một ngôi sao của david (6 đỉnh, 6 giao điểm) và 6 dòng gồm 4 số phải cộng thêm 26. Trong số khoảng 479 triệu khả năng (12P12 ) rõ ràng có 144 giải pháp. Tôi đã cố gắng viết mã này trong R như sau nhưng có vẻ như bộ nhớ là một vấn đề. Tôi sẽ đánh giá rất cao bất kỳ lời khuyên nào để đưa ra câu trả lời nếu các thành viên có thời gian. Cảm ơn các thành viên trước.

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
Tôi không hiểu logic nhưng bạn nên véc tơ cách tiếp cận của bạn. x<- 1:elementsvà quan trọng hơn L1 <- y[,1] + y[,3] + y[,6] + y[,8]. Điều này thực sự không giúp ích gì cho vấn đề bộ nhớ của bạn, do đó bạn luôn có thể xem xét RCpp
Cole

4
xin vui lòng không đưa rm(list=ls())vào MRE của bạn. Nếu ai đó sao chép vào một phiên hoạt động, họ có thể mất dữ liệu của chính họ.
DWW

Lời xin lỗi trên rm (list = ls ()) ..
Dự án sa mạc

Bạn có tự tin chỉ có 144? Tôi vẫn đang làm việc với nó và tôi nhận được 480 nhưng tôi không chắc lắm về cách tiếp cận hiện tại của mình.
Cole

1
@Cole, tôi đang nhận được 960 giải pháp.
Joseph Wood

Câu trả lời:


3

Đây là một cách tiếp cận khác. Nó dựa trên một bài đăng trên blog MathWorks của Cleve Moler , tác giả của MATLAB đầu tiên.

Trong bài đăng trên blog, để lưu bộ nhớ, tác giả chỉ cho phép 10 phần tử, giữ phần tử đầu tiên là phần tử đỉnh và phần 7 làm phần tử cơ sở. Do đó, chỉ có 10! == 3628800hoán vị cần phải được kiểm tra.
Trong mã dưới đây,

  1. Tạo hoán vị của các phần tử 1để 10. Có tổng cộng trong 10! == 3628800số họ.
  2. Chọn 11làm phần tử đỉnh và giữ nó cố định. Nó thực sự không quan trọng khi các bài tập bắt đầu, các yếu tố khác sẽ ở đúng vị trí tương đối .
  3. Sau đó gán phần tử thứ 12 cho vị trí thứ 2, vị trí thứ 3, v.v., trong một forvòng lặp.

Điều này sẽ tạo ra hầu hết các giải pháp, đưa ra hoặc thực hiện các phép quay và phản xạ. Nhưng nó không đảm bảo rằng các giải pháp là duy nhất. Nó cũng nhanh chóng hợp lý.

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

Thực tế có 960 giải pháp. Dưới đây chúng tôi sử dụng Rcpp, RcppAlgos*parallelgói để có được giải pháp chỉ 6 secondsbằng cách sử dụng 4 lõi. Ngay cả khi bạn chọn sử dụng một cách tiếp cận luồng đơn với cơ sở R lapply, giải pháp được trả về sau khoảng 25 giây.

Đầu tiên, chúng tôi viết một thuật toán đơn giản C++để kiểm tra một hoán vị cụ thể. Bạn sẽ lưu ý rằng chúng tôi sử dụng một mảng để lưu trữ tất cả sáu dòng. Điều này là để thực hiện vì chúng tôi sử dụng bộ nhớ cache hiệu quả hơn so với sử dụng 6 mảng riêng lẻ. Bạn cũng sẽ phải ghi nhớ rằng C++sử dụng lập chỉ mục dựa trên số không.

#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);
}

Bây giờ, bằng cách sử dụng lowervà các upperđối số trong permuteGeneral, chúng ta có thể tạo ra các khối hoán vị và kiểm tra từng phần này để kiểm tra bộ nhớ. Dưới đây, tôi đã chọn kiểm tra khoảng 4,7 triệu hoán vị tại một thời điểm. Đầu ra cho các chỉ số từ điển của hoán vị của 12! như vậy điều kiện Lucky 26 được thỏa mãn.

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

Bây giờ, chúng tôi xác minh bằng cách sử dụng permuteSamplevà đối số sampleVeccho phép bạn tạo các hoán vị cụ thể (ví dụ: nếu bạn vượt qua 1, nó sẽ cung cấp cho bạn hoán vị đầu tiên (nghĩa là 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

Cuối cùng, chúng tôi xác minh giải pháp của chúng tôi với cơ sở 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

* Tôi là tác giả củaRcppAlgos


6

Đối với hoán vị, là tuyệt vời. Thật không may, có 479 triệu khả năng với 12 trường có nghĩa là chiếm quá nhiều bộ nhớ cho hầu hết mọi người:

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

Có một số lựa chọn thay thế.

  1. Lấy một mẫu của hoán vị. Ý nghĩa, chỉ làm 1 triệu thay vì 479 triệu. Để làm điều này, bạn có thể sử dụng permuteSample(12, 12, n = 1e6). Xem câu trả lời của @ JosephWood cho một cách tiếp cận tương tự ngoại trừ anh ta lấy mẫu tới 479 triệu hoán vị;)

  2. Xây dựng một vòng lặp trong để đánh giá hoán vị khi tạo. Điều này giúp tiết kiệm bộ nhớ vì cuối cùng bạn sẽ xây dựng hàm để chỉ trả về kết quả chính xác.

  3. Tiếp cận vấn đề với một thuật toán khác nhau. Tôi sẽ tập trung vào tùy chọn này.

Thuật toán mới w / ràng buộc

ngôi sao may mắn 26 in r

Phân khúc nên là 26

Chúng tôi biết rằng mỗi phân đoạn dòng trong ngôi sao ở trên cần thêm tối đa 26. Chúng tôi có thể thêm ràng buộc đó để tạo hoán vị - chỉ cung cấp cho chúng tôi các kết hợp có tối đa 26:

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

Các nhóm ABCDEFGH

Trong ngôi sao trên, tôi đã tô màu ba nhóm khác nhau: ABCD , EFGHIJLK . Hai nhóm đầu tiên cũng không có điểm chung và cũng nằm trên các phân đoạn quan tâm. Do đó, chúng ta có thể thêm một ràng buộc khác: đối với các kết hợp cộng tới 26, chúng ta cần đảm bảo ABCDEFGH không có số trùng nhau. IJLK sẽ được chỉ định 4 số còn lại.

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

Cho phép qua các nhóm

Chúng ta cần tìm tất cả các hoán vị của mỗi nhóm. Đó là, chúng tôi chỉ có các kết hợp cộng tối đa 26. Ví dụ: chúng tôi cần thực hiện 1, 2, 11, 12và thực hiện 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]

Tính toán cuối cùng

Bước cuối cùng là làm toán. Tôi sử dụng lapply()Reduce()ở đây để thực hiện lập trình chức năng nhiều hơn - nếu không, rất nhiều mã sẽ được gõ sáu lần. Xem giải pháp ban đầu để được giải thích kỹ hơn về mã toán học.

# 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 

Hoán đổi ABCDEFGH

Vào cuối đoạn mã trên, tôi đã lợi dụng rằng chúng ta có thể trao đổi ABCDEFGHđể có được các hoán vị còn lại. Đây là mã để xác nhận rằng có, chúng ta có thể trao đổi hai nhóm và chính xác:

# 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

Hiệu suất

Cuối cùng, chúng tôi chỉ đánh giá 1,3 triệu trong số 479 hoán vị và chỉ được xáo trộn qua 550 MB RAM. Phải mất khoảng 0,7 giây để chạy

# 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

giải pháp sao may mắn r thống kê


Cách tốt đẹp để suy nghĩ về điều này. Cảm ơn bạn.
Dự án sa mạc

1
Tôi đã +1, tôi ước tôi có thể cung cấp thêm. Đây là ý tưởng tôi có ban đầu nhưng mã của tôi rất lộn xộn. Đồ đẹp quá!
Joseph Wood

1
Ngoài ra, ngoài các phân vùng số nguyên (hoặc các tác phẩm trong trường hợp của chúng tôi), tôi đã giải trí bằng cách sử dụng phương pháp đồ thị / mạng. Chắc chắn có một thành phần biểu đồ ở đây, nhưng một lần nữa, tôi không thể thực hiện bất kỳ cách nào với nó. Tôi nghĩ bằng cách nào đó sử dụng các thành phần nguyên cùng với biểu đồ có thể đưa cách tiếp cận của bạn lên cấp độ tiếp theo.
Joseph Wood

3

nhập mô tả hình ảnh ở đây

Đây là giải pháp cho các bạn nhỏ:

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

"Tôi đang cố gắng chỉ cho con trai tôi cách mã hóa có thể được sử dụng để giải quyết vấn đề do trò chơi gây ra cũng như xem cách R xử lý dữ liệu lớn." -> có. có ít nhất 1 giải pháp như mong đợi. Nhưng, nhiều giải pháp có thể được tìm thấy bằng cách chạy lại dữ liệu.
Jorge Lopez

Giải pháp nhanh chóng để giải quyết điều này - cảm ơn rất nhiều!
Dự án sa mạc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.