Simulating Snakes and Ladders in R - r

I am a beginner in R and I have to simulate a snakes and ladders game in R for an assignment. The board has 100 squares. The only winning square is 100, for example if you’re on square 98 and roll a 6 you would go forward 2 spaces to 100 and then bounce back 4 spaces to 96. My difficulty is insterting the snakes/ladders transitions in the complete transition matrix, and coding the winning condition. Here is my code so far:
snakesNladders <-
function()
{
transitions <- rbind(
c(40, 3),
c(4, 25),
c(27, 5),
c(13, 46),
c(43, 18),
c(54, 31),
c(33, 49),
c(99, 41),
c(42, 63),
c(66, 45),
c(50, 69),
c(89, 53),
c(76, 58),
c(62, 81),
c(74, 92))
transmat <- 1:100
names(transmat) <- as.character(1:100)
transmat[transitions[,1]] <- transitions[,2]
firstpos <- 0
curpos_player1 <- NULL
curpos_player2 <- NULL
while(curpos_player1 & curpos_player2 < 100) {
curpos_player1 <- firstpos + curpos_player1 + sample(1:6, 1, replace=TRUE)
curpos_player2 <- firstpos + curpos_player2 + sample(1:6, 1, replace = TRUE)
curpos_player1 <- transmat[curpos_player1]
curpos_player2 <- transmat[curpos_player2]
if(curpos_player1 | curpos_player2 == 100){
print(win)
}else if(curpos_player1 > 100){
return()
}else if(curpos_player2 > 100){
return()
}
}
}
}
}
Not sure what I should put in the return brackets to simulate the winning condition. Also if the rest of the code seems ok. I would really appreciate any help.

Here's how I might approach it. Maybe you can get some ideas by walking through the code.
library(data.table)
set.seed(1633654196) # for reproducibility
# for the transitions, position 1 is the start position, position 2 is the
# square at the bottom left of the board, position 101 is the winning position
# get a vector of snake/ladder transitions (where a player moves after
# landing on each square)
trans <- 1:101
trans[c(40, 4, 27, 13, 43, 54, 33, 99, 42, 66, 50, 89, 76, 62, 74) + 1] <-
c( 3, 25, 5, 46, 18, 31, 49, 41, 63, 45, 69, 53, 58, 81, 92) + 1
# get a matrix of all possible starting/ending positions (based on die
# roll 1 through 6)
m <- c(sequence(rep(6L, 100), 2:101), rep(101L, 6))
# handle overshooting the winning square
blnBounce <- m > 101
m[blnBounce] <- 202 - m[blnBounce]
# add in the snake/ladder transitions
# (faster to index moves as a list of vectors than as a matrix)
to <- asplit(m <- matrix(trans[m], 6), 2)
simgame <- function(nplayers = 1, to, blnPrint = TRUE, turns = 50L, start = rep(1L, nplayers)) {
# turn 1 is the starting position, and won't be retained in the output, so
# add 1 to turns
turns <- as.integer(turns) + 1L
# sample the rolls for all players up front
rolls <- matrix(sample(6, nplayers*turns, TRUE), ncol = 2)
# initialize the output matrix
out <- matrix(0, turns, nplayers)
out[1,] <- start
for (j in 1:nplayers) {
for (i in 2:turns) {
out[i, j] <- to[[out[i - 1L]]][rolls[i, j]]
if (out[i, j] == 101L) {
turns <- i
break
}
}
}
# subtract 1 so "1" corresponds to square 1 and "100" corresponds to the
# winning square
out <- out[2:turns,,drop = FALSE] - 1L
turns <- turns - 1L
winner <- match(100L, out[turns,], 0L)
if (winner) {
if (blnPrint) print(paste("Player", winner, "wins"))
out
} else {
# no winner yet, recursively call simgame
rbind(out, Recall(nplayers, to, blnPrint, turns, out[turns,]))
}
}
# simulate a 2-player game
(game <- simgame(2, to))
#> [1] "Player 2 wins"
#> [,1] [,2]
#> [1,] 25 6
#> [2,] 31 30
#> [3,] 32 32
#> [4,] 34 34
#> [5,] 3 36
#> [6,] 25 6
#> [7,] 5 28
#> [8,] 11 10
#> [9,] 46 14
#> [10,] 52 51
#> [11,] 31 31
#> [12,] 35 37
#> [13,] 39 38
#> [14,] 3 41
#> [15,] 8 7
#> [16,] 12 12
#> [17,] 46 17
#> [18,] 69 48
#> [19,] 75 71
#> [20,] 78 77
#> [21,] 84 83
#> [22,] 87 53
#> [23,] 93 92
#> [24,] 96 94
#> [25,] 41 100
# simulate the number of rounds for 15k games
system.time(turns <- replicate(15e3, nrow(simgame(2, to, FALSE))))
#> user system elapsed
#> 1.18 0.01 1.06
hist(turns)
# Probability of single player finishing by turn
dt <- data.table(from = c(col(m)), to = c(m))[, .(prob = .N/6), from:to]
# full transition matrix
m1 <- matrix(0L, 101, 101)
m1[as.matrix(dt[, 1:2])] <- dt$prob
# calculate the probabilities for 300 turns
mm <- m1
prob1 <- numeric(300)
for (i in 1:300) {
mm <- mm %*% m1
prob1[i] <- mm[1, 101]
}
plot(prob1, xlab = "Roll number", main = "Probability of winning by turn")
# for a 3-player game:
prob3 <- 1 - (1 - prob1)^3

