设想

设想

设想

我想将学生的作业打包成一个 PDF。学生以 PNG 格式提交他们的作业,然后我给他们评分。每个作业都用一个文件夹分隔,并标明DueDate。例如,对于 的作业2020-07-16,有一个名为 的文件夹2020-07-16。该文件夹包含学生的已评分作业。

为了节省空间,我对学生姓名进行了编码,例如,,A10X02。所交作业包括 3 个问题:“简单”,“中级”和“高级”。P33X042020-07-16

在文件夹中2020-07-16,有

  • A10-1.png得分 4.5 (满分 5 分)。

  • X02-1.png得分 5 (满分 5 分)。

  • P33-1.png得分 2.5 (满分 5 分)。

  • X04-1.png得分 3.3 (满分 5 分)。

  • A10-2.png得分为 2 (满分 5 分)。

  • X02-2.png得分为 2 (满分 5 分)。

  • X04-2.png得分 2.3 (满分 5 分)。

  • A10-3.png得分为 1 (满分 5 分)。

  • X02-3.png得分为 2 (满分 5 分)。

  • P33-3.png得分为 3 (满分 5 分)。

  • X04-3.png得分为 4 (满分 5 分)。

正如你所看到的,P33他没有提交问题 2 的作业。在这种情况下,他应该自动获得零分。

用户界面

\subsection用于区分一个截止日期与其他截止日期,而\subsubsection用于区分同一截止日期内的各个问题。

我在下面定义\sx,因为它对应于到期日文件夹。

