How to arrange a numbered item list into two columns?

I have the following code which resets the numbering on an enum in the second block. Here is the code.

- Which of the following statements generates 10 random numbers from a Poisson distribution with $lambda = 2$? 
#grid(
  columns: (1fr, 1fr),
  column-gutter: -15em,
  block(width: 100%)[
+ `rpois(10, lambda = 2)` 
+ `ppois(10, lambda = 2)`],
  block(width: 100%)[
+ `rpois(10, lambda = 2)`
+ `ppois(10, lambda = 2)`
  ],
)
#solution[Correct answer: A]

How can I make the numbering continue?

Edit: Yes, in this case I can manually just type in the letters as there are only four of them, but it’s a good learning experience for me.

I moved your question to the Questions category, as it fits better there :)

One (admittedly quite hacky) way is to make a custom show rule that ignores the “enum” value, and instead manually inserts a value from a counter, like so:

#let cnt = counter("resume-enum-numbering")
#cnt.update(1)

#show grid: it => {
  show enum.item: i => context [
    #cnt.display(enum.numbering) 
    #cnt.step()
    #i.body\
  ]
  it
  cnt.update(1)  // reset
}

One way to make this more elegant would be to update the enum counter directly, but this is not supperted yet

1 Like

You haven’t specified the order of numbering, so I made a solution for both:

Row-first:

#let solution(body) = body

#let options(body) = context {
  let items = body
    .children
    .filter(x => x.func() == enum.item)
    .enumerate()
    .map(((i, x)) => [#numbering(enum.numbering, i + 1) #x.body])
  set raw(lang: "r")
  grid(columns: 2, column-gutter: 1.65em, row-gutter: par.leading, ..items)
}

- Which of the following statements generates 10 random numbers from a Poisson
  distribution with $lambda = 2$?
  #options[
    + `rpois(10, lambda = 1)`
    + `ppois(10, lambda = 2)`
    + `rpois(10, lambda = 3)`
    + `ppois(10, lambda = 4)`
    + `rpois(10, lambda = 5)`
    + `ppois(10, lambda = 6)`
  ]
  #solution[Correct answer: A]

image

Column-first:

#let zip-long(..arrays, fill: none) = {
  let arrays = arrays.pos()
  let max-len = calc.max(..arrays.map(x => x.len()))
  let filler = x => if x.len() == max-len { x } else {
    x + (fill,) * (max-len - x.len())
  }
  array.zip(..arrays.map(filler))
}

#let solution(body) = body

#let options(body) = context {
  if type(body) != content or body.func() != [].func() { return }
  let items = body
    .children
    .filter(x => x.func() == enum.item)
    .enumerate()
    .map(((i, x)) => [#numbering(enum.numbering, i + 1) #x.body])
  if items.len() == 0 { return }
  set raw(lang: "r")
  grid(
    columns: 2,
    column-gutter: 1.65em,
    row-gutter: par.leading,
    ..zip-long(..items.chunks(int((items.len() + 1) / 2))).flatten(),
  )
}

- Which of the following statements generates 10 random numbers from a Poisson
  distribution with $lambda = 2$?
  #options[
    + `rpois(10, lambda = 1)`
    + `ppois(10, lambda = 2)`
    + `rpois(10, lambda = 3)`
    + `ppois(10, lambda = 4)`
    + `rpois(10, lambda = 5)`
    + `ppois(10, lambda = 6)`
  ]
  #solution[Correct answer: A]

image


Notes:

  • Please use the appropriate language name marker for code block to get the correct syntax highlighting, in your case:

    ```typ
    ```
    
  • You can use code syntax highlighting when using raw syntax in Typst, if you need it.

  • The code example should be compilable, but you didn’t show how solution is defined. See https://sscce.org.

  • There is a difference between

    - #lorem(10)
    text1
    
    text2
    

    and

    - #lorem(10)
      text1
    
      text2
    
  • The column-first implementation handles the case where there is an odd number of items, which is also why a custom zip-long function is required.

1 Like

The #i.body\ adds the hackyness level (since it doesn’t visually separate multiple markup lines), so you should probably make it more descriptive:

#show grid: it => {
  show enum.item: item => context {
    [#cnt.display(enum.numbering) #item.body]
    linebreak()
    cnt.step()
  }
  it
  cnt.update(1) // reset
}

Here, a reader clearly sees that a linebreak() is used, which is a regular form of \ sugar syntax. Also, the i makes it look like an index, so naming it item further improves the readability. And the last point, is that in markup mode each code expression needs to be prefixed with #, which adds unnecessary visual noise, if it can be avoided. You can use the code mode first, and write a single markup line inside. This is usually results in a cleaner solution.

Note that since .step() is called inside context, the .display() call won’t be affected by it, so you can put them in any order.

I would personally also avoid the cnt name as well as move the number value to a variable:

#show grid: it => {
  let counter = counter("resume-enum-numbering")
  counter.update(1)
  show enum.item: item => context {
    let num = counter.display(enum.numbering)
    counter.step()
    [#num #item.body]
    linebreak()
  }
  it
  counter.update(1) // reset
}

Since the counter is used in just one place, you can move everything into a single place to keep it all together.

1 Like