'Randomize items in rmarkdown transparently

I have the following rmarkdown document:

---
title: "Untitled"
output: pdf_document
date: '2022-04-28'
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

1. This is the first outer item.
    (a) A
    (b) B
2. This is the second outer item.
    (a) C
    (b) D
3. This is the third outer item.
    (a) E
    (b) F

I would like to randomly shuffle this list simultaneously in two ways: Inside the inner lists and outside those. Say, I want to get something like the following:

1. This is the second outer item. 
    (a) D 
    (b) A 
2. This is the first outer item. 
    (a) B 
    (b) A 
3. This is the third outer item. 
    (a) F 
    (b) E 

I know that I could accomplish that by creating the rmarkdown code by programming with strings. So, my question is: Can one achieve the goal by writing the text of the document without having to resort to programming with strings? The reason is that I would like to have a clear picture of the document as I write it, without having the text of the document buried in code. (I am aware that some programming is unavoidable though.)



Solution 1:[1]

I'd do it as follows: Write your text just as you did above, and have a function convert that into a structure like a list of lists. Then randomize the list of lists, and convert back to text. Don't forget to save the answer key! Here's an example for the first conversion:

questions <- "
1. This is the first outer item.
    (a) A
    (b) B
2. This is the second outer item.
    (a) C
    (b) D
3. This is the third outer item.
    (a) E
    (b) F
"

toListOfLists <- function(text) {
  text <- unlist(strsplit(text, "\n"))
  # These regular expressions match the first line of a question
  # or an answer.  \\1 will be the text.
  qreg <- "^[[:digit:]]+[.](.*)"
  areg <- "^[[:space:]]*[(][[:alpha:]][)](.*)"
 
  qstart <- grep(qreg, text)
  astart <- grep(areg, text)
  class <- rep("", length(text))
  class[qstart] <- "q"
  class[astart] <- "a"
  
  result <- list()
  for (i in seq_along(qstart)) {
    line <- qstart[i]
    question <- sub(qreg, "\\1", text[line])
    line <- line + 1
    while (line < length(text) & class[line] == "") {
      question <- paste(question, text[line], sep="\n")
      line <- line + 1
    }
    this_astart <- astart[astart >= line]
    if (line < max(qstart))
      this_astart <- this_astart[this_astart < min(qstart[qstart > line])]
    answers <- list()
    for (j in seq_along(this_astart)) {
      line <- this_astart[j]
      answers[[j]] <- sub(areg, "\\1", text[line])
      line <- line + 1
      while (line < length(text) & class[line] == "")
        answers[[j]] <- paste(answers[[j]], text[line], sep="\n")
    }
    result[[i]] <- list(question = question, answers = answers)
  }
  result
}

toListOfLists(questions)
#> [[1]]
#> [[1]]$question
#> [1] " This is the first outer item."
#> 
#> [[1]]$answers
#> [[1]]$answers[[1]]
#> [1] " A"
#> 
#> [[1]]$answers[[2]]
#> [1] " B"
#> 
#> 
#> 
#> [[2]]
#> [[2]]$question
#> [1] " This is the second outer item."
#> 
#> [[2]]$answers
#> [[2]]$answers[[1]]
#> [1] " C"
#> 
#> [[2]]$answers[[2]]
#> [1] " D"
#> 
#> 
#> 
#> [[3]]
#> [[3]]$question
#> [1] " This is the third outer item."
#> 
#> [[3]]$answers
#> [[3]]$answers[[1]]
#> [1] " E"
#> 
#> [[3]]$answers[[2]]
#> [1] " F"

Created on 2022-04-29 by the reprex package (v2.0.1)

Something like your existing solution for the randomization and conversion back to text will work on this structure.

Solution 2:[2]

Part of the solution is to use latex. An example where the answers are randomized is shown below. But I am not able to randomize the Questions as well. Maybe a deeper look into the manual can bring up a solution.

---
title: "List Randomize"
header-includes:
   - \usepackage{randomlist}
output: pdf_document
---

## Test randomize

Eine Liste, randomisiert:

\begin{enumerate}
\item Question 1
  \RandomEnumerateList{
  answer1}{
  answer2}{
  answer3}
\item Question 2
  \RandomItemizeList{
  answer1}{
  answer2}{
  answer3}
\end{enumerate}

Package randomlist: https://ctan.joethei.xyz/macros/generic/randomlist/randomlist.pdf

Solution 3:[3]

This is the best that I have achieved until now:

---
title: "Untitled"
output: pdf_document
date: '2022-04-28'
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse)
```

```{r include=FALSE}
questions <- list(
  question1 = list(
    enun = "This is the first outer item.",
    ans1 = "A.",
    ans2 = "B.",
    ans3 = "X.",
    ans4 = "Y."
  ),
  question2 = list(
    enun = "This is the second outer item.",
    ans1 = "C.",
    ans2 = "D."
  ),
    question3 = list(
    enun = "This is the thrid outer item.",
    ans1 = "E.",
    ans2 = "F."
  )
)
```

```{r echo=FALSE, results='asis'}
qexpand <- function(x)
{
  s1 <- str_c("1. ", x[[1]])
  s2 <- map_chr(sample(2:length(x)), ~ str_c( "\n    (a) ", x[[.x]])) %>% 
        str_c(collapse = " ")
  
  str_c(s1, s2)
}

scr <- map(questions[c(3,1,2)], qexpand)

res = knitr::knit_child(text = scr, quiet = TRUE, )

cat(res, sep="\n")
```

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 user2554330
Solution 2 Lucas
Solution 3 PaulS