ใช้ R เพื่อแก้ปัญหาเกม Lucky 26


15

ฉันพยายามแสดงให้ลูกชายเห็นว่าการเขียนโค้ดสามารถใช้ในการแก้ปัญหาที่เกิดจากเกมได้อย่างไรรวมถึงการเห็นว่า R จัดการกับข้อมูลขนาดใหญ่ได้อย่างไร เกมดังกล่าวมีชื่อว่า "Lucky 26" ในเกมนี้หมายเลข (1-12 โดยไม่มีการซ้ำซ้อน) ถูกวางตำแหน่งที่ 12 คะแนนบนดาวของดาวิด (6 จุดยอด, 6 ทางแยก) และ 6 บรรทัดของ 4 หมายเลขทั้งหมดต้องเพิ่มเป็น 26 จากความเป็นไปได้ประมาณ 479 ล้าน (12P12 ) เห็นได้ชัดว่ามี 144 โซลูชั่น ฉันพยายามที่จะรหัสนี้ใน R ดังนี้ แต่หน่วยความจำเป็นปัญหาดูเหมือนว่า ฉันขอขอบคุณคำแนะนำใด ๆ ที่จะตอบคำถามหากสมาชิกมีเวลา ขอบคุณสมาชิกล่วงหน้า

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
ฉันไม่เข้าใจตรรกะ แต่คุณควรปรับวิธีการของคุณ x<- 1:elementsและที่สำคัญกว่าL1 <- y[,1] + y[,3] + y[,6] + y[,8]นั้น สิ่งนี้จะไม่ช่วยแก้ปัญหาความจำของคุณจริงๆดังนั้นคุณสามารถดูrcpp ได้เสมอ
Cole

4
กรุณาอย่าใส่rm(list=ls())ใน MRE ของคุณ หากมีคนคัดลอกวางในเซสชันที่ใช้งานอยู่พวกเขาอาจสูญเสียข้อมูลของตนเอง
dww

ขอโทษที่ rm (รายการ = ls ()) ..
DesertProject

คุณมั่นใจมีเพียง 144 ไหม ฉันยังคงทำงานอยู่และได้ 480 แต่ฉันไม่แน่ใจเกี่ยวกับวิธีการปัจจุบันของฉัน
โคล

1
@ เสาฉันได้รับ 960 โซลูชั่น
โจเซฟวู้ด

คำตอบ:


3

นี่คือวิธีการอื่น มันขึ้นอยู่กับการโพสต์บล็อก MathWorksโดยCleve Molerผู้เขียน MATLAB แรก

ในการโพสต์บล็อกการบันทึกหน่วยความจำผู้เขียนอนุญาตเพียง 10 องค์ประกอบทำให้องค์ประกอบแรกเป็นองค์ประกอบเอเพ็กซ์และที่ 7 เป็นองค์ประกอบพื้นฐาน ดังนั้นจึง10! == 3628800ต้องทำการทดสอบการเปลี่ยนลำดับเท่านั้น
ในรหัสด้านล่าง

  1. สร้างพีชคณิตขององค์ประกอบที่จะ1 10มีทั้งหมด10! == 3628800ของพวกเขา
  2. เลือก11เป็นองค์ประกอบปลายและคงไว้ ไม่สำคัญว่าจะเริ่มต้นที่ใดการมอบหมายองค์ประกอบอื่น ๆ จะอยู่ในตำแหน่งสัมพัทธ์ที่ถูกต้อง
  3. จากนั้นกำหนดองค์ประกอบที่ 12 ให้กับตำแหน่งที่ 2 ตำแหน่งที่ 3 ฯลฯ ในการforวนซ้ำ

สิ่งนี้ควรสร้างวิธีแก้ปัญหาส่วนใหญ่ให้หรือหมุนเวียนและสะท้อนกลับ แต่ไม่รับประกันว่าโซลูชันจะไม่เหมือนใคร มันยังเร็วพอสมควร

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

