summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/randexam/randexam.cls
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-01-21 03:01:05 +0000
committerNorbert Preining <norbert@preining.info>2024-01-21 03:01:05 +0000
commitb2fc73208c72c65ca8f9ce068898291b69322d32 (patch)
treec5027d520af6b383a642459af510e38224cf97d5 /macros/latex/contrib/randexam/randexam.cls
parent2fd07558ccd8d3c8e354f8ea0b342ef7ac1e87a3 (diff)
CTAN sync 202401210301
Diffstat (limited to 'macros/latex/contrib/randexam/randexam.cls')
-rw-r--r--macros/latex/contrib/randexam/randexam.cls942
1 files changed, 942 insertions, 0 deletions
diff --git a/macros/latex/contrib/randexam/randexam.cls b/macros/latex/contrib/randexam/randexam.cls
new file mode 100644
index 0000000000..8f5ae8bde9
--- /dev/null
+++ b/macros/latex/contrib/randexam/randexam.cls
@@ -0,0 +1,942 @@
+% -*- coding: utf-8 -*-
+% ----------------------------------------------------------------------------
+% Author: Jianrui Lyu <tolvjr@163.com>
+% License: The LaTeX Project Public License 1.3c
+% ----------------------------------------------------------------------------
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{randexam}[2024-01-20 v2024B Make an exam paper and its randomized variants]
+
+%% Old LaTeX release could not recongnize date format like 2022-11-01
+%\@ifl@t@r\fmtversion{2022-11-01}{}{
+\@ifl@t@r\fmtversion{2022/11/01}{}{
+ \ClassError{randexam}{%
+ Your current TeX distribution is quite old.\MessageBreak
+ We need CTeX 3.0+ or MiKTeX 2023+ or TeXLive 2023+%
+ }{Please update your TeX distribution first.}
+}
+
+\RequirePackage{etoolbox}
+
+\newbool{plain} \boolfalse{plain} % use plain page style
+\newbool{twoinone} \boolfalse{twoinone} % use A3 paper
+\newbool{oneside} \boolfalse{oneside} % use single sided exam paper
+\newbool{resetnumber} \booltrue{resetnumber} % reset numbers in new exam groups
+\newbool{random} \boolfalse{random} % shuffle questions
+\newbool{answer} \booltrue{answer} % show answers
+\newbool{amsfonts} \boolfalse{amsfonts} % use ams fonts
+\newbool{freealign} \boolfalse{freealign} % load freealign package
+\newbool{medmath} \boolfalse{medmath} % use medium-size formulas
+\newbool{grader} \boolfalse{grader} % add grader line in the grade table
+
+\DeclareKeys{
+ plain .if = plain,
+ a3paper .if = twoinone,
+ a3input .code = \booltrue{twoinone}\booltrue{plain},
+ oneside .if = oneside,
+ random .if = random,
+ noanswer .ifnot = answer,
+ amsfonts .if = amsfonts,
+ freealign .if = freealign,
+ medmath .if = medmath,
+ grader .if = grader,
+ many .code = \booltrue{freealign},
+ most .code = \booltrue{freealign}\booltrue{medmath}
+}
+
+\DeclareUnknownKeyHandler{\PassOptionsToClass{\CurrentOption}{article}}
+
+\ProcessKeyOptions
+
+\LoadClass{article}
+
+\iftwoinone
+ \RequirePackage[a3paper,landscape,twocolumn,columnsep=60mm,left=30mm,right=30mm,top=25mm,bottom=25mm]{geometry}
+\else
+ \RequirePackage[a4paper,left=30mm,right=30mm,top=25mm,bottom=25mm]{geometry}
+\fi
+
+\RequirePackage{amsmath}
+\RequirePackage{array}
+\RequirePackage{calc}
+\RequirePackage{comment}
+\RequirePackage[inline]{enumitem}
+\RequirePackage{environ}
+\RequirePackage{fancyhdr}
+\RequirePackage{zref-user,zref-lastpage}
+\RequirePackage{tabularx}
+\RequirePackage{xcolor}
+\RequirePackage{xkeyval}
+
+\ifplain
+ \allowdisplaybreaks[4]
+\fi
+
+\ifamsfonts
+ \RequirePackage{amssymb}
+\else
+ \RequirePackage[utopia]{mathdesign} % charter, utopia
+ \renewcommand\bfdefault{bx}
+ \let\oldoiint\oiint\renewcommand{\oiint}{\oldoiint\nolimits}
+ \DeclareTextCommandDefault{\nobreakspace}{\leavevmode\nobreak\ }
+\fi
+
+\newcolumntype{Y}{>{\centering\arraybackslash}X}
+\newcolumntype{n}[1]{>{\centering\arraybackslash}m{#1}}
+
+\setlength{\parindent}{0em}
+\setlength{\lineskiplimit}{4pt}
+\setlength{\lineskip}{4pt}
+
+\NewDocumentCommand\SetExamOption{+m}{\SetKeys[randexam]{#1}}
+
+%% ---------------------------------------------------------------------------
+%% Command for exam title: \examtitle
+%% ---------------------------------------------------------------------------
+
+\newcommand{\underspace}[1]{\kern0pt\underline{\hspace{#1}}\kern0pt\relax}
+\newcommand{\underbox}[2]{\kern0pt\underline{\makebox[#1]{#2}}\kern0pt\relax}
+\newcommand{\underparbox}[2]{\kern0pt\underline{\parbox[b]{#1}{#2}}\kern0pt\relax}
+
+\newcommand{\ischeck}[1]{\ifnum#1>0\,$\checkmark$\,\else\quad\fi}
+\newcommand{\isquad}[1]{\ifnum#1=0\,$\checkmark$\,\else\quad\fi}
+
+\def\rdxm@temp@a{A}
+\def\rdxm@temp@c{C}
+\def\rdxm@empty{}
+
+\DeclareKeys[randexam-title]{
+ name .store = \@rdxm@title@name,
+ date .store = \@rdxm@title@date,
+ version .store = \@rdxm@title@version
+}
+
+\NewDocumentCommand\SetExamTitle{+m}{\SetKeys[randexam-title]{#1}}
+
+\SetExamTitle{name=Math 1906,date=\today,version=A}
+
+\newcommand\examtitle[1]{%
+ \SetExamTitle{#1}%
+ \thispagestyle{plain}
+ \ifbool{random}{%
+ \ifx\@rdxm@title@version\rdxm@temp@a\def\@rdxm@title@version{B}\fi
+ \ifx\@rdxm@title@version\rdxm@temp@c\def\@rdxm@title@version{D}\fi
+ }{}%
+ \begingroup
+ \Large\noindent
+ \ifbool{answer}{%
+ \textcolor{red!80!black}{\@rdxm@title@name\hfill Solutions}%
+ }{%
+ \@rdxm@title@name\hfill Name:\underspace{6em}%
+ }\par
+ \endgroup
+}
+
+%% ---------------------------------------------------------------------------
+%% Command for grade table: \gradetable
+%% ---------------------------------------------------------------------------
+
+%% You need to put .code:n before .initial:n
+\DeclareKeys[randexam]{%
+ groupnumber .code:n = \let\exam@group@number#1,
+ groupnumber .initial:n = \Roman,
+}
+
+\newcounter{@exam@grade@cnt}
+\newcommand\rdxm@group@number[1]{%
+ \stepcounter{@exam@grade@cnt}%
+ \exam@group@number{@exam@grade@cnt}%
+}
+
+\newcommand\insertgroupname{Part}
+\newcommand\insertscorename{Score}
+\newcommand\insertgradername{Grader}
+\newcommand\inserttotalname{Total}
+
+\newcommand\gradetable{%
+ \par\vspace{1em}%
+ \setcounter{@exam@grade@cnt}{0}%
+ \noindent\begin{tabularx}{\linewidth}{|*{8}{Y|}}
+ \hline
+ \textbf{\insertgroupname}
+ & \rdxm@group@number{1} & \rdxm@group@number{2} & \rdxm@group@number{3}
+ & \rdxm@group@number{4} & \rdxm@group@number{5} & \rdxm@group@number{6}
+ & \inserttotalname\\
+ \hline
+ \textbf{\insertscorename}\rule[-0.75em]{0pt}{2.5em} & & & & & & & \\
+ \hline
+ \ifbool{grader}{
+ \textbf{\insertgradername}\rule[-0.75em]{0pt}{2.5em} & & & & & & & \\
+ \hline
+ }{}
+ \end{tabularx}
+}
+
+%% ---------------------------------------------------------------------------
+%% Setting header and footer
+%% ---------------------------------------------------------------------------
+
+\newcommand{\rdxm@columnbox}[1]{\makebox[\columnwidth]{#1}}
+\newcommand{\rdxm@headleft}{\@rdxm@title@name}
+\newcommand{\rdxm@headright}{\ifbool{answer}{Solutions}{Name:\hspace{12em}}}
+\newcommand{\rdxm@headtext}{\rdxm@headleft\hfill\rdxm@headright}
+\newcommand{\rdxm@footleft}{\@rdxm@title@date}
+\newcommand{\rdxm@footcenter}{Page~\thepage~of~\zpageref{LastPage}}
+\newcommand{\rdxm@footright}{Version \@rdxm@title@version}
+\newcommand{\rdxm@foottext}{\rdxm@footleft\hfill\rdxm@footcenter\hfill\rdxm@footright}
+
+% fancy page style
+\fancyhf{} % clear head and foot
+\iftwoinone
+ \renewcommand{\headrulewidth}{0pt}%
+ \lhead{\small\underline{\rdxm@columnbox{\rdxm@headtext}\strut}}
+ \rhead{\small\underline{\rdxm@columnbox{\rdxm@headtext}\strut}}
+ \lfoot{\small\rdxm@columnbox{\rdxm@foottext}}
+ \rfoot{\small\rdxm@columnbox{\stepcounter{page}\rdxm@foottext}}
+\else
+ \lhead{\small\rdxm@headleft}
+ \rhead{\small\rdxm@headright}
+ \cfoot{\small\rdxm@foottext}
+\fi
+
+% plain page style
+\fancypagestyle{plain}{
+ \renewcommand{\headrulewidth}{0pt}%
+ \fancyhf{}
+ \iftwoinone
+ \rhead{\small\underline{\rdxm@columnbox{\rdxm@headtext\strut}}}
+ \lfoot{\small\rdxm@columnbox{\rdxm@foottext}}
+ \rfoot{\small\rdxm@columnbox{\stepcounter{page}\rdxm@foottext}}
+ \else
+ \cfoot{\small\rdxm@foottext}
+ \fi
+}
+
+\ifplain
+ \pagestyle{plain}
+\else
+ \pagestyle{fancy}
+\fi
+
+%% ---------------------------------------------------------------------------
+%% Class option for shuffling questions: random
+%% Class option for random seed: seed
+%% ---------------------------------------------------------------------------
+
+%% The random seed could not exceed 2147483647 = "7FFFFFFF
+%% You need to put .store before .initial:n
+\DeclareKeys[randexam]{
+ seed .store = \rdxm@random@seed,
+ seed .initial:n = 19061116
+}
+
+\ifrandom
+ \RequirePackage{pgf}
+ \RequirePackage{pgffor}
+ \newcommand*\exam@set@seed{%
+ %% 当\pgfmathrandom的参数为3的倍数时,对相邻种子生成的多个随机数分布不均匀
+ %\pgfmathsetseed{\numexpr\rdxm@random@seed+\value{rdxm@group}-1\relax}%
+ %% 因此我们改用下面的方法,用随机数种子生成下一个随机数种子
+ \pgfmathsetseed{\rdxm@random@seed}%
+ \pgfmathrandominteger\rdxm@random@seed{1}{2147483647}%
+ }
+\fi
+
+%% ---------------------------------------------------------------------------
+%% Command for exam groups: \examgroup
+%% Command for appendix data: \examdata
+%% Environment for questions: question
+%% Environment for solutions: solution
+%% ---------------------------------------------------------------------------
+
+\newif\ifonlyonequestion \onlyonequestionfalse % 此部分仅有一道题时不显示题目编号
+\xdef\allquestions{}
+\xdef\lastquestion{}
+\newcounter{question} % 当前题型的小题编号
+\newcounter{questionreal} % 实际显示的小题编号,在各题型小题统一编号时使用
+\newcounter{totalquestions} % 之前各题型小题总数,在各题型小题统一编号时使用
+\newcommand{\solutionname}{Solution}
+\newcounter{choice} % 后面选择题的 abcd 环境要用到
+\newcommand{\hangtext}{}
+\newlength{\hanglength}
+\colorlet{group number}{black}
+\colorlet{question number}{blue!80!black}
+\colorlet{solution name}{blue!80!black}
+
+\newcounter{rdxm@shuffle@temp@cnt}
+\newcounter{rdxm@list@temp@cnt}
+
+\newcommand\rdxm@list@print[1]{%
+ \par\renewcommand*{\do}[1]{(##1)}%
+ \dolistloop#1%
+}
+
+\newcommand\rdxm@list@remove[2]{%
+ %\rdxm@list@print\rdxm@shuffle@list
+ \setcounter{rdxm@list@temp@cnt}{0}%
+ \global\let\rdxm@tmpa@list=#1%
+ \gdef#1{}%
+ \par\renewcommand*{\do}[1]{%
+ \stepcounter{rdxm@list@temp@cnt}%
+ \ifnumequal{\value{rdxm@list@temp@cnt}}{#2}{%
+ \def\rdxm@list@item{##1}%
+ %[##1]%
+ }{
+ \listxadd#1{##1}%
+ }%
+ }%
+ \dolistloop\rdxm@tmpa@list
+}
+
+\newcommand\rdxm@shuffle@questions{%
+ \exam@set@seed
+ \ifnumgreater{\value{question}}{2}{%
+ \gdef\rdxm@shuffle@list{}%
+ \foreach \i in {1,...,\value{question}} {\listxadd\rdxm@shuffle@list{\i}}%
+ %% 首尾两个小题的位置总要改变
+ \pgfmathrandom{2,\value{question}}%
+ \rdxm@list@remove\rdxm@shuffle@list{\pgfmathresult}%
+ \global\csletcs{rdxm@question@b@1}{rdxm@question@a@\rdxm@list@item}%
+ \ifnumequal{\rdxm@list@item}{\value{question}}{
+ \pgfmathrandom{\numexpr\value{question}-1\relax}%
+ \rdxm@list@remove\rdxm@shuffle@list{\pgfmathresult}%
+ \global\csletcs{rdxm@question@b@\the\value{question}}{rdxm@question@a@\rdxm@list@item}%
+ }{%
+ \pgfmathrandom{\numexpr\value{question}-2\relax}%
+ \rdxm@list@remove\rdxm@shuffle@list{\pgfmathresult}%
+ \global\csletcs{rdxm@question@b@\the\value{question}}{rdxm@question@a@\rdxm@list@item}%
+ }%
+ %% 其他小题的位置没有任何限制
+ \setcounter{rdxm@shuffle@temp@cnt}{1}%
+ \whileboolexpr{%
+ test{\ifnumless{\value{rdxm@shuffle@temp@cnt}}{\numexpr\value{question}-1\relax}}%
+ }{%
+ \stepcounter{rdxm@shuffle@temp@cnt}%
+ \pgfmathrandom{\numexpr\value{question}-\value{rdxm@shuffle@temp@cnt}\relax}%
+ \rdxm@list@remove\rdxm@shuffle@list{\pgfmathresult}%
+ \global\csletcs{rdxm@question@b@\the\value{rdxm@shuffle@temp@cnt}}{%
+ rdxm@question@a@\rdxm@list@item
+ }%
+ }%
+ }{%
+ \ifnumequal{\value{question}}{2}{%
+ \global\csletcs{rdxm@question@b@1}{rdxm@question@a@2}%
+ \global\csletcs{rdxm@question@b@2}{rdxm@question@a@1}%
+ }{}%
+ }%
+}
+
+\newcommand{\printquestions}{%
+ \ifrandom
+ \rdxm@appto@questions
+ \rdxm@shuffle@questions
+ \setcounter{question}{0}%
+ \allquestions
+ \fi
+ \xdef\allquestions{}%
+ \xdef\lastquestion{}%
+}
+
+\newcounter{rdxm@group}
+
+\newcommand\insertgroup[2]{%
+ \noindent\textbf{\textcolor{group number}{Part~\Roman{rdxm@group}}: #1} (#2)%
+}
+
+\newcommand{\examgroup}[2]{%
+ \printquestions
+ \setcounter{totalquestions}{\value{totalquestions}+\value{question}}%
+ \setcounter{question}{0}%
+ \stepcounter{rdxm@group}%
+ \vspace{1em}%
+ \insertgroup{#1}{#2}%
+ \par\nopagebreak
+ \if\relax\detokenize{#1}\relax % #1 is empty
+ \onlyonequestiontrue
+ \else
+ \onlyonequestionfalse
+ \vspace{1em}%
+ \fi
+ %其中设定了\@nobreaktrue,保证在列表前也不分页,详情见 source2e
+ \@afterheading
+}
+
+\renewcommand\appendixname{Appendix}
+\newcommand{\examdata}[1]{%
+ \printquestions\rdxm@stop@random
+ \centerline{\textbf{\appendixname}\quad #1}\smallskip
+}
+
+\preto{\@enddocumenthook}{\printquestions\rdxm@stop@random}
+
+\newcommand\ignorepars{\@ifnextchar\par{\expandafter\ignorepars\@gobble}{}}
+
+% 局部定义,仅在当前题目内有效
+\define@key{exam@question}{points}[-1]{\def\rdxm@question@points{#1}}
+\define@key{exam@question}{level}[]{\def\rdxm@question@level{#1}}
+\define@key{exam@question}{year}[]{\def\rdxm@question@year{#1}}
+
+\newcommand\pointname{point}
+\newcommand\pointsname{points}
+\newcommand\pointorpoints[1]{\ifnumgreater{#1}{1}{\pointsname}{\pointname}}
+
+\newcommand{\questionpointstext}[1]{ (#1 \pointorpoints{#1}) }
+
+\newcommand\rdxm@hook@exec@other@keys{}
+
+\newcommand{\execute@question@keys}[1]{%
+ \setkeys{exam@question}{#1}%
+ \rdxm@hook@exec@other@keys
+ \ifdefvoid{\rdxm@question@points}{}{\questionpointstext{\rdxm@question@points}}%
+}
+
+\newenvironment{questionreal}[1][]{%
+ \stepcounter{question}\setcounter{choice}{0}%
+ \ifresetnumber
+ \ifonlyonequestion
+ \renewcommand{\hangtext}{\qquad}%
+ \else
+ \renewcommand{\hangtext}{\textbf{\textsf{\textcolor{question number}{\arabic{question}}.}}\;\,}%
+ \fi
+ \else
+ \setcounter{questionreal}{\value{totalquestions}+\value{question}}%
+ \renewcommand{\hangtext}{\textbf{\textsf{\textcolor{question number}{\arabic{questionreal}}.}}\;\,}%
+ \fi
+ \settowidth{\hanglength}{\hangtext}%
+ \description[leftmargin=\hanglength,labelwidth=0pt,labelsep=0pt,topsep=0pt,parsep=0pt]
+ \item[\hangtext]\execute@question@keys{#1}%
+}{\enddescription}
+\newenvironment{solutionreal}{%
+ \renewcommand{\hangtext}{\textbf{\textsf{\textcolor{solution name}{\solutionname}.}}\;\,}%
+ \settowidth{\hanglength}{\hangtext}%
+ \description[leftmargin=\hanglength,labelwidth=0pt,labelsep=0pt,topsep=0pt,parsep=0pt]
+ \item[\hangtext]
+}{\enddescription}
+
+\let \oldnewpage = \newpage
+\let \oldvfill = \vfill
+\let \oldsmallskip = \smallskip
+\let \oldmedskip = \medskip
+\let \oldbigskip = \bigskip
+
+\ifrandom
+ \newcommand\rdxm@appto@questions{%
+ \xappto\allquestions{\expandonce\lastquestion}%
+ }%
+ \NewEnviron{question}{%
+ \stepcounter{question}%
+ \rdxm@appto@questions
+ \csxdef{rdxm@question@a@\the\value{question}}{%
+ \unexpanded{\begin{questionreal}}%
+ \unexpanded\expandafter{\BODY}%
+ \unexpanded{\end{questionreal}}%
+ }%
+ \csxdef{rdxm@question@b@\the\value{question}}{%
+ \expandonce{\csname rdxm@question@a@\the\value{question}\endcsname}%
+ }%
+ \xdef\lastquestion{%
+ \expandonce{\csname rdxm@question@b@\the\value{question}\endcsname}%
+ }%
+ }
+ \NewEnviron{solution}{%
+ \csxappto{rdxm@question@a@\the\value{question}}{%
+ \unexpanded{\begin{solutionreal}}%
+ \expandonce{\BODY}%
+ \unexpanded{\end{solutionreal}}%
+ }%
+ }
+ \renewcommand{\newpage}{\gappto\lastquestion{\oldnewpage}}
+ \renewcommand{\vfill}{\csgappto{rdxm@question@a@\the\value{question}}{\oldvfill}}
+ \renewcommand{\smallskip}{\csgappto{rdxm@question@a@\the\value{question}}{\oldsmallskip}}
+ \renewcommand{\medskip}{\csgappto{rdxm@question@a@\the\value{question}}{\oldmedskip}}
+ \renewcommand{\bigskip}{\csgappto{rdxm@question@a@\the\value{question}}{\oldbigskip}}
+\else
+ \newenvironment{question}[1][]{\questionreal[#1]}{\endquestionreal}
+ %\newenvironment{solution}{\solutionreal}{\endsolutionreal}
+ \NewEnviron{solution}{\begin{solutionreal}\BODY\end{solutionreal}}
+\fi
+
+\newcommand{\rdxm@stop@random}{%
+ \ifrandom
+ \renewenvironment{question}{\questionreal}{\endquestionreal}%
+ \renewenvironment{solution}{\solutionreal}{\endsolutionreal}%
+ \let \newpage = \oldnewpage
+ \let \vfill = \oldvfill
+ \let \smallskip = \oldsmallskip
+ \let \medskip = \oldmedskip
+ \let \bigskip = \oldbigskip
+ \fi
+}
+
+\def\CommentCutFile{\jobname.cut}
+
+\AtBeginDocument{%
+ \ifanswer\else
+ \excludecomment{solution}
+ \fi
+}
+
+%% ---------------------------------------------------------------------------
+%% Command for answer tables: \answertable
+%% ---------------------------------------------------------------------------
+
+\gdef\answer@lines@temp{}%
+\newcommand{\answer@lines@add}[1]{%
+ \xdef\answer@lines@temp{\answer@lines@temp#1}%
+}
+
+\newcommand\insertnumbertext{Number}
+\newcommand\insertanswertext{Answer}
+\newcommand\insertanswertabletext{%
+ Notice: you MUST write the answers in the following tables.%
+}
+
+\newrobustcmd{\answer@number@hided}[1]{\insertnumbertext} % 在 PDFLaTeX 中需要保护中文
+\newrobustcmd{\answer@cell@strut}[1]{\parbox[c][#1][c]{2em}{\hbox{\insertanswertext}}}
+
+\newcounter{answer@col}
+\newcounter{answer@row}
+\newcounter{answer@total}
+
+\newcommand{\answer@lines}[3]{%
+ % #1 答题栏各栏指定高度
+ % #2 答题栏总共答案个数
+ % #3 答题栏每行答案个数
+ \setcounter{answer@row}{(#2-1)/#3+1}% 除法向下取整,改为向上取整
+ \begingroup
+ \let\hline=\relax \let\\=\relax % 禁止展开
+ \gdef\answer@lines@temp{}%
+ \setcounter{answer@total}{1}%
+ \whileboolexpr{
+ test{\ifnumgreater{\value{answer@row}}{0}}
+ }{%
+ \addtocounter{answer@row}{-1}%
+ \answer@lines@add{\answer@number@hided}%
+ \setcounter{answer@col}{1}%
+ \unlessboolexpr{%
+ test{\ifnumgreater{\value{answer@col}}{#3}}%
+ }{%
+ \answer@lines@add{&}%
+ \ifnumgreater{\value{answer@total}}{#2}{}{%
+ \answer@lines@add{\arabic{answer@total}}%
+ }%
+ \stepcounter{answer@col}%
+ \stepcounter{answer@total}%
+ }%
+ \answer@lines@add{\\ \hline \answer@cell@strut{#1}}%
+ \setcounter{answer@col}{1}%
+ \unlessboolexpr{
+ test{\ifnumgreater{\value{answer@col}}{#3}}
+ }{%
+ \answer@lines@add{&}%
+ \stepcounter{answer@col}%
+ }%
+ \answer@lines@add{\\ \hline}%
+ }%
+ \endgroup
+ \answer@lines@temp
+}
+
+\newcommand{\answertable}[3][1em]{%
+ \insertanswertabletext\par
+ \begin{tabularx}{\linewidth}{|c|*{#3}{Y|}}
+ \hline
+ \answer@lines{#1}{#2}{#3}
+ \end{tabularx}%
+ \par\vspace{0.8em}%
+}
+
+%% ---------------------------------------------------------------------------
+%% Command for toggling answers: \answer
+%% Command for true-or-false questions: \tickin and \tickout
+%% Command for fill-in-the-blank questions: \fillin and \fillout
+%% Command for multiple-choice questions: \pickin and \pickout
+%% ---------------------------------------------------------------------------
+
+\newcommand{\answer}[1]{\ifanswer#1\else\phantom{#1}\fi}
+
+\newcommand*{\cdotfill}{\leavevmode\xleaders\hbox to 0.5em{\hss$\cdot$\hss}\hfill\kern0pt\relax}
+
+\newcommand*{\tick@box}[1]{[\makebox[1.5em]{\color{blue}\answer{#1}}]}
+\newcommand*{\tick@text@t}{$\checkmark$}
+\newcommand*{\tick@text@f}{{\large$\times$}}
+\newcommand*{\tick@text@T}{\sffamily T}
+\newcommand*{\tick@text@F}{\sffamily F}
+\newcommand*{\tickin}[1]{\tick@box{\csname tick@text@#1\endcsname}}
+\newcommand*{\tickout}[1]{\unskip\nobreak\cdotfill\tick@box{\csname tick@text@#1\endcsname}}
+
+\newcommand*{\ulinefill}[1]{\xleaders\hbox{\underline{\vphantom{#1}\kern1pt}}\hfill\kern0pt}
+\newcommand*{\minwidthbox}[2]{\makebox[{\ifdim#1<\width\width\else#1\fi}]{#2}}
+
+\newcommand*{\fillout}[1]{\allowbreak\hbox{}\nobreak\ulinefill{#1}\underline{\color{blue}\answer{#1}}\ulinefill{#1}}
+\newcommand*{\fillin}[1]{\underline{\hspace{1em}\color{blue}\minwidthbox{2em}{\answer{#1}}\hspace{1em}}}
+
+\newcommand*\pickoutreal[1]{%
+ \unskip\nobreak\cdotfill(\makebox[1.5em]{\color{blue}\answer{#1}})%
+}
+\newcommand*\pickinreal[1]{%
+ \unskip\nobreak
+ \hspace{0.3em}(\makebox[1.5em]{\color{blue}\answer{#1}})\hspace{0.3em}%
+ \ignorespaces
+}
+
+%% 选择题四个选项打乱顺序,用三种方法即可保证答案不同
+%% 1:ABCD -> CDAB; 2:ABCD -> BADC; 3: ABCD -> DCBA
+%% 即在两行排列时仅用到上下交换以及左右交换,这样可以保持两行长度不变
+
+\csdef{rdxm@shuffle@1@A}{C} \csdef{rdxm@shuffle@2@A}{B} \csdef{rdxm@shuffle@3@A}{D}
+\csdef{rdxm@shuffle@1@B}{D} \csdef{rdxm@shuffle@2@B}{A} \csdef{rdxm@shuffle@3@B}{C}
+\csdef{rdxm@shuffle@1@C}{A} \csdef{rdxm@shuffle@2@C}{D} \csdef{rdxm@shuffle@3@C}{B}
+\csdef{rdxm@shuffle@1@D}{B} \csdef{rdxm@shuffle@2@D}{C} \csdef{rdxm@shuffle@3@D}{A}
+
+\def\@rdxm@choice@random{0}
+\newcommand\rdxm@shuffle@abcd[1]{\csuse{rdxm@shuffle@\@rdxm@choice@random @#1}}
+
+\newcommand*\pickout[1]{%
+ \ifbool{random}{%
+ \exam@set@seed
+ \pgfmathrandominteger\@rdxm@choice@random{1}{3}%
+ %\@rdxm@choice@random
+ \pickoutreal{\rdxm@shuffle@abcd{#1}}%
+ }{%
+ \pickoutreal{#1}%
+ }%
+}
+\newcommand*\pickoutfixed[1]{%
+ \pickoutreal{#1}%
+ \randomfalse
+}
+\newcommand*\pickin[1]{%
+ \ifbool{random}{%
+ \exam@set@seed
+ \pgfmathrandominteger\@rdxm@choice@random{1}{3}%
+ %\@rdxm@choice@random
+ \pickinreal{\rdxm@shuffle@abcd{#1}}%
+ }{%
+ \pickinreal{#1}%
+ }%
+}
+\newcommand*\pickinfixed[1]{%
+ \pickinreal{#1}%
+ \randomfalse
+}
+
+%% ---------------------------------------------------------------------------
+%% Environment abcd for typesetting options of multiple-choice questions
+%% Put them in one, two, or four rows according to the lengths of the choices
+%% ---------------------------------------------------------------------------
+
+\newlength{\rdxm@item@len}
+\newlength{\rdxm@label@len}
+
+\newcommand\rdxm@item@temp{%
+ \unskip\cr\stepcounter{choice}(\Alph{choice})\ %
+}
+\newcommand\rdxm@item@box{%
+ \hfill\egroup\hfill\hbox to \rdxm@item@len\bgroup
+ \stepcounter{choice}(\Alph{choice})\ \ignorespaces
+}
+\newcommand\rdxm@item@par{%
+ \stepcounter{choice}%
+ \def\rdxm@label@text{(\Alph{choice})\ }%
+ \settowidth{\rdxm@label@len}{\rdxm@label@text}%
+ \par \parshape 2 \hanglength \linewidth
+ \dimexpr\hanglength + \rdxm@label@len\relax
+ \dimexpr\linewidth - \rdxm@label@len\relax
+ \rdxm@label@text\ignorespaces
+}
+
+\NewEnviron{abcdreal}{
+ \unskip
+ \setlength{\parindent}{0pt}%
+ \setlength{\parskip}{0pt}%
+ \setcounter{choice}{0}%
+ \let\item=\rdxm@item@temp
+ \settowidth{\rdxm@item@len}{\vbox{\halign{##\hfil\cr\BODY\crcr}}}%
+ \setcounter{choice}{0}%
+ \ifdim\rdxm@item@len>0.486\linewidth
+ \setlength{\rdxm@item@len}{\linewidth}%
+ \let\item=\rdxm@item@par
+ \BODY\par
+ \else
+ \ifdim\rdxm@item@len>.243\linewidth
+ \setlength{\rdxm@item@len}{0.5\linewidth}%
+ \else
+ \setlength{\rdxm@item@len}{0.25\linewidth}%
+ \fi
+ \let\item=\rdxm@item@box
+ \par\bgroup\BODY\hfill\egroup\par
+ \fi
+}
+
+\newcommand\rdxm@item@one@line{%
+ \unskip
+ \ifnumequal{\value{choice}}{0}{}{\hfill}
+ \stepcounter{choice}(\Alph{choice})\ %
+}
+\newcommand\rdxm@item@two@line{%
+ \unskip
+ \ifnumodd{\value{choice}}{&}{\unskip\cr}%
+ \stepcounter{choice}(\Alph{choice})\ %
+}
+
+\NewEnviron{abcd*real}{
+ \unskip
+ \setlength{\parindent}{0pt}%
+ \setlength{\parskip}{0pt}%
+ \setcounter{choice}{0}%
+ \let\item=\rdxm@item@one@line
+ \settowidth{\rdxm@item@len}{\BODY}%
+ \ifdim\rdxm@item@len<0.95\linewidth
+ \setcounter{choice}{0}%
+ \par\bgroup\BODY\hfill\hfill\par\egroup\par
+ \else
+ \setcounter{choice}{0}%
+ \let\item=\rdxm@item@two@line
+ \settowidth{\rdxm@item@len}{\vbox{\halign{##&##\hfil\cr\BODY\crcr}}}%
+ \ifdim\rdxm@item@len<0.975\linewidth
+ \setcounter{choice}{0}%
+ \par\bgroup\nointerlineskip
+ \vbox{\halign to\linewidth{##\hfil\tabskip=0pt plus 1fil&##\hfil\cr\BODY\crcr}}%
+ \egroup\par
+ \else
+ \setcounter{choice}{0}%
+ \let\item=\rdxm@item@par
+ \par\bgroup\BODY\hfill\egroup\par
+ \fi
+ \fi
+}
+
+\ifbool{random}{%
+ \csdef{rdxm@swap@items@1}#1#2#3#4{\item#3\item#4\item#1\item#2}
+ \csdef{rdxm@swap@items@2}#1#2#3#4{\item#2\item#1\item#4\item#3}
+ \csdef{rdxm@swap@items@3}#1#2#3#4{\item#4\item#3\item#2\item#1}
+ \long\def\rdxm@swap@items#1\item#2\item#3\item#4\item#5\@rdxm@stop@mark{%
+ #1\csuse{rdxm@swap@items@\@rdxm@choice@random}{#2}{#3}{#4}{#5}%
+ }
+}{}
+
+\NewDocumentEnvironment{abcd}{+b}{%
+ \ifbool{random}{%
+ \begin{abcdreal}\rdxm@swap@items#1\@rdxm@stop@mark\end{abcdreal}%
+ }{%
+ \begin{abcdreal}#1\end{abcdreal}%
+ }%
+}{}
+\NewDocumentEnvironment{abcd*}{+b}{%
+ \ifbool{random}{%
+ \begin{abcd*real}\rdxm@swap@items#1\@rdxm@stop@mark\end{abcd*real}%
+ }{%
+ \begin{abcd*real}#1\end{abcd*real}%
+ }%
+}{}
+
+%% ---------------------------------------------------------------------------
+%% Use hangindent in enumerate lists, and remove extra vertical space
+%% The itemjoin set space between items in inline enumerate* environment
+%% ---------------------------------------------------------------------------
+
+\setlist[enumerate]{labelindent=0pt,labelsep=0.2em,itemindent=0pt,leftmargin=*,nosep,itemjoin=\quad}
+\setlist[enumerate,1]{label=(\arabic*)}
+\setlist[enumerate,2]{label=(\alph*),widest*=1}
+
+%% ---------------------------------------------------------------------------
+%% Use freealign package to align math formulas in different lines
+%% ---------------------------------------------------------------------------
+
+\AtBeginDocument{%
+ \ifbool{freealign}{\RequirePackage{freealign}}{}%
+}
+
+%% ---------------------------------------------------------------------------
+%% Command for giving points in solutions: \points
+%% ---------------------------------------------------------------------------
+
+\PassOptionsToPackage{tbtags}{amsmath}
+\RequirePackage{amsmath}
+
+\newcommand{\solutionpointstext}[1]{%
+ \textcolor{red}{#1\kern0.15em\pointorpoints{#1}}%
+}
+
+\newcommand{\pointstext}[1]{%
+ \mbox{}\nobreak\hfill$\cdots\cdots$\solutionpointstext{#1}%
+ \par\noindent\ignorespaces
+}
+\newcommand{\pointseqno}[1]{\eqno{\cdots\cdots\text{\solutionpointstext{#1}}}}
+\newcommand{\pointstag}[1]{\tag*{$\cdots\cdots$\solutionpointstext{#1}}}
+
+\newrobustcmd{\points}[1]{%
+ \ifbool{mmode}{%
+ \ifdefstrequal{\tag}{\dft@tag}{\pointseqno{#1}}{\pointstag{#1}}%
+ }{%
+ \pointstext{#1}%
+ }%
+}
+
+%% ---------------------------------------------------------------------------
+%% Use medium-size fractions and operators in both inline and displayed formulas
+%% ---------------------------------------------------------------------------
+
+\AtBeginDocument{%
+ \ifbool{medmath}{\RequirePackage{medmath}}{}%
+}
+
+%% ---------------------------------------------------------------------------
+%% Load more packages, define more commands
+%% ---------------------------------------------------------------------------
+
+\AtBeginDocument{
+ \setlength{\abovedisplayskip}{4pt minus 2pt}
+ \setlength{\belowdisplayskip}{4pt minus 2pt}
+ \setlength{\abovedisplayshortskip}{2pt}
+ \setlength{\belowdisplayshortskip}{2pt}
+}
+
+\setlength\arraycolsep{4pt}
+
+\RequirePackage{diagbox}
+%% 修正 \diagbox 在 array 环境中使用的问题
+\newrobustcmd{\diagboxtwo}[3][]{%
+ \ifbool{mmode}{%
+ \hbox{\let\tabcolsep=\arraycolsep\diagbox[#1]{$#2$}{$#3$}}%
+ }{%
+ \diagbox[#1]{#2}{#3}%
+ }
+}
+\newrobustcmd{\diagboxthree}[4][]{%
+ \ifbool{mmode}{%
+ \hbox{\let\tabcolsep=\arraycolsep\diagbox[#1]{$#2$}{$#3$}{$#4$}}%
+ }{%
+ \diagbox[#1]{#2}{#3}{#4}%
+ }
+}
+
+\RequirePackage{mathtools} % \mathllap 命令,pmatrix* 环境等
+\RequirePackage{extarrows}
+
+\AtBeginDocument{%
+ \let\rdxm@saved@lim=\lim \def\lim{\rdxm@saved@lim\limits}%
+ \let\rdxm@saved@sum=\sum \def\sum{\rdxm@saved@sum\limits}%
+ \let\rdxm@saved@prod=\prod \def\prod{\rdxm@saved@prod\limits}%
+}
+
+\newcommand{\e}{\mathrm{e}}
+\newcommand{\R}{\mathbb{R}}
+
+\DeclareMathOperator{\arccot}{arccot}
+\DeclareMathOperator{\Corr}{\rho}
+\DeclareMathOperator{\Cov}{Cov}
+\DeclareMathOperator{\diag}{diag}
+\DeclareMathOperator{\grad}{grad}
+\DeclareMathOperator{\Prj}{Prj}
+\DeclareMathOperator{\tr}{tr}
+\DeclareMathOperator{\Var}{Var}
+
+\DeclareMathOperator{\diver}{div}
+\let\division=\div
+\let\div=\diver
+
+\newcommand{\diff}{\mathop{}\!\mathrm{d}}
+\newcommand{\dx}{\diff x}
+\newcommand{\dy}{\diff y}
+\def\dz{\diff z} % 不确定命令是否已经定义
+\newcommand{\du}{\diff u}
+\newcommand{\dv}{\diff v}
+\newcommand{\dr}{\diff r}
+\newcommand{\ds}{\diff s}
+\newcommand{\dt}{\diff t}
+\newcommand{\dS}{\diff S}
+% 有些宏包比如 hyperref 会修改 \d 的定义,所以放在 document 开始处
+% 利用 etoolbox 将 \d 定义为健壮命令,以避免在 align 等环境中错误地展开
+\AtBeginDocument{%
+ \let\oldd=\d
+ \renewrobustcmd{\d}{\ifbool{mmode}{\diff}{\oldd}}%
+}
+
+\let\pd=\partial
+\newcommand{\pdf}{\pd f}
+\newcommand{\pdg}{\pd g}
+\newcommand{\pdh}{\pd h}
+\newcommand{\pdl}{\pd l}
+\newcommand{\pdn}{\pd n}
+\newcommand{\pdu}{\pd u}
+\newcommand{\pdv}{\pd v}
+\newcommand{\pdx}{\pd x}
+\newcommand{\pdy}{\pd y}
+\newcommand{\pdz}{\pd z}
+\newcommand{\pdF}{\pd F}
+\newcommand{\pdL}{\pd L}
+\newcommand{\pdP}{\pd P}
+\newcommand{\pdQ}{\pd Q}
+\newcommand{\pdR}{\pd R}
+
+% from mathabx package
+\DeclareFontFamily{U}{mathx}{\hyphenchar\font45}
+\DeclareFontShape{U}{mathx}{m}{n}{<-> mathx10}{}
+\DeclareSymbolFont{mathx}{U}{mathx}{m}{n}
+\DeclareMathAccent{\widebar}{0}{mathx}{"73}
+
+\newcommand{\va}{\vec{a}}
+\newcommand{\vb}{\vec{b}}
+\newcommand{\vc}{\vec{c}}
+\newcommand{\vd}{\vec{d}}
+\newcommand{\ve}{\vec{e}}
+\newcommand{\vi}{\vec{i}}
+\newcommand{\vj}{\vec{j}}
+\newcommand{\vk}{\vec{k}}
+\newcommand{\vn}{\vec{n}}
+\newcommand{\vs}{\vec{s}}
+\newcommand{\vv}{\vec{v}}
+
+\let\ov=\overrightarrow
+
+\let\le=\leqslant
+\let\ge=\geqslant
+
+\let\lb=\{
+\let\rb=\}
+
+\def\T{\mathrm{T}\kern-.5pt}
+
+% 分数线长一点的分数,\wfrac[2pt]{x}{y} 表示左右加 2pt
+% 和前面的 medmath 一样,将代码放在 \AtBeginDocument 里
+\AtBeginDocument{%
+ \newrobustcmd{\wfrac}[3][2pt]{%
+ \frac{\hspace{#1}#2\hspace{#1}}{\hspace{#1}#3\hspace{#1}}%
+ }%
+ \newrobustcmd{\wdfrac}[3][2pt]{%
+ \dfrac{\hspace{#1}#2\hspace{#1}}{\hspace{#1}#3\hspace{#1}}%
+ }%
+ \newrobustcmd{\wtfrac}[3][2pt]{%
+ \tfrac{\hspace{#1}#2\hspace{#1}}{\hspace{#1}#3\hspace{#1}}%
+ }%
+}
+
+% 使用 stix font 中的 white arrows
+\AtBeginDocument{\@ifpackageloaded{fontspec}{%
+ %\IfFileExists{STIX-Regular.otf}{% 在 TeXLive 中无效
+ \IfFileExists{stix.sty}{%
+ \newfontfamily{\@rdxm@stix}{STIX} % stix v1.1
+ }{%
+ \newfontfamily{\@rdxm@stix}{STIXGeneral} % stix v1.0
+ }
+ \newrobustcmd\leftwhitearrow{%
+ \mathrel{\text{\normalfont\@rdxm@stix\symbol{"21E6}}}%
+ }
+ \newrobustcmd\upwhitearrow{%
+ \mathrel{\text{\normalfont\@rdxm@stix\symbol{"21E7}}}%
+ }
+ \newrobustcmd\rightwhitearrow{%
+ \mathrel{\text{\normalfont\@rdxm@stix\symbol{"21E8}}}%
+ }
+ \newrobustcmd\downwhitearrow{%
+ \mathrel{\text{\normalfont\@rdxm@stix\symbol{"21E9}}}%
+ }
+}{%
+ \let \leftwhitearrow = \Leftarrow
+ \let \rightwhitearrow = \Rightarrow
+ \let \upwhitearrow = \Uparrow
+ \let \downwhitearrow = \Downarrow
+}}
+