自动对枚举环境中的物品进行排序

自动对枚举环境中的物品进行排序

我在枚举环境中有一个包含近 300 个项目的列表

\documentclass{article}
\begin{document}
\begin{enumerate}
\item This is the second item.
\item This is the third item.
\item This is the first item.
\end{enumerate}
\end{document}

我想以不同的顺序列出(第三项应该是第一项,第一项应该是第二项,第二项应该是第三项。)我希望输出如下所示:

在此处输入图片描述

有没有办法将参数 [3, 1, 2] 传递给某个函数并自动按顺序排版列表,而不是手动排版?谢谢。

编辑:我看到我们过去xpatch使用旧的 item。但是,在我的文档中,我重新定义了 item,如下所示:

\newcounter{myenumi}
\renewcommand{\themyenumi}{\textbf{Example \thesection.\arabic{myenumi}.}}
\newenvironment{exenumerate}{%
% stuff for beginning of environment goes here
\setlength{\parindent}{0pt}% don't indent paragraphs
\setcounter{myenumi}{0}% restart numbering
\bigskip% skip a line
\renewcommand{\item}{% new definition of item
\par %start a new line
\medskip
\refstepcounter{myenumi}% advance counter
\noindent \makebox[8em][l]{\themyenumi}% print counter to width of 3em, aligned to left
 }% end of definition of item
 }{% at end of environment
 \par% start new paragraph
 \bigskip% skip a line
 \noindent% don't indent new paragraph
 \ignorespacesafterend% ignore spaces after environment
 }

沃纳的解决方案仍然有效吗?

答案1

尽管 LaTeX 不是处理此类问题的最佳语言,但这里有一个潜在的解决方案。它使用包pgffor

首先,我们加载pgffor包并定义一个计数器,用于跟踪列表中有多少个项目。

\usepackage{pgffor}
\newcounter{SortListTotal}

然后,我们定义一个存储列表项的命令。