Related

Reordering elements of a list

I'm trying to create a function in R that takes a list as an input and reorder its elements based on the mean of each element (for example, the first element will be the one with the largest mean value, etc.) I'm trying to find the easiest way to do it without having to use any loops. I tried to sort the mean like below but couldn't figure out how to have the elements associated with the mean to move along. Any suggestions or advice would be appreciated.
function1 <- function(x){
return(sort(mean(x), decreasing = T))
}
function2 <- function(x) {
return(lapply(function1, x))
}
testlist <- list(c(10, 5, 1), c(3, 2), c(77, 90, 1), c(23, 34), c(2, 35, 22))
function2(testlist)
Here's one way that is really just testlist[order(sapply(testiest, mean))], but put inside a function. The idea is that sapply() returns a vector that gives the mean of each list element, order() gives the element numbers in order of the mean. Then, you are giving that ordered element numbers based on the mean to index the values of the list.
testlist <- list(c(10, 5, 1), c(3, 2), c(77, 90, 1), c(23, 34), c(2, 35, 22))
function1 <- function(x){
x[order(sapply(x, mean))]
}
function1(testlist)
#> [[1]]
#> [1] 3 2
#>
#> [[2]]
#> [1] 10 5 1
#>
#> [[3]]
#> [1] 2 35 22
#>
#> [[4]]
#> [1] 23 34
#>
#> [[5]]
#> [1] 77 90 1
Created on 2023-02-18 by the reprex package (v2.0.1)
You can use sapply function and then order the elements in decreasing ordedr with the first element with the highest mean :
function2 <- function(z) {
order_ind <- order(sapply(z, mean), decreasing = TRUE)
z[order_ind]
}
sapply() computes the mean value of each element and
z[order_ind] outputs the ordered list using the ordered indexes
function2(testlist)
the results:
[[1]]
[1] 77 90 1
[[2]]
[1] 23 34
[[3]]
[1] 2 35 22
[[4]]
[1] 10 5 1
[[5]]
[1] 3 2
Using fmean from collapse
library(collapse)
testlist[order(fmean(testlist))]
-output
[[1]]
[1] 3 2
[[2]]
[1] 10 5 1
[[3]]
[1] 2 35 22
[[4]]
[1] 23 34
[[5]]
[1] 77 90 1
data
testlist <- list(c(10, 5, 1), c(3, 2), c(77, 90, 1), c(23, 34), c(2, 35,
22))

Group a vector of numbers by range

I have a sorted vector:
c(1, 10, 31, 80, 100, 120, 160)
I would like to split its elements into several groups by range, which I set to 31 in this example.
The result is like this:
[[1]] 1, 10, 31
[[2]] 80, 100
[[3]] 100, 120
[[4]] 160
The ranges in each group is less than 31. I have tried a loop, but I do not like it. Also, I tried the outer function, where I calculated all pairwise differences:
res <- outer(vec, vec, "-")
Then filter each column by the condition > 0 and < 31.
apply(res, 2, function(x) x[x > 0 & x < 31])
The result is not good enough though...
I think this will serve your purpose finally
First list will extract items fulfilling your condition of range, whereas final_list will remove items that are actually contained in some other items.
vec <- c(1, 10, 31, 80, 100, 120, 160)
first_list <- unique(apply(outer(vec, vec, "-"), 1, function(x){vec[(x < 31 & x >= 0)] }))
final_list <- first_list[!sapply(seq_along(first_list), function(i) max(sapply(first_list[-i],function(L) all(first_list[[i]] %in% L))))]
> final_list
[[1]]
[1] 1 10 31
[[2]]
[1] 80 100
[[3]]
[1] 100 120
[[4]]
[1] 160
Here's a neat solution
x <- c(1, 10, 31, 80, 100, 120, 160)
y <- findInterval(x+30, x)
lapply(seq_along(x)[!duplicated(y)], function(z) x[z:y[z]])
#> [[1]]
#> [1] 1 10 31
#>
#> [[2]]
#> [1] 80 100
#>
#> [[3]]
#> [1] 100 120
#>
#> [[4]]
#> [1] 160