จริงๆแล้วมีวิธีแก้ไข 960 ข้อ ด้านล่างนี้เราใช้ประโยชน์จากRcpp, RcppAlgos* , และparallelแพ็กเกจเพื่อรับวิธีแก้ปัญหา6 secondsโดยใช้ 4 คอร์ แม้ว่าคุณจะเลือกใช้วิธีการเธรดเดียวกับ base R's lapplyโซลูชันจะถูกส่งคืนภายในประมาณ 25 วินาที

อันดับแรกเราเขียนอัลกอริทึมอย่างง่ายในการC++ตรวจสอบการเปลี่ยนแปลงโดยเฉพาะ คุณจะทราบว่าเราใช้หนึ่งอาเรย์เพื่อจัดเก็บทั้งหกบรรทัด เพื่อประสิทธิภาพในการทำงานเนื่องจากเราใช้หน่วยความจำแคชอย่างมีประสิทธิภาพมากกว่าการใช้อาร์เรย์ 6 ตัว คุณจะต้องจำไว้ว่าC++ใช้ดัชนีตามศูนย์

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

ตอนนี้การใช้lowerและupperข้อโต้แย้งในpermuteGeneralเราสามารถสร้างชิ้นพีชคณิตของพีชคณิตและทดสอบเป็นรายบุคคลเพื่อให้หน่วยความจำในการตรวจสอบ ด้านล่างนี้ฉันเลือกทดสอบครั้งละประมาณ 4.7 ล้านใบ การส่งออกจะให้ดัชนีศัพท์การเรียงลำดับของ 12! ดังนั้นเงื่อนไขลัคกี้ 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

ตอนนี้เราตรวจสอบการใช้permuteSampleและอาร์กิวเมนต์sampleVecที่อนุญาตให้คุณสร้างพีชคณิตที่เฉพาะเจาะจง (เช่นถ้าคุณผ่าน 1 มันจะทำให้คุณมีการเปลี่ยนแปลงครั้งแรก (เช่น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

สุดท้ายเราจะตรวจสอบการแก้ปัญหาของเราด้วยฐาน 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

*ฉันเป็นผู้เขียนRcppAlgos


6

สำหรับวิธีเรียงสับเปลี่ยนนั้นยอดเยี่ยม น่าเสียดายที่มีความเป็นไปได้479 ล้านรายการกับ 12 สาขาซึ่งหมายความว่าใช้หน่วยความจำมากเกินไปสำหรับคนส่วนใหญ่:

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

มีทางเลือกบางอย่าง

  1. ตัวอย่างการเรียงสับเปลี่ยน ความหมายทำเพียง 1 ล้านแทน 479 ล้าน permuteSample(12, 12, n = 1e6)การทำเช่นนี้คุณสามารถใช้ See @ JosephWood คำตอบของวิธีการที่ค่อนข้างคล้ายกันยกเว้นเขาตัวอย่างออกไป 479 ล้าน permutations;)

  2. สร้างลูปในเพื่อประเมินการเปลี่ยนแปลงในการสร้าง วิธีนี้ช่วยประหยัดหน่วยความจำเพราะคุณจะต้องสร้างฟังก์ชั่นเพื่อคืนผลลัพธ์ที่ถูกต้องเท่านั้น

  3. เข้าหาปัญหาด้วยอัลกอริทึมที่แตกต่างกัน ฉันจะมุ่งเน้นไปที่ตัวเลือกนี้

อัลกอริทึมใหม่พร้อมข้อ จำกัด

ลัคกี้สตาร์ 26 นิ้ว

ส่วนควรเป็น 26

เรารู้ว่าแต่ละส่วนของดาวดังกล่าวข้างต้นจำเป็นต้องเพิ่มมากถึง 26 เราสามารถเพิ่มข้อ จำกัด นั้นในการสร้างการเรียงสับเปลี่ยนของเรา - ให้ชุดค่าผสมที่เพิ่มขึ้นถึง 26 เท่านั้น:

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

กลุ่มABCDและEFGH

ในดาวดังกล่าวข้างต้นผมได้สีสามกลุ่มที่แตกต่างกัน: ABCD , EFGHและIJLK สองกลุ่มแรกนั้นไม่มีจุดร่วมและอยู่ในส่วนของเส้นตรงที่น่าสนใจ ดังนั้นเราสามารถเพิ่มข้อ จำกัด อื่น ๆ : สำหรับชุดค่าผสมที่เพิ่มขึ้นถึง 26 เราจำเป็นต้องตรวจสอบให้แน่ใจว่าABCDและEFGHไม่มีการทับซ้อนจำนวน IJLKจะถูกกำหนดหมายเลข 4 ที่เหลือ

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

ดัดแปรผ่านกลุ่มต่างๆ

เราจำเป็นต้องค้นหาวิธีเรียงสับเปลี่ยนทั้งหมดของแต่ละกลุ่ม นั่นคือเรามีเพียงการรวมกันที่เพิ่มขึ้นถึง 26 ตัวอย่างเช่นเราต้องใช้เวลาและทำให้1, 2, 11, 121, 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]