\newcommand{\sortitem}[2]{\expandafter\def\csname SortListItem#1\endcsname{#2}\stepcounter{SortListTotal}}

第一个参数\sortitem是项目的编号;第二个参数是项目文本。

现在,我们定义一个命令来打印出列表。此命令还会重置计数器,为新的排序列表做好准备。

\newcommand{\printsortlist}{\foreach\currentlistitem in{1,2,...,\value{SortListTotal}}{\item[\currentlistitem]\csname SortListItem\currentlistitem\endcsname}\setcounter{SortListTotal}{0}}

这些命令的使用方法如下:

\begin{enumerate}
\sortitem{3}{This is the third item.}
\sortitem{4}{This is the fourth item.}
\sortitem{1}{This is the first item.}
\sortitem{2}{This is the second item.}
\printsortlist
\end{enumerate}

或者,如果您想在存储项目后指定顺序:

\makeatletter
\newcounter{SortListTotal}
\newcommand{\sortitem}[1]{\stepcounter{SortListTotal}\expandafter\def\csname SortItem\arabic{SortListTotal}\endcsname{#1}}
\newcommand{\printsortlist}[1]{\@for\currentitem:=#1\do{\item\csname SortItem\currentitem\endcsname}\setcounter{SortListTotal}{0}}
\makeatother

这些命令的使用方法如下:

\begin{enumerate}
\sortitem{This is the third item.}
\sortitem{This is the fourth item.}
\sortitem{This is the first item.}
\sortitem{This is the second item.}
\printsortlist{3,4,1,2}
\end{enumerate}

答案2

这是一个满足您需求的实现:

在此处输入图片描述

\documentclass{article}
\usepackage{environ,etoolbox}% http://ctan.org/pkg/{environ,etoolbox}
\makeatletter
\newcounter{orderenum}\newcounter{listcount}[orderenum]%\newcounter{listtotal}[orderenum]
\let\olditem\item% Store regular \item macro
\NewEnviron{orderenum}[1][\relax]{%
  \stepcounter{orderenum}% New orderenum environment (also resets listcount)
  \def\optarg{#1}% Store optional argument
  \expandafter\ifx\optarg\relax% A normal list
    \enumerate\BODY\endenumerate% Process environment
  \else% A reordered list
    \g@addto@macro{\BODY}{\item\relax\item}% Used to delimit the items; last item identified by \item\relax\item
    \def\item##1\item{% Redefine \item to capture contents
      \def\optarg{##1}%\show\optarg%
      \expandafter\ifx\optarg\relax\else% Last item not reached
        \stepcounter{listcount}% Next item being processed
        \csgdef{orderenum@\theorderenum @\thelistcount}{##1}% Store item in control sequence
        \expandafter\item% Recursively continue processing items
      \fi
    }
    \BODY% Process environment (save items)
    \renewcommand*{\do}[1]{\olditem \csname orderenum@\theorderenum @##1\endcsname}% Print each item in order
    \enumerate\docsvlist{#1}\endenumerate% Process items
  \fi%
}
\makeatother
\begin{document}
\begin{orderenum}
  \item This is the second item.
  \item This is the third item.
  \item This is the first item.
\end{orderenum}
\begin{orderenum}[3,1,2]
  \item This is the second item.
  \item This is the third item.
  \item This is the first item.
\end{orderenum}
\end{document}

整个列表被“处理”两次。第一次,每个\item都存储在基于数字的宏中。第二次,按照 的可选参数中逗号分隔列表定义的顺序逐个调用这些项目orderenum

这种方法的优点是您可以使用常规的\item-​​interface(而不必使用(例如))\myitem{...}来捕获项目。此外,还会进行测试以确保没有可选参数默认为常规列表。从技术上讲,您还可以指定要打印的列表中项目的子集。


更新:对于使用amsthm在的proof环境中orderenum,需要了解proof实际上是设置为单项列表。因此,它使用\itemorderenum重新定义 来捕获其内容。因此,我们需要一个围绕它的策略。由于 定义的性质proofxpatch提供了一种修补方法。应使用以下代码替换上述示例:

\usepackage{environ,xpatch}% http://ctan.org/pkg/{environ,xpatch}
\xpatchcmd{\proof}{\item}{\olditem}{}{}% Patch proof environment to use \olditem

现在可以成功使用,比如说

%...
\begin{orderenum}[3,1,2]
  \item This is the second item.
  \item This is the third item.
    \begin{proof} some proof \end{proof}
  \item This is the first item.
   \begin{proof} another proof \end{proof}
\end{orderenum}
%...

在此处输入图片描述


exenumerate将问题的更新添加到有序列表中并不困难。以下是包含您从枚举示例中定义的 MWE :

在此处输入图片描述

\documentclass{article}
\usepackage{environ,etoolbox}% http://ctan.org/pkg/{environ,etoolbox}
\makeatletter
\newcounter{myenumi}
\renewcommand{\themyenumi}{\textbf{Example \thesection.\arabic{myenumi}.}}
\newenvironment{exenumerate}{%
  % stuff for beginning of environment goes here
  \setlength{\parindent}{0pt}% don't indent paragraphs
  \setcounter{myenumi}{0}% restart numbering
  \bigskip% skip a line
  \renewcommand{\olditem}{% new definition of item
    \par %start a new line
    \medskip
    \refstepcounter{myenumi}% advance counter
    \noindent \makebox[8em][l]{\themyenumi}% print counter to width of 3em, aligned to left
  }% end of definition of item
}{% at end of environment
 \par% start new paragraph
 \bigskip% skip a line
 \noindent% don't indent new paragraph
 \ignorespacesafterend% ignore spaces after environment
}
\newcounter{orderenum}\newcounter{listcount}[orderenum]%\newcounter{listtotal}[orderenum]
\let\olditem\item% Store regular \item macro
\NewEnviron{orderenum}[1][\relax]{%
  \stepcounter{orderenum}% New orderenum environment (also resets listcount)
  \def\optarg{#1}% Store optional argument
  \expandafter\ifx\optarg\relax% A normal list
    \enumerate\BODY\endenumerate% Process environment
  \else% A reordered list
    \g@addto@macro{\BODY}{\item\relax\item}% Used to delimit the items; last item identified by \item\relax\item
    \def\item##1\item{% Redefine \item to capture contents
      \def\optarg{##1}%\show\optarg%
      \expandafter\ifx\optarg\relax\else% Last item not reached
        \stepcounter{listcount}% Next item being processed
        \csgdef{orderenum@\theorderenum @\thelistcount}{##1}% Store item in control sequence
        \expandafter\item% Recursively continue processing items
      \fi
    }
    \BODY% Process environment (save items)
    \renewcommand*{\do}[1]{\olditem \csname orderenum@\theorderenum @##1\endcsname}% Print each item in order
    \exenumerate\docsvlist{#1}\endexenumerate% Process items
  \fi%
}
\makeatother
\begin{document}
\begin{orderenum}
  \item This is the second item.
  \item This is the third item.
  \item This is the first item.
\end{orderenum}
\begin{orderenum}[3,1,2]
  \item This is the second item.
  \item This is the third item.
  \item This is the first item.
\end{orderenum}
\end{document}

答案3

这里有两种简单的方法。一种依赖于datatool;另一种依赖于手动强制enumerate打印您选择的任意数量的项目。

\documentclass{article}

% To create an external .csv file
\usepackage{filecontents}
\begin{filecontents}{\jobname.csv}
Order, Text
3, Third item
1, First item
2, Second item
4, Fourth item
\end{filecontents}

\usepackage{datatool}
\DTLloaddb{externalcsv}{\jobname.csv}

\begin{document}

An example using \verb+datatool+ and an external \verb+.csv+ file:

\begin{enumerate}
\DTLforeach{externalcsv}{\myorder=Order, \mytext=Text}{\item[\myorder.] \mytext}
\end{enumerate}

Or a very simple approach:

\begin{enumerate}
\item[2.] This is the second item.
\item[3.] This is the third item.
\item[1.] This is the first item.
\setcounter{enumi}{3}
\item This is the fourth item.
\end{enumerate}

\end{document}

或者,如果您希望.csv按(比如说)“顺序”字段自动排序,您可以添加\DTLsort{Order=ascending}{externalcsv}%;即:

\begin{enumerate}
\DTLsort{Order=ascending}{externalcsv}%
\DTLforeach{externalcsv}{\myorder=Order, \mytext=Text}{\item[\myorder.]\mytext}
\end{enumerate}

...至于如何从现有enumerate输入创建“编号”列表,有多种方法。使用sed您可以只取枚举行(因此每行都是\item This is ...),将其命名为 input.csv,然后执行以下操作:

sed = input.csv | sed 'N;s/\n\\item/,/' > output.csv

这应该产生output.csv,其中每一行都是

<num>, This is the <num>th item

但这将不是是一个排序列表:它根据输入文件的实际行号分配行号。

答案4

其他人给出了答案,告诉你如何在 (La)TeX 中做到这一点,但考虑到你有 300 个条目,这可能会大大减慢文档编译速度。这是另一种可能性。

首先,将所有物品保存在datatool数据库如下:

\documentclass{article}

\usepackage{morewrites}
\usepackage{etoolbox}
\usepackage{datatool}
\usepackage{probsoln}% for \long@collect@body

\newwrite\tmpwrite

\newcommand{\dbname}{enumdata}
\DTLnewdb{\dbname}

\makeatletter

\def\itemreplacement{%
  \expandafter\@gobble\string\}^^J%
  \string\DTLnewrow{\dbname}^^J%
  \string\DTLnewdbentry{\dbname}{Text}%
  \expandafter\@gobble\string\{%
}%

\bgroup
 \obeyspaces
 \gdef\activespace{ }
\egroup

\newcount\envcount

\long\def\parsecontents#1{%
  \def\thisval{#1}%
  \ifx\thisval\@nnil
    \let\next\relax
  \else
    \ifx\thisval\@empty
    \else
      \ifx#1\par
        \appto\enumcontents{\DTLpar}%
      \else
        \ifx#1\begin
          \advance\envcount by 1\relax
          \appto\enumcontents{#1}%
        \else
          \ifx#1\end
            \advance\envcount by -1\relax
            \appto\enumcontents{#1}%
          \else
            \ifx#1\item
              \ifnum\envcount > 0\relax
                \appto\enumcontents{#1}%
              \else
                \eappto\enumcontents{\itemreplacement}%
              \fi
            \else
              \ifx\thisval\activespace
                \appto\enumcontents{ }%
              \else
                \dtl@ifsingle{#1}%
                {%
                  \appto\enumcontents{#1}%
                }%
                {%
                  \appto\enumcontents{{#1}}%
                }%
              \fi
            \fi
          \fi
        \fi
      \fi
    \fi
    \let\next\parsecontents
  \fi
  \next
}

\newcommand{\gatherenumcontents}[1]{%
  \parsecontents#1\@nil
}

\newenvironment{orderenumerate}%
{%
  \envcount=0\relax
  \def\enumcontents{}%
  \obeyspaces
  \long@collect@body\gatherenumcontents
}%
{%
  \@onelevel@sanitize\enumcontents
  \let\gobble\@gobble
  \immediate\openout\tmpwrite=\jobname.tmp
  \immediate\write\tmpwrite{%
    \string\gobble
    \expandafter\@gobble\string\{\enumcontents
    \expandafter\@gobble\string\}}%
  \immediate\closeout\tmpwrite
  \DTLcleardb{\dbname}%
  \input{\jobname.tmp}%
  \DTLprotectedsaverawdb{\dbname}{\dbname.dbtex}%
}

\makeatother

\begin{document}

\begin{orderenumerate}

\item This is the second item.

\begin{proof}
This is the proof of the second item.
\end{proof}

\item This is the third item. This item has a list:

 \begin{enumerate}
   \item First item.
   \item Second item.
 \end{enumerate}

\begin{proof}
This is the proof of the third item.
\end{proof}

\item This is the first item.

\begin{proof}
This is the proof of the first item.
\end{proof}

\end{orderenumerate}

% force shipout to ensure .dbtex file is written:
\mbox{}\newpage
\end{document}

这将创建一个名为 的新文件enumdata.dbtex。(您可以通过修改 的定义来更改此文件\dbname。)现在,您无需在.tex文件中编辑项目,而是可以在datatooltk。请注意,上述示例不产生任何文本,它只是收集数据并将其保存到文件中.dbtex。(我刚刚上传datatooltk到 CTAN,因此可能需要几天时间才能传播到所有镜像。)

上述示例创建的文件.dbtex加载后如下所示datatooltk

datatooltk 中的数据图像

您可以通过拖动行号按钮来上下移动行,也可以添加可用作排序键的新列。例如:

带有新列的数据图像

datatooltk现在可以通过菜单对数据进行排序Tools。双击单元格即可打开单元格编辑器,从而编辑单个项目:

单元格编辑器图像

完成后,保存数据库,现在您可以像这样显示文档中的项目:

\documentclass{article}

\usepackage{amsthm}
\usepackage{datatool}

% Load data:
\input{enumdata.dbtex}

\begin{document}

\begin{enumerate}\let\DTLpar\par
 \DTLforeach*{\dtllastloadeddb}{\Text=Text}%
 {%
   \item \Text
 }%
\end{enumerate}

\end{document}

文档现在如下所示:

已排序商品的图片

现在,当您处理文档的其余部分时,您的速度不会因为 TeX 处理未分类的项目而减慢。

相关内容