Unlist str_locate_all into separate start and end lists

I use str_locate_all to get the start and end positions of a list of patterns in my string. It returns a list with the start and stop position for each match. How can I get the start and stop positions of all matches into separate lists?
library(stringr)
patterns <- c("ABS", "BSDF", "ERIDF", "RTZOP")
string <- "ABSBSDFERIDFRTZOPABSBSDFRTZOPABSBSDFERIDFRTZOP"
matches <- str_locate_all(string, patterns)
Result:
[[1]]
start end
[1,] 1 3
[2,] 18 20
[3,] 30 32
[[2]]
start end
[1,] 4 7
[2,] 21 24
[3,] 33 36
[[3]]
start end
[1,] 8 12
[2,] 37 41
[[4]]
start end
[1,] 13 17
[2,] 25 29
[3,] 42 46
What I would like:
start <- c(1, 18, 30, 4, 21, 33, 8, 37, 13, 25, 42)
end <- c(3, 20, 32, 7, 24, 36, 12, 41, 17, 29, 46)
Use do.call with rbind to stack the lists together, then take out the desired columns.
> library(stringr)
>
> patterns <- c("ABS", "BSDF", "ERIDF", "RTZOP")
> string <- "ABSBSDFERIDFRTZOPABSBSDFRTZOPABSBSDFERIDFRTZOP"
>
> matches <- str_locate_all(string, patterns)
>
> all <- do.call(rbind, matches)
> start <- all[, 1]
> stop <- all[, 2]
> start
[1] 1 18 30 4 21 33 8 37 13 25 42
> stop
[1] 3 20 32 7 24 36 12 41 17 29 46

R : Swapping 2 values in a nested list