\let\oldsubsection\subsection
\renewcommand{\subsection}[2][]{\def\sx{#2}\oldsubsection[#1]{#2}}

\sx用法\score如下。

\newcommand\score[2]{%
    % #1 student codename
    % #2 score
    \begin{center}
    \includegraphics{\sx/#1-\arabic{subsubsection}}
    \captionof{figure}{#1: #2}
    \end{center}}

\score接受两个参数:学生编码的姓名和分数。

例如,在主题中Solving With Factorization Method有一项作业是2020-07-16。我需要定义输入文件如下。

\section{Solving With Factorization Method}

\subsection{2020-07-16}

\subsubsection{Simple}
\score{A10}{4.5}
\score{X02}{5}
\score{P33}{2.5}
\score{X04}{3.3}

\subsubsection{Intermediate}
\score{A10}{2}
\score{X02}{2}
% If, for example, P33 does not submit the solution
%  he should  get zero automatically.
% His score cell  on the cumulative table with
% missing homework must be highlighted with a unique color. 
\score{X04}{2.3}


\subsubsection{Advanced}
\score{A10}{1}
\score{X02}{2}
\score{P33}{3}
\score{X04}{4}

问题

我想动态生成以下累积分数表,其中

  • 每个分数单元都超链接到其图形标题,反之亦然。

  • 每个问题编号也超链接到相应的小节,反之亦然。

  • Sub Average分数是根据同一截止日期内的总分数除以总问题数自动计算出来的。

  • Total Average分数也是根据的平均值自动计算的Sub Average

\section{Cumulative Score Table}
\begin{landscape}
\begin{longtable}{|m{20mm}|m{5mm}|*4{m{10mm}|}}\hline
Deadline        & No. & A10     & X02   & P33       & X04 \\\hline\hline
2020-07-16  & 1     & 4.5   & 5         & 2.5       & 3.3\\\hline
                        & 2     & 2         & 2         & \fcolorbox{black}{yellow}{0}  & 2.3\\\hline
                        & 3     & 1         & 2         & 3             & 4\\\hline
Sub Avg         & 2     & 2.5   & 3         & 1.83      & 3.2\\\hline\hline
Total Avg       & 2     & 2.5   & 3         & 1.83      & 3.2\\\hline
\end{longtable}
\end{landscape}

“动态”意味着我必须从手动输入上面的表格中解放出来。怎么做呢?

平均能量损失

\documentclass[demo,12pt]{article}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage[a6paper,hmargin=5mm,top=5mm,bottom=13mm]{geometry}
\usepackage[labelformat=empty]{caption}
\usepackage{longtable}
\usepackage{array}
\usepackage{capt-of}

\let\oldsubsection\subsection
\renewcommand{\subsection}[2][]{\def\sx{#2}\oldsubsection[#1]{#2}}




\newcommand\score[2]{%
    % #1 student codename
    % #2 score
    \begin{center}
    \includegraphics{\sx/#1-\arabic{subsubsection}}
    \captionof{figure}{#1: #2}
    \end{center}}

\usepackage{pdflscape}
\usepackage[colorlinks]{hyperref}

\begin{document}


\section{Cumulative Score Table}
\begin{landscape}
\begin{longtable}{|m{20mm}|m{5mm}|*4{m{10mm}|}}\hline
Deadline        & No. & A10     & X02   & P33       & X04 \\\hline\hline
2020-07-16  & 1     & 4.5   & 5         & 2.5       & 3.3\\\hline
                        & 2     & 2         & 2         & \fcolorbox{black}{yellow}{0}  & 2.3\\\hline
                        & 3     & 1         & 2         & 3             & 4\\\hline
Sub Avg         & 2     & 2.5   & 3         & 2.83      & 3.2\\\hline\hline
Total Avg       & 2     & 2.5   & 3         & 2.83      & 3.2\\\hline
\end{longtable}
\end{landscape}

\section{Solving With Factorization Method}

\subsection{2020-07-16}

\subsubsection{Simple}
\score{A10}{4.5}
\score{X02}{5}
\score{P33}{2.5}
\score{X04}{3.3}

\subsubsection{Intermediate}
\score{A10}{2}
\score{X02}{2}
% If, for example, P33 does not submit the solution
%  he should  get zero automatically.
% His score cell  on the cumulative table with
% missing homework must be highlighted with a unique color. 
\score{X04}{2.3}


\subsubsection{Advanced}
\score{A10}{1}
\score{X02}{2}
\score{P33}{3}
\score{X04}{4}

\end{document}

答案1

完整的示例相当长,我确信有优化空间,只\printScoreTable新提供了一个用户命令。

  • 的语法\score没有改变。
  • 为了修补分段命令,titlesec需要加载包。
  • 实现可以自然地分为几个部分:
    • 将信息写入辅助设备,
    • 当 aux 输入时收集信息\begin{document}
    • 打印分数表和单元格格式化程序,
    • 最后处理 aux 输入的情况\end{document}
  • 除了您的要求外,
    • 每个练习的问题数量是自动计算的;
    • 学生代号的完整列表是自动累积的;
    • 检测重复练习、同一练习下的重复题目、同一题目下的重复学生;
    • 每道题的学生输入顺序没有限制,比如可以先输入\score{A01}{...} \score{A02}{...}一道题,再输入\score{A02}{...} \score{A01}{...}另一道题。
    • 不同的链接颜色用于链接到练习、问题和图形标题。
    • 与要打印的分数单元总数相比,时间和空间要求都是线性的

一些自我对话:LaTeX 可以进行编程和数据处理,但不是很擅长。在一般情况下以及在更复杂的情况下,我的建议是,

  • 在 LaTeX 中定义适当的标记命令,
  • 然后使用另一种脚本语言(例如 Python)来处理数据并tex利用这些标记命令输出文件,
  • 最后将tex文件输入 LaXeX 并生成 PDF 输出。
\documentclass[demo,12pt]{article}

% normal packages in lexicographical order
\usepackage{array}
%\usepackage[labelformat=empty]{caption}
\usepackage{caption}
\usepackage[a6paper,hmargin=5mm,top=5mm,bottom=13mm]{geometry}
\usepackage{graphicx}
\usepackage{longtable}
\usepackage[explicit]{titlesec}
\usepackage{xcolor}

% special package
\usepackage[colorlinks]{hyperref}

\ExplSyntaxOn
\makeatletter

% use uniform prefix "cst", cumulative score table

%%
%% write info to aux
%%
\titleformat{\subsection}
  {\normalfont\large\bfseries}{\thesubsection}{1em}
  { % before code
    \immediate\write\@auxout{\string\cst@record@exercise{#1}}
    \gdef\cst@current@exercise{#1}
    #1
  }
  [ % after code
    \label{sec:exer#1}
  ]

\titleformat{\subsubsection}
  {\normalfont\normalsize\bfseries}{\thesubsubsection}{1em}
  { % before code
    \immediate\write\@auxout{
      \string\cst@record@problem{\cst@current@exercise}{\number\c@subsubsection}
    }
    \xdef\cst@current@problem{\number\c@subsubsection}
    #1
  }
  [
    \label{sec:exer\cst@[email protected]\cst@current@problem}
  ]

\let\cst@current@exercise=\@empty
\let\cst@current@problem =\@empty


%%
%% user interface
%%

% #1 student
% #2 score
\newcommand\score[2]{
  % write \cst@record@score{<exercise>}{<problem>}{<student>}{<score>} to auxhy
  \immediate\write\@auxout{
    \string\cst@record@score
      {\cst@current@exercise}{\cst@current@problem}{#1}{#2}
  }
  \begin{center}
    % image path: ./<exercise>/<student>-<problem>.png
    \includegraphics{\cst@exercise/#1-\arabic{subsubsection}}    
    \captionof{figure}{#1:~ #2}\label{fig:exer\cst@[email protected]\cst@[email protected]#1}
  \end{center}
}


%%
%% collect info when aux is input by \begin{document}
%%
\clist_new:N \l_cst_exercise_clist
\clist_new:N \l_cst_student_clist

\tl_new:N \l_cst_table_tl
\fp_new:N \l_cst_score_temp_fp
\int_new:N \l_cst_problems_count_int
\int_new:N \l_cst_total_problems_count_int

% #1 = exercise
\newcommand{\cst@record@exercise}[1]{
  % TODO: use \tl_if_exist:cTF?
  \ifcsname cst.exer#1\endcsname
    \PackageError{cst}
      {Exercise~ with~ due~ date~ "#1"~ multiply~ specified}{}
  \else
    \tl_new:c {cst.exer#1}
    % a problem clist per exercise
    % this allows different exercises having different number of problems
    \clist_new:c {l_cst_exer#1_problem_clist}
    \clist_gput_right:Nn \l_cst_exercise_clist {#1}
  \fi
}

% #1 = exercise
% #2 = problem
\newcommand{\cst@record@problem}[2]{
  \ifcsname cst.exer#1.prob#2\endcsname
    \PackageError{cst}
      {Problem~ "#2"~ under~ Exercise~ "#1"~ multiply~ specified}{}
  \else
    \tl_new:c {cst.exer#1.prob#2}
    \clist_gput_right:cn {l_cst_exer#1_problem_clist} {#2}
  \fi
}

% #1 = exercise due date
% #2 = problem serial number
% #3 = student codename
% #3 = score
\newcommand{\cst@record@score}[4]{
  % record a student list in \l_cst_student_clist, without duplicates
  \ifcsname cst.stud#3\endcsname
  \else
    \tl_new:c {cst.stud#3}
    \clist_gput_right:Nn \l_cst_student_clist {#3}
  \fi
  
  \ifcsname cst.exer#1.prob#2.stud#3\endcsname
    \PackageError{cst}
      {Score~ for~ student~ "#3'',~ Problem~ "#2",~ Exercise~ "#1"~ multiply~ specified}{}
  \else
    \tl_new:c {cst.exer#1.prob#2.stud#3}
    \tl_gset:cn {cst.exer#1.prob#2.stud#3} {#4}
  \fi
}


%%
%% print score table
%%
\cs_new:Npn \printScoreTable
  {
    % store table environment
    \tl_clear:N \l_cst_table_tl

    % for every student, create two fp
    \clist_map_inline:Nn \l_cst_student_clist
      {
        % sum of scores for all exercises
        \fp_new:c {l_cst_stud##1_fp}
        % sum of scores per exercise
        \fp_new:c {l_cst_stud##1_per_exercise_fp}
      }
    
    % table begin
    \tl_put_right:Nn \l_cst_table_tl { \begin{longtable} }
    
    % table preamble
    \tl_put_right:Nx \l_cst_table_tl 
      { {|m{20mm}|m{5mm}|*{\clist_count:N \l_cst_student_clist}{m{10mm}|}} }
    
    % table first row
    \tl_put_right:Nn \l_cst_table_tl { \hline }
    \tl_put_right:Nn \l_cst_table_tl { Deadline & No. }
    \clist_map_inline:Nn \l_cst_student_clist
      {
        \tl_put_right:Nn \l_cst_table_tl { & ##1 }
      }
    \tl_put_right:Nn \l_cst_table_tl { \\ \hline\hline }
    
    % for every exercise
    % convention: 
    % ##1: current exercise, ####1: current problem, \l_cst_curr_student_tl
    \clist_map_inline:Nn \l_cst_exercise_clist
      {
        % init per exercise accumulator
        \clist_map_variable:NNn \l_cst_student_clist \l_cst_curr_student_tl
          {
            \fp_zero:c {l_cst_stud \l_cst_curr_student_tl _per_exercise_fp}
          }
        
        % get number of problems in current exercise
        \int_set:Nn \l_cst_problems_count_int
          { \clist_count:c {l_cst_exer##1_problem_clist} }
        % and add it to total count of problems
        \int_add:Nn \l_cst_total_problems_count_int
          { \l_cst_problems_count_int }

        \cst_print_exercise_name:n { ##1 }
        
        % for every problem
        \clist_map_inline:cn {l_cst_exer##1_problem_clist}
          {
            \cst_print_problem_name:nn { ##1 } { ####1 }
            
            % for every student
            % use map_variable instead of map_inline, to get rid of ########1
            \clist_map_variable:NNn \l_cst_student_clist \l_cst_curr_student_tl
              {
                \tl_set_eq:Nc \l_cst_curr_score_tl 
                  {cst.exer##1.prob####1.stud \l_cst_curr_student_tl}
                \tl_if_exist:NTF \l_cst_curr_score_tl
                  { % if submitted
                    \fp_add:cn {l_cst_stud \l_cst_curr_student_tl _fp} {\l_cst_curr_score_tl}
                    \fp_add:cn {l_cst_stud \l_cst_curr_student_tl _per_exercise_fp} {\l_cst_curr_score_tl}

                    \cst_print_score:nnxx
                      {##1} {####1}
                      {\l_cst_curr_student_tl} {\l_cst_curr_score_tl}
                  }
                  { % unsubmitted
                    \cst_print_score_unsubmitted:
                  }
              } % end of every student
            \tl_put_right:Nn \l_cst_table_tl { \\ \hline }
          } % end of every problem
        
        % Sub Avg row
        \tl_put_right:Nn \l_cst_table_tl { Sub~ Avg & } % first two cells
        \clist_map_variable:NNn \l_cst_student_clist \l_cst_curr_student_tl
          {
            \fp_set:Nn \l_cst_score_temp_fp 
              {
                round(
                  \fp_use:c {l_cst_stud \l_cst_curr_student_tl _per_exercise_fp} /
                  \l_cst_problems_count_int
                , 2) % round to 2 places
              }

            \tl_put_right:Nn \l_cst_table_tl { & }
            \tl_put_right:Nx \l_cst_table_tl
             {
               \fp_to_decimal:N \l_cst_score_temp_fp
             }
          }
        \tl_put_right:Nn \l_cst_table_tl { \\ \hline }
      } % end of every exercise
    
    % Total Avg row
    \tl_put_right:Nn \l_cst_table_tl { \hline Total Avg & }
    \clist_map_variable:NNn \l_cst_student_clist \l_cst_curr_student_tl
      {
        \fp_set:Nn \l_cst_score_temp_fp
          {
            round(
              \fp_use:c {l_cst_stud \l_cst_curr_student_tl _fp} /
              \l_cst_total_problems_count_int
            , 2)
          }
        
        \tl_put_right:Nn \l_cst_table_tl { & }
        \tl_put_right:Nx \l_cst_table_tl
          {
            \fp_to_decimal:N \l_cst_score_temp_fp
          }
      }
    \tl_put_right:Nn \l_cst_table_tl { \\ \hline }
    
    % table end
    \tl_put_right:Nn \l_cst_table_tl { \end{longtable} }
    
    % print table
    \l_cst_table_tl
  }

%%
%% cell formatter
%%
\cs_new:Nn \cst_print_exercise_name:n
  {
    % exercise is always the first cell in a table row, so no need to put &
    \tl_put_right:Nn \l_cst_table_tl
      { 
        % every table cell is already inside a group, 
        % so the change to \@linkcolor is ensured locel
        \tl_set:Nn \@linkcolor {blue}
        % syntax: \hyperref[<label>]{<text>}
        \hyperref[sec:exer#1]{#1}
      }
  }

% #1 = exercise
% #2 = problem
\cs_new:Nn \cst_print_problem_name:nn
  {
    \tl_put_right:Nn \l_cst_table_tl
      {
        & \tl_set:Nn \@linkcolor {teal}
        \hyperref[sec:exer#1.prob#2]{#2}
      }
  }

% #1 = exercise
% #2 = problem
% #3 = student
% #4 = score
\cs_new:Nn \cst_print_score:nnnn
  {
    \tl_put_right:Nn \l_cst_table_tl
      {
        & \hyperref[fig:exer#1.prob#2.stud#3]{#4}
      }
  }
\cs_generate_variant:Nn \cst_print_score:nnnn {nnxx}

\cs_new:Nn \cst_print_score_unsubmitted:
  {
    \tl_put_right:Nn \l_cst_table_tl
      {
        & \fcolorbox{black}{yellow}{0}
      }
  }

%%
%% handle the situation when aux is input by \end{document}
%%

\AtEndDocument{
  \let\cst@record@exercise=\@gobble
  \let\cst@record@problem=\@gobbletwo
  \let\cst@record@score=\@gobblefour
}

\makeatother
\ExplSyntaxOff


\begin{document}

\section{Cumulative Score Table}
\printScoreTable

\section{Solving With Factorization Method}

\subsection{2020-07-16}

\subsubsection{Simple}
\score{A10}{1}
\score{X02}{5}
\score{P33}{2.5}
\score{X04}{3.3}

\subsubsection{Intermediate}
\score{A10}{2}
\score{X02}{2}
% Here student P33 does not submit the solution.
\score{X04}{2.3}

\subsubsection{Advanced}
\score{A10}{3}
\score{X02}{2}
\score{P33}{3}
\score{X04}{4}

\subsection{2020-08-01}

\subsubsection{Simple}
\score{A10}{4}
% Here student X02 does not submit the solution.
\score{P33}{4.4}
\score{X04}{5}

\subsubsection{Intermediate}
\score{A10}{5}
\score{X02}{1}
\score{P33}{2}
\score{X04}{4}

\end{document}

在此处输入图片描述

答案2

一个更具“创造性的答案”,稍微改变你的分数输入,并假设你总是按照相同的顺序输入每个学生的分数(固定的学生姓名列表),让我得到了这个解决方案:

在输入新子小节的分数时,\scoreN{{4.5,5,2.5,3.3}}我会在外部文件中写入一行新行,用于存储生成的表格。同时,数据工具表中会生成一行。完成所有子小节区域(日期/小节)后,将使用 Sub Avg 值进行计算,DTLmeanforcolumn然后再次在生成的选项卡中写入一行,并在第二个数据工具选项卡中写入一行以存储 Suv Avg 值。完成所有日期后,使用 TotalAvg 选项卡写入生成的选项卡的最后一行以计算总平均值。在文档末尾,只需包含生成的外部文件即可\input{tmpFile.tex}

当然,代码可以进一步优化,但它似乎可以工作。顺便说一句,我强制为未提交解决方案的学生添加零分,并且我还为这种情况绘制了一张图。这样做的想法是,有些学生提交了解决方案,但得分为零 - 所以我希望在单击表格中的链接时看到解决方案。

\documentclass[demo,12pt]{article}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage[labelformat=empty]{caption}
\usepackage{longtable}
\usepackage{array}
\usepackage{capt-of}

\let\oldsubsection\subsection
\renewcommand{\subsection}[2][]{\def\sx{#2}\oldsubsection[#1]{#2}}

\usepackage{tikz} % for foreach, pfgmath...
\usepackage{ifthen}

\usepackage{datatool}

%workaround for unmatching pairs of braces within \immediate
\usepackage{newverbs}
\Verbdef\leftb|{|
\Verbdef\rightb|}|

%list of student names
\def\StudentNames{{"A10","X02","P33","X04"}}
\def\StudentNamesS{A10,X02,P33,X04}

%a temp file to store the generated tab
\newwrite\mytmpfile
\immediate\openout\mytmpfile=tmpFile.tex

%probelm number counter 
\newcounter{NoCounter}
\setcounter{NoCounter}{1}

% #1 list of scores
\newcommand\scoreN[1]{%
    \ifthenelse{\equal{\theNoCounter}{1}}{
        \immediate\write\mytmpfile{\sx\unexpanded{ & \hyperlink}{\sx:\theNoCounter}{\theNoCounter}}
    }{
        \immediate\write\mytmpfile{\unexpanded{ & \hyperlink}{\sx:\theNoCounter}{\theNoCounter}}
    }
    
    \dtlexpandnewvalue
    \DTLnewrow{SubAvgTab}
    \foreach \x [count=\xi from 0] in #1 {
        \begin{center}
        \includegraphics{\sx/\x-\arabic{subsubsection}}
        \pgfmathsetmacro{\StudentName}{\StudentNames[\xi]}
        \captionof{figure}{\StudentName: \x}\hypertarget{\sx-\StudentName-\theNoCounter}{}
        \end{center}
        
        \pgfmathsetmacro{\StudentName}{\StudentNames[\xi]} % id do not know why i have to repeat this here (but without the \StudentName definition is not known)
        \ifthenelse{\equal{\x}{0}}{
            \immediate\write\mytmpfile{
                \unexpanded{ & \fcolorbox{black}{yellow}}
                \leftb\unexpanded{\hyperlink}
                {\sx-\StudentName-\theNoCounter}{0}
                \rightb}
        }{
            \immediate\write\mytmpfile{\unexpanded{ & \hyperlink}{\sx-\StudentName-\theNoCounter}{\x}}
        }
        \DTLnewdbentry{SubAvgTab}{\StudentName}{\x}
    }
    \immediate\write\mytmpfile{\unexpanded{\\\hline}}
    \stepcounter{NoCounter}
}

\newcommand\finishDate{%
    %set problem number counter back
    \setcounter{NoCounter}{1}
    
    \dtlexpandnewvalue
    \DTLnewrow{TotalAvgTab}
    \immediate\write\mytmpfile{Sub Avg\unexpanded{ & }}
    %calc average for each column in SubAvgTab and write them to the tmp file
    \foreach \x in \StudentNamesS {
        \DTLmeanforcolumn{SubAvgTab}{\x}{\menaX}
        \immediate\write\mytmpfile{\unexpanded{ & \pgfmathprintnumber[fixed,precision=2]}{\menaX}}
        \DTLnewdbentry{TotalAvgTab}{\x}{\menaX}
    }
    \immediate\write\mytmpfile{\unexpanded{\\\hline\hline}}
    %clear SubAvgTab
    \DTLcleardb{SubAvgTab}
}

\newcommand\finishTotal{%
    \immediate\write\mytmpfile{\unexpanded{\hline\hline}Total Avg\unexpanded{ & }}
    %calc average for each column in SubAvgTab
    \foreach \x in \StudentNamesS {
        \DTLmeanforcolumn{TotalAvgTab}{\x}{\menaX}
        \immediate\write\mytmpfile{\unexpanded{ & \pgfmathprintnumber[fixed,precision=2]}{\menaX}}
    }
    \immediate\write\mytmpfile{\unexpanded{\\\hline\hline}}
}

\usepackage{pdflscape}
\usepackage[colorlinks]{hyperref}

\begin{document}
\DTLnewdb{SubAvgTab}
\DTLnewdb{TotalAvgTab}

\section{Solving With Factorization Method}

\subsection{2020-07-16}

\subsubsection{Simple}\hypertarget{\sx:\theNoCounter}{}
\scoreN{{4.5,5,2.5,3.3}}

\subsubsection{Intermediate}\hypertarget{\sx:\theNoCounter}{}
\scoreN{{2,2,0,2.3}}

\subsubsection{Advanced}\hypertarget{\sx:\theNoCounter}{}
\scoreN{{1,2,3,4}}

\finishDate
\subsection{2020-07-23}

\subsubsection{Simple}\hypertarget{\sx:\theNoCounter}{}
\scoreN{{2.5,4,3.5,4}}

\subsubsection{Intermediate}\hypertarget{\sx:\theNoCounter}{}
\scoreN{{0,4,3,3}}

\finishDate

\section{Generated Cumulative Score Table}
\finishTotal
\immediate\closeout\mytmpfile % write the tmp file

\begin{landscape}
\begin{longtable}{|m{20mm}|m{5mm}|*4{m{10mm}|}}\hline
Deadline        & No. & A10     & X02   & P33       & X04 \\\hline\hline
\input{tmpFile.tex}
\end{longtable}
\end{landscape}

\end{document}

答案3

关于此解决方案

  • 本方案采用Lua实现,因此编译器必须是LuaTeX。

  • 基本上,Lua 中一个table名为的对象student_info会跟踪所有学生信息。例如,如果写:

    \subsection{2020-07-16}
    \subsubsection{Simple}
    \score{A10}{4.5}
    

    那么 的值student_info["2020-07-16"]["Simple"]["A10"]就是4.5。大部分代码负责转换和格式化此表以供输出。

  • 表格是用generate_tex_table()函数构造的,每次文档编译时都会将构造好的表格保存到 中\jobname.mytable,可以使用命令来显示表格\printtable,命令的本质就是\jobname.mytable

    % update the table when document finishes
    \makeatletter
    \AtEndDocument{
      \directlua{
          out = io.open("\jobname.mytable", "w")
          out:write(generate_tex_table())
          io.close(out)
      }
    }
    \makeatother
    % try to retrieve the table generated from last run
    \newcommand{\printtable}{
      \InputIfFileExists{\jobname.mytable}
    }
    
  • 一组format_函数控制数据在输出中的格式。

来源

\documentclass{article}
\usepackage{array}
\usepackage{newtxtext, newtxmath}
\usepackage{expl3}
\usepackage{luacode}
\usepackage{xcolor}
\usepackage{longtable}
\usepackage{float}
\usepackage{graphicx}
\usepackage{datetime2}
\usepackage[colorlinks]{hyperref}

\begin{document}

\directlua{
    % debug only
    % https://github.com/kikito/inspect.lua
    % inspect = require('inspect')
    
    subsection_name = ''
    subsubsection_name = ''
}

\begin{luacode*}
    student_info = {}
    
    function table_get(tb, key, default)
        if (tb[key] == nil) then
            tb[key] = default
        end
        return tb[key]
    end
    
    function get_table_size(t)
        local count = 0
        for _, __ in pairs(t) do
            count = count + 1
        end
        return count
    end
    
    -- using `get_table_size` to track the order of occurrence
    function append_student_info(name, score)
        local subsec_tb = table_get(student_info, subsection_name, {{}, get_table_size(student_info)})
        local subsubsec_tb = table_get(subsec_tb[1], subsubsection_name, {{}, get_table_size(subsec_tb[1])})
        subsubsec_tb[1][name] = tonumber(score)
    end
    
    -- sort keys by occurrence order
    function get_sorted_keys(tb)
        local arr = {}
        for key, val in pairs(tb) do
            local seq = val[2]
            arr[seq + 1] = key
        end
        return arr
    end
    
    function _get_all_student_name(tb, set)
        for key, val in pairs(tb) do
            if (type(val) == "table") then
                if (type(val[1]) == "table") then
                    _get_all_student_name(val[1], set)
                end
            elseif (type(val) == "number") then
                set[key] = true
            end
        end
    end
    
    -- get all student names in sorted order
    function get_all_student_name(tb)
        local set = {}
        _get_all_student_name(tb, set)
        
        local lst = {}
        for key, val in pairs(set) do
            table.insert(lst, key)
        end
        
        table.sort(lst)
        return lst
    end
    
    -- get tex label of something
    function get_label(l)
        return table.concat(l, "-")
    end
    
    -- format a score
    function format_score(ops)
        local score = ops[1]
        
        local no_img = false
        if (score == nil) then
            no_img = true
            score = 0.0
        end
        
        
        local num_str = string.format("%2.2f", score)
        local tex_str = ""
        
        if (math.abs(score) < 0.01) then
            tex_str = string.format("\\fcolorbox{black}{yellow}{%s}", num_str)
        else
            tex_str = num_str
        end
        
        if no_img or ops["no_link"] then
            return tex_str
        else
            local label = get_label{"fig", ops[2], ops[3], ops[4]}
            return [[\def\@linkcolor{red}]] .. string.format("\\hyperref[%s]{%s}", label, tex_str)
        end
    end
    
    function format_deadline(ops)
        local label = get_label{"subsec", ops[1]}
        return [[\def\@linkcolor{blue}]] .. string.format("\\hyperref[%s]{%s}", label, ops[1])
    end
    
    function format_no(ops)
        local label = get_label{"subsubsec", ops[2], ops[3]}
        return [[\def\@linkcolor{green}]] .. string.format("\\hyperref[%s]{%s}", label, ops[1])
    end
    
    function get_sub_average(subsec_name, student_name, subsubsec_names)
        local sum = 0.0
        local count = 0
        local tb = student_info[subsec_name][1]
        for ind, subsubsec_name in pairs(subsubsec_names) do
            local score = tb[subsubsec_name][1][student_name]
            if (score ~= nil) then
                sum = sum + score
            end
            count = count + 1
        end
        return sum / count
    end
    
    function get_total_average(student_name, subsec_names)
        local sum = 0.0
        local count = 0
        
        for _, subsec_name in pairs(subsec_names) do
            local subsec_tb = student_info[subsec_name]
            local subsubsec_names = get_sorted_keys(subsec_tb[1])
            for ind, subsubsec_name in pairs(subsubsec_names) do
                local score = subsec_tb[1][subsubsec_name][1][student_name]
                if (score ~= nil) then
                    sum = sum + score
                end
                count = count + 1
            end
        end
        
        return sum / count
        
    end
    
    function generate_tex_table()
        local student_names = get_all_student_name(student_info)
        
        local rows = {}
        
        local row = {"Deadline", "No."}
        for _, student_name in pairs(student_names) do
            table.insert(row, student_name)
        end
        table.insert(rows, row)
        
        local subsec_names = get_sorted_keys(student_info)
        for _, subsec_name in pairs(subsec_names) do
            local subsec_tb = student_info[subsec_name]
            local subsubsec_names = get_sorted_keys(subsec_tb[1])
            
            for ind, subsubsec_name in pairs(subsubsec_names) do
                local row = nil
                if (ind == 1) then
                    row = {format_deadline{subsec_name}, format_no{ind, subsec_name, subsubsec_name}}
                else
                    row = {'', format_no{ind, subsec_name, subsubsec_name}}
                end
                
                local subsubsec_tb = subsec_tb[1][subsubsec_name]
                for _, student_name in pairs(student_names) do
                    table.insert(row, format_score{subsubsec_tb[1][student_name], subsec_name, subsubsec_name, student_name})
                end
                
                table.insert(rows, row)
            end
            
            local row = {"Sub Avg", ""}
            for _, student_name in pairs(student_names) do
                table.insert(row, format_score{get_sub_average(subsec_name, student_name, subsubsec_names), no_link=true})
            end
            table.insert(rows, row)
        end
        
        row = {"Total Avg", ""}
        for _, student_name in pairs(student_names) do
            table.insert(row, format_score{get_total_average(student_name, subsec_names), no_link=true})
        end
        table.insert(rows, row)
        
        
        -- construct tex string
        local n_cols = get_table_size(rows[1])
        local table_fmt_tmp = {"m{20mm}", "m{5mm}"}
        for i = 3,n_cols do
            table.insert(table_fmt_tmp, "m{10mm}")
        end
        local table_fmt = "|" .. table.concat(table_fmt_tmp, "|") .. "|"
        local tex_str = "\\makeatletter\n\\begin{longtable}{" .. table_fmt .. "} \\hline \n"
        
        for _, row in pairs(rows) do
            local row_str = table.concat(row, " & ") .. "\\\\ \\hline \n"
            tex_str = tex_str .. row_str
        end
        
        tex_str = tex_str .. "\\end{longtable}\n\\makeatother\n"
        
        return tex_str
    end
    
\end{luacode*}


\newcommand{\score}[2]{
    \directlua{append_student_info("\luaescapestring{#1}", "\luaescapestring{#2}")}
    \def\imgfilename{\directlua{tex.print(subsection_name)}/#1-\arabic{subsubsection}}
    \IfFileExists{\imgfilename}{
        \begin{figure}[H]
            \centering
            \includegraphics{\imgfilename}
            \caption{#1: #2}
            \label{\directlua{tex.print(get_label{"fig", subsection_name, subsubsection_name, "\luaescapestring{#1}"})}}
        \end{figure}
    }{
        % this is for testing
        % maybe raise error if not found?
        \begin{figure}[H]
            \centering
            \includegraphics[width=0.4\linewidth]{example-image}
            \caption{#1: #2}
            \label{\directlua{tex.print(get_label{"fig", subsection_name, subsubsection_name, "\luaescapestring{#1}"})}}
        \end{figure}
    }
}

% reset section commands
\let\oldsubsection\subsection
\let\oldsubsubsection\subsubsection


\renewcommand{\subsection}[2][]{
    \directlua{
        subsection_name="\luaescapestring{#2}"
        local subsec_tb = table_get(student_info, subsection_name, {{}, get_table_size(student_info)})
    }
    \oldsubsection[#1]{#2}
    \label{\directlua{tex.print(get_label{"subsec", subsection_name})}}
}
\renewcommand{\subsubsection}[2][]{
    \directlua{
        subsubsection_name="\luaescapestring{#2}"
        local subsec_tb = table_get(student_info, subsection_name, {{}, get_table_size(student_info)})
        local subsubsec_tb = table_get(subsec_tb[1], subsubsection_name, {{}, get_table_size(subsec_tb[1])})
    }
    \oldsubsubsection[#1]{#2}
    \label{\directlua{tex.print(get_label{"subsubsec", subsection_name, subsubsection_name})}}
}


% update the table when document finishes
\makeatletter
\AtEndDocument{
    \directlua{
        out = io.open("\jobname.mytable", "w")
        out:write(generate_tex_table())
        io.close(out)
    }
}
\makeatother
% try to retrieve the table generated from last run
\newcommand{\printtable}{
    \InputIfFileExists{\jobname.mytable}
}


\printtable


\section{Solving With Factorization Method}

\subsection{2020-07-16}

\subsubsection{Simple}
\score{A10}{4.5}
\score{X02}{5}
\score{P33}{2.5}
\score{X04}{3.3}

\subsubsection{Intermediate}
\score{A10}{2}
\score{X02}{2}
% If, for example, P33 does not submit the solution
%  he should  get zero automatically.
% His score cell  on the cumulative table with
% missing homework must be highlighted with a unique color. 
\score{X04}{2.3}


\subsubsection{Advanced}
\score{A10}{1}
\score{X02}{2}
\score{P33}{3}
\score{X04}{4}


\subsection{2020-07-18}

\subsubsection{Simple}
\score{A10}{2}
\score{X02}{4.3}
\score{P33}{0}
\score{X04}{6.5}

\subsubsection{Intermediate}
\score{A10}{3}
\score{X02}{4}
\score{P33}{2}
\score{X04}{5}


\subsubsection{Advanced}
\score{A10}{4}
\score{X02}{5}
\score{P33}{2}
\score{X04}{1}

\subsubsection{Impossible}


\DTMNow

\end{document}

桌子

(设计灵感来自木子木志Z的答案。看了他的解决方案,我不禁佩服他的 LaTeX 编程技巧有多好……它很漂亮,他绝对值得这个奖励。我唯一担心的是 LaTeX3 浮点运算的精度。不过,对于这种应用来说,它应该没问题。)

luatex 方法

我的想法

以前我非常热衷于 LaTeX 编程,并用 LaTeX 实现了一堆算法。然而,当我的热情开始消退时,我开始思考:“我真的需要在 LaTeX 中实现这个/那个吗“?LaTeX 的问题在于它不是为通用编程而设计的是的,它是图灵完备的,但是在 LaTeX 中实现简单算法的工作量可能会非常巨大。

在您的具体情况下,您尝试直接使用 LaTeX 进行简单的数据处理和文件管理。我尝试使用 Lua 来避免使用 LaTeX3,但事实证明 Lua 也不是实现这些目的的最佳语言:您可能会注意到,我需要编写显式循环块来聚合数组,这在 Python 中是一行代码。事实上,整个Lua代码几乎和LaTeX没有任何关系也就是说,它可以用任何语言来写。

如果我是你,我可能会这么做:

  1. 将学生信息保存在 CSV 文件中(您可以使用 Microsoft Excel)。
  2. 使用Python的pandasnumpy处理数据和os.path管理文件。
  3. 在 Python 中构建 TeX 源代码。借助 Python 强大的字符串库和派拉泰
  4. 将 TeX 源保存到文件并用于subprocess.run调用 TeX 可执行文件来编译该文件。

如果效率是关键,我认为这种方法可以节省大量时间,而且与纯 LaTeX/LuaTeX 解决方案相比,结果将更加可定制。尽管如此,我仍然很享受编写上述代码的过程。希望你喜欢我的选择解决方案

相关内容