การคำนวณขั้นสุดท้าย

ขั้นตอนสุดท้ายคือการทำคณิตศาสตร์ ฉันใช้lapply()และReduce()ที่นี่เพื่อทำงานเขียนโปรแกรมที่ทำงานได้มากกว่า - มิฉะนั้นรหัสจำนวนมากจะถูกพิมพ์หกครั้ง ดูโซลูชันต้นฉบับสำหรับคำอธิบายที่ละเอียดยิ่งขึ้นของรหัสคณิตศาสตร์

# 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 

การแลกเปลี่ยนABCDและEFGH

ในตอนท้ายของรหัสข้างต้นฉันใช้ประโยชน์จากการที่เราสามารถสลับABCDและEFGHรับการเปลี่ยนลำดับที่เหลือ นี่คือรหัสเพื่อยืนยันว่าใช่เราสามารถสลับทั้งสองกลุ่มและถูกต้อง:

# 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

ประสิทธิภาพ

ในที่สุดเราประเมินเพียง 1.3 ล้านจาก 479 พีชคณิตและเพียงสับผ่าน RAM ขนาด 550 MB เท่านั้น ใช้เวลาประมาณ 0.7 วินาทีในการทำงาน

# 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

ลัคกี้สตาร์สถิติ r


เป็นวิธีที่ดีที่จะคิดเกี่ยวกับเรื่องนี้ ขอบคุณ.
DesertProject

1
ฉัน +1 แล้วฉันหวังว่าจะให้มากขึ้น นี่เป็นความคิดที่ฉันเคยมีมา แต่รหัสของฉันยุ่งมาก สิ่งที่สวยงาม!
โจเซฟวู้ด

1
นอกจากนี้ในพาร์ทิชันจำนวนเต็ม (หรือองค์ประกอบในกรณีของเรา) ฉันเพลิดเพลินกับการใช้กราฟ / เครือข่าย มีองค์ประกอบกราฟอยู่ที่นี่ แต่อีกครั้งฉันไม่สามารถมุ่งหน้าไปได้ ฉันคิดว่าการใช้องค์ประกอบจำนวนเต็มพร้อมกับกราฟอาจนำคุณไปสู่ระดับถัดไป
โจเซฟวู้ด

3

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือวิธีแก้ปัญหาสำหรับเด็กน้อย:

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

"ฉันพยายามแสดงให้ลูกชายเห็นว่าการเขียนโค้ดสามารถใช้เพื่อแก้ปัญหาที่เกิดจากเกมได้อย่างไรรวมถึงการเห็นว่า R จัดการกับข้อมูลขนาดใหญ่ได้อย่างไร" -> ใช่ มีวิธีแก้ปัญหาอย่างน้อย 1 อย่างที่คาดไว้ แต่สามารถหาวิธีแก้ไขเพิ่มเติมได้จากการเรียกใช้ข้อมูลอีกครั้ง
Jorge Lopez

ทางออกที่รวดเร็วในการแก้ปัญหานี้ - ขอบคุณมาก!
DesertProject
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.