1

I am trying to write a macro that can take in a grouping of choices, then list them (in a specific, but possibly randomized, order) to use as little vertical space as possible while still presenting nicely. I have come up with a solution, but it uses hbox and thus fails if a single given choice exceeds one line of text. My MWE:

\documentclass{article}
\usepackage{forloop}
\makeatletter
\newcounter{choiceNum}
\newcounter{choiceEnvNum}
\newcounter{Iteration@printChoices}

\newenvironment{choices}{% Start environment code for multiple choice Choices environment
        \setcounter{choiceNum}{0}% New MCChoices environment means a new set of choices, so reset current choice to 0.
        \refstepcounter{choiceEnvNum}
    }% End of start-environment code.
    {% Start of end-environment code.
        \printChoices
    }% End of end-environment code.


\newcommand{\choice}[1]{% Choice command holds a multiple choice option by generating a new command with unique name that will be used to write the outputs at the end of the MCChoices environment.
        \refstepcounter{choiceNum}% A new choice means we need to step current choice (before definition since counters starts at 0)
        \expandafter\def\csname \Roman{choiceEnvNum}choice\roman{choiceNum}\endcsname{#1}
    }% End of \choice code.


\providecommand{\printChoices}{% \printChoices is an internal macro to print the choices at the end of a MCChoice environment, depending on if we want them shuffled or not. Note: I removed the shuffle content as this is a mwe

    % Now print the results:

    \stepcounter{choiceNum}% Step counter for the < symbol in foreloop.

    \forloop{Iteration@printChoices}{1}{\arabic{Iteration@printChoices}<\arabic{choiceNum}}{% Begin forloop
        \hspace*{0pt}\hfill\hbox{\expandafter\choiceLetter{\arabic{Iteration@printChoices}} % Print the correct letter of the choice. Note that \hspace*{0pt} forces the \hfill to populate correctly, or else TeX will ignore it at the start of lines which makes everything look super weird.
        \csname \Roman{choiceEnvNum}choice\roman{Iteration@printChoices}\endcsname}\hfill% Print choice
        }% End forloop
        % Check to see if we have a "last choice".
            \ifcsname\Roman{choiceEnvNum}lastChoice\endcsname% If we do, then we should print it.
            \hspace*{0pt}\hfill\hbox{\expandafter\choiceLetter{\arabic{choiceNum}} % Since we stepped choiceNum this hack will work.
            \csname \Roman{choiceEnvNum}lastChoice\endcsname}\hfill% Print lastChoice
            \fi% 
    }% End of \printChoices code.

\providecommand{\choiceLetter}[1]{% A simple internal command to print the correct letter.
    \ifnum\numexpr#1\relax=1 A)\fi
    \ifnum\numexpr#1\relax=2 B)\fi
    \ifnum\numexpr#1\relax=3 C)\fi
    \ifnum\numexpr#1\relax=4 D)\fi
    \ifnum\numexpr#1\relax=5 E)\fi
    \ifnum\numexpr#1\relax>5 ?Too many choices?)\fi
    }% End \choiceLetter code.

\providecommand{\lastChoice}[1]{% This is a \choice command, but it will be forced to be printed last.
        \expandafter\def\csname \Roman{choiceEnvNum}lastChoice\endcsname{\noexpand{#1}}
    }% End of \lastChoice command.

\begin{document}
\begin{choices}
\choice{This is choice 1}
\choice{This is choice 2}
\choice{This is choice 3}
\choice{And this is some really long choice that will not fit on one line, which is why hbox really isn't a great plan because I may have someone crazy try to do something like this even though they really probably shouldn't!}
\choice{Oh, and we need math, so $\frac{x}{2} \cdot 15 = $ ?}
\end{choices}
\end{document}

This is merely the solution I have come up with, but I'd like to have some kind of (relatively) nicely separated horizontal expansion of up to 5 or so choices where they line break only if a single \choice content would take up an entire line. I can't specify that the width be a certain amount as a single long choice would then become a vertical column and the real goal is to minimize vertical spread. So I used the hbox to avoid the linebreak, except clearly there are cases where a line break becomes necessary or the content goes way off the side of the paper.

Any suggestions?

  • Before of breaking your horns trying to invent a LaTeX system to make exams with randomized choices, maybe you should know about AMC. Probably too far of what of you have in mind right now, but also (probably) much better. – Fran Sep 29 '18 at 7:06
  • Yea, I've found a number of nice options for exams like this. Unfortunately I'm trying to make a cls file for my dept and they have a lot of 'official requirements' that are impractical to try and modify the existing cls or other options out in the LaTeX community already... bureaucracy at it's finest :p – Jason Sep 29 '18 at 14:15
  • If you like an answer and it was helpful, please consider upvoting by clicking on the arrows next to the score. If any of the answers solve your problem then mark the best solution as an accepted answer by clicking on the checkmark ✓. – Andrew Oct 4 '18 at 1:40
  • Thanks Andrew. Unfortunately although the current answer is close, it doesn't actually solve my problem. I've added a comment to the answer to specify the remaining trouble but it hasn't been addressed yet. If/when it is successfully addressed I'll definitely accept it however! – Jason Oct 4 '18 at 1:46
1

Perhaps this is something like what you're looking for?

\documentclass{article}
\usepackage{forloop}
\makeatletter
\newcounter{choiceNum}
\newcounter{choiceEnvNum}
\newcounter{Iteration@printChoices}

\newenvironment{choices}{% Start environment code for multiple choice Choices environment
        \setcounter{choiceNum}{0}% New MCChoices environment means a new set of choices, so reset current choice to 0.
        \refstepcounter{choiceEnvNum}
    }% End of start-environment code.
    {% Start of end-environment code.
        \printChoices
    }% End of end-environment code.


\newcommand{\choice}[1]{% Choice command holds a multiple choice option by generating a new command with unique name that will be used to write the outputs at the end of the MCChoices environment.
        \refstepcounter{choiceNum}% A new choice means we need to step current choice (before definition since counters starts at 0)
        \expandafter\def\csname \Roman{choiceEnvNum}choice\roman{choiceNum}\endcsname{#1}
    }% End of \choice code.


\providecommand{\printChoices}{% \printChoices is an internal macro to print the choices at the end of a MCChoice environment, depending on if we want them shuffled or not. Note: I removed the shuffle content as this is a mwe

    % Now print the results:

    \stepcounter{choiceNum}% Step counter for the < symbol in foreloop.
    \newdimen\qb@xlen \qb@xlen 0pt

    \forloop{Iteration@printChoices}{1}{\arabic{Iteration@printChoices}<\arabic{choiceNum}}{% Begin forloop
        \parindent=0pt
        \newbox\ch@icebox
        \setbox\ch@icebox\hbox{\expandafter\choiceLetter{\arabic{Iteration@printChoices}} % Print the correct letter of the choice. Note that \hspace*{0pt} forces the \hfill to populate correctly, or else TeX will ignore it at the start of lines which makes everything look super weird.
        \csname \Roman{choiceEnvNum}choice\roman{Iteration@printChoices}\endcsname }
        \ifdim \wd\ch@icebox > \textwidth
            % Too wide!
            \hfill \par \unhbox\ch@icebox \hfill \par
            \qb@xlen=0pt
        \else
            \advance\qb@xlen by \wd\ch@icebox
            \ifdim \qb@xlen > \textwidth
                \par \hfill \box\ch@icebox \hfill
                \qb@xlen=\wd\ch@icebox
            \else
                \hfill \box\ch@icebox \hfill 
            \fi
        \fi
        % \hfill\hbox{\expandafter\choiceLetter{\arabic{Iteration@printChoices}} % Print the correct letter of the choice. Note that \hspace*{0pt} forces the \hfill to populate correctly, or else TeX will ignore it at the start of lines which makes everything look super weird.
        % \csname \Roman{choiceEnvNum}choice\roman{Iteration@printChoices}\endcsname}\hfill% Print choice
        }% End forloop
        % Check to see if we have a "last choice".
            \ifcsname\Roman{choiceEnvNum}lastChoice\endcsname% If we do, then we should print it.
            \hspace*{0pt}\hfill\hbox{\expandafter\choiceLetter{\arabic{choiceNum}} % Since we stepped choiceNum this hack will work.
            \csname \Roman{choiceEnvNum}lastChoice\endcsname}\hfill% Print lastChoice
            \fi% 
    }% End of \printChoices code.

\providecommand{\choiceLetter}[1]{% A simple internal command to print the correct letter.
    \ifnum\numexpr#1\relax=1 A)\fi
    \ifnum\numexpr#1\relax=2 B)\fi
    \ifnum\numexpr#1\relax=3 C)\fi
    \ifnum\numexpr#1\relax=4 D)\fi
    \ifnum\numexpr#1\relax=5 E)\fi
    \ifnum\numexpr#1\relax>5 ?Too many choices?)\fi
    }% End \choiceLetter code.

\providecommand{\lastChoice}[1]{% This is a \choice command, but it will be forced to be printed last.
        \expandafter\def\csname \Roman{choiceEnvNum}lastChoice\endcsname{\noexpand{#1}}
    }% End of \lastChoice command.

\begin{document}
\begin{choices}
\choice{This is choice 1}
\choice{This is choice 2}
\choice{This is choice 3, it can't fit with A and B}
\choice{And this is some really long choice that will not fit on one line, which is why hbox really isn't a great plan because I may have someone crazy try to do something like this even though they really probably shouldn't!}
\choice{Oh, and we need math, so $\frac{x}{2} \cdot 15 = $ ?}
\end{choices}
\end{document}

enter image description here

  • This was almost perfect for me.... for some reason I was getting some weird alignment problems that made it so if the line had more than one answer choices before it, but had only one answer choice on it's line, it would become right-justified. I fixed this by commenting out the line qb@xlen=\wd\ch@icebox in the \ifdim \qb@xlen > \textwidth conditional, but I found it through trial and error, so I don't really know if that has bad repercussions in other cases I'm not trying. If you want to address this in your answer, I'll accept it. Thanks! – Jason Sep 30 '18 at 17:31
  • @Jason, I'm not entirely sure from the description what is going wrong, do you have some example question choices that caused the problem? Commenting out that line should have consequences. Specifically, if you have a large number of small (less than textwidth) choices then the \qb@xlen will eventually exceed the textwidth because of the \advances and you would see new choices on separate lines all the time. Also you should check out @John's answer regarding moving the \newbox and \newdimen out of the loop. – Joseph Gleason Oct 4 '18 at 16:32
1

This is a two box version of Gleason's answer. I also engaged is a lot of gratuitous and some really necessary changes (like not putting \newbox or \newdimen inside a loop).

One key addition is \choicesep (glue) which both expands like \hfill and provides a minimum gap of 2em.

\documentclass{article}
\usepackage{forloop}
\makeatletter
\newcounter{choiceNum}
\newcounter{choiceEnvNum}
\newcounter{Iteration@printChoices}

\renewcommand{\thechoiceEnvNum}{\Roman{choiceEnvNum}}
\renewcommand{\thechoiceNum}{\Alph{choiceNum}}
\renewcommand{\theIteration@printChoices}{\Alph{Iteration@printChoices}}

\newsavebox\choicebox
\newskip\choicesep% separation between choices (glue)
\choicesep=2em plus 100fil

\newenvironment{choices}{% Start environment code for multiple choice Choices environment
        \setcounter{choiceNum}{0}% New MCChoices environment means a new set of choices, so reset current choice to 0.
        \refstepcounter{choiceEnvNum}%
    }% End of start-environment code.
    {% Start of end-environment code.
        \printChoices
    }% End of end-environment code.


\newcommand{\choice}[1]{% Choice command holds a multiple choice option by generating a new command with unique name that will be used to write the outputs at the end of the MCChoices environment.
        \refstepcounter{choiceNum}% A new choice means we need to step current choice (before definition since counters starts at 0)
        \expandafter\def\csname \thechoiceEnvNum choice\roman{choiceNum}\endcsname{#1}%
        \ignorespaces
    }% End of \choice code.


\providecommand{\printChoices}{% \printChoices is an internal macro to print the choices at the end of a MCChoice environment, depending on if we want them shuffled or not. Note: I removed the shuffle content as this is a mwe
    % Now print the results:
    \stepcounter{choiceNum}% Step counter for the < symbol in foreloop.
    \savebox\choicebox{}%
    \parindent=0pt
    \forloop{Iteration@printChoices}{1}{\value{Iteration@printChoices}<\value{choiceNum}}{% Begin forloop
        \sbox0{\theIteration@printChoices\space
          \csname \thechoiceEnvNum choice\roman{Iteration@printChoices}\endcsname}%
        \ifdim \textwidth > \dimexpr \wd\choicebox+\wd0\relax
            % okay
            \ifdim \wd\choicebox>0pt
              \savebox\choicebox{\unhbox\choicebox \hspace{\choicesep}\unhbox0}%
            \else
              \setbox\choicebox=\box0
            \fi
        \else% too wide
            \ifdim \wd\choicebox>0pt
              \par
              \hangindent=1em
              \unhbox\choicebox
            \fi
            \setbox\choicebox=\box0
        \fi}%
    \ifdim \wd\choicebox>0pt %last box
      \par
      \hangindent=1em
      \unhbox\choicebox
      \par
    \fi
    }% End of \printChoices code.

\begin{document}
\begin{choices}
\choice{This is choice 1}
\choice{This is choice 2}
\choice{This is choice 3, it can't fit with A and B}
\choice{And this is some really long choice that will not fit on one line, which is why hbox really isn't a great plan because I may have someone crazy try to do something like this even though they really probably shouldn't!}
\choice{Oh, and we need math, so $\frac{x}{2} \cdot 15 = $ ?}
\end{choices}
\end{document}

demo

  • This is muuuuch nicer, thanks! Is it possible to add some kind of \hfill on the right edge however, to prevent the 'spread to the edges' effect, as seen in your choice A and B? Ideally "choice 1" would stay in the same spot, but "choice 2" would be shifted to just about the middle or so in your example; as if choice 1 and 2 were left justified in 2 equal size columns if that makes sense? – Jason Oct 4 '18 at 23:58
  • Also, I just tried a few options and found a weird example where the choice letter is on one line and the rest of the choice is on the other; seems that the choice letter needs to be moved into the box as well so they stay together? – Jason Oct 5 '18 at 0:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.