[[1]]
[[1]][[1]]
[1] 1 46 107 69 1
[[1]][[2]]
[1] 1 146 145 71 92 1
####################
[[2]]
[[2]][[1]]
[1] 1 46 18 92 1
[[2]][[2]]
[1] 1 127 145 53 168 1
Assume I have 2 nested list, as shown above, I'm looking for function where I can update (say 46) in both the list with some other number in list (say 92) and update 92's with 46 without altering the structure of the list
Expected output will be something like this
[[1]]
[[1]][[1]]
[1] 1 92 107 69 1
[[1]][[2]]
[1] 1 146 145 71 46 1
####
[[2]]
[[2]][[1]]
[1] 1 92 18 46 1
[[2]][[2]]
[1] 1 127 145 53 168 1
Rlist library has functions like list.find/list.findi which works only for named nested list. Mine is not a named list
This is another way to achieve that. First you simply convert your list to vector (unlist(l)). Do your necessary swaps and convert it back to your list (relist(x, skeleton = l)).
x <- unlist(l)
a <- which(x==46)
b <- which(x==92)
x[a] <- 92
x[b] <- 46
relist(x, skeleton = l)
Benchmarking
library(microbenchmark)
l <- list(list(c(1, 46, 107, 69, 1), c(1, 146, 145, 71, 92, 1)), list(
c(1, 46, 18, 92, 1), c(1, 127, 145, 53, 168, 1)))
f_m0h3n <- function(l){x <- unlist(l);a <- which(x==46);b <- which(x==92);x[a] <- 92;x[b] <- 46;relist(x, l);}
f_jakub <- function(li) rapply(li, function(x) ifelse(x == 46, 92,ifelse(x==92, 46, x)), how = "list")
all.equal(f_m0h3n(l), f_jakub(l))
# [1] TRUE
microbenchmark(f_m0h3n(l), f_jakub(l))
# Unit: microseconds
# expr min lq mean median uq max neval
# f_m0h3n(l) 100.942 103.509 109.7108 107.3580 111.6355 204.879 100
# f_jakub(l) 126.178 131.738 142.8850 137.9405 143.7150 357.148 100
Larger scale
library(microbenchmark)
set.seed(123)
l <- list(list(sample(1000), sample(2000)),list(sample(1000), sample(2000)))
all.equal(f_m0h3n(l), f_jakub(l))
# [1] TRUE
microbenchmark(f_m0h3n(l), f_jakub(l))
# Unit: microseconds
# expr min lq mean median uq max neval
# f_m0h3n(l) 588.973 615.0645 896.9371 651.2065 692.268 2827.242 100
# f_jakub(l) 1022.683 1053.9070 1914.0769 1253.0115 2848.842 3287.898 100
It is evident that f_m0h3n works better than f_jakub. The difference is even more significant for larger scales (the time is reduced almost by half).
Could it be a simple rapply? See this example, where 46 is replaced by 92 (and the other way round, as added by #akrun):
li = list(list(c(1, 46, 107, 69, 1),
c(1, 146, 145, 71, 92, 1)))
# [[1]]
# [[1]][[1]]
# [1] 1 46 107 69 1
#
# [[1]][[2]]
# [1] 1 146 145 71 92 1
rapply(li, function(x) ifelse(x == 46, 92,ifelse(x==92, 46, x)), how = "list")
# [[1]]
# [[1]][[1]]
# [1] 1 92 107 69 1
#
# [[1]][[2]]
# [1] 1 146 145 71 46 1
The how = "list" makes sure you get the original structure back.

weird output when partitioning a matrix into lists in R

I've been trying to write a function that takes in matrix (x) and a vector (cut.vec) and outputs a list, where each element of the list is a combination of some of the columns in the input matrix. Each element in the input vector is an index where want to partition the matrix. Then I want to save every partition to an element in a list and return that list.
Here's what I've got so far:
This is the actual function that I'm running:
make.cut <- function(x, cut.vec){
ran.once <- 0 #This checks for first run
out <- list() #This creates the output list
temp.matrix <- matrix() #For holding data
for(i in 1:length(cut.vec)){
for(n in 1:cut.vec[i]){
if(cut.vec[i]<n){
#Do nothing
}else{
hold <- x[,n]
if(ran.once != 0){
temp.matrix <- cbind(temp.matrix, hold)
}else{
temp.matrix <- hold
ran.once <- 1
}
}
}
out[[i]] <- temp.matrix
temp.matrix <- matrix()
}
return(out)
}
When I run this I get a list out, but only the first element is correct. Each element except the first one only contains one column of the input matrix.
**Example Input**
x<-matrix(c(341, 435, 834, 412, 245, 532.2, 683.4, 204.2, 562.7, 721.5, 149, 356, 112, 253, 211, 53, 92, 61, 84, 69), nrow=4)
x= 341 435 834 412 245
532.2 683.4 204.2 562.7 721.5
149 356 112 253 211
53 92 61 84 69
cut.vec = c(2, 3, 5)
out <- make.cut(x, cut.vec):
a <- out[[1]]
b <- out[[2]]
c <- out[[3]]
**Intended Output**
a= 341 435
532.2 683.4
149 356
53 92
b= 834
204.2
112
61
c= 412 245
562.7 721.5
253 211
84 69
**Actual Output**
a= 341 435
532.2 683.4
149 356
53 92
b= 435
683.4
356
92
c= 834
204.2
112
61
I can do this from the console manually, one element at a time and it works, but every time I try and do it with the make.cut function it breaks.
This is how I did it by hand in terminal:
cut.vec<-c(3, 5)
a<-x[,1]
b<-x[,2]
c<-x[,3]
temp <- cbind(a,b,c)
out[[1]] <- temp
cut.vec[2] is equal to 5
a<-x[,4]
b<-x[,5]
temp <- cbind(a,b)
out[[2]] <- temp
However, when I try to apply the same methodology in a function it breaks.
You can use the following approach to "cut" a matrix along a vector:
Example data:
mat <- matrix(1:16, nrow = 2)
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
# [1,] 1 3 5 7 9 11 13 15
# [2,] 2 4 6 8 10 12 14 16
cutvec <- c(2,5)
First, cut the column numbers of mat along cutvec:
cuts <- cut(seq(ncol(mat)), c(0, cutvec - 1))
Then you can create a list with subsets with tapply:
tapply(seq(ncol(mat)), cuts, function(x) mat[, x, drop = FALSE])
# $`(0,1]`
# [,1]
# [1,] 1
# [2,] 2
#
# $`(1,4]`
# [,1] [,2] [,3]
# [1,] 3 5 7
# [2,] 4 6 8

Resources