14

I have a list of almost 300 items in an enumerate environment

\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}

that I want to list in a different order (the third item should be first, the first item should be second, and the second item should be third.) I want the output to look like this:

enter image description here

Is there a way I can pass the argument [3, 1, 2] to some function and have the list typeset in the order automatically instead of doing so manually ? Thank you.

EDIT: I see that we use xpatch to use the old item. However, in my document I have redefined item as follows:

\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
 }

Will Werner's solution still work?

  • 1
    Unless you have a huge list that is disorganized, wouldn't copy and paste be easy here? – dustin Aug 15 '13 at 0:01
  • 1
    I have a list of almost 300 items that I want to sort in a specified way -- I could do it manually, but I'd rather not. Thanks! – WeierstrassSauce Aug 15 '13 at 0:17
  • You mean writing \item[3] This is the third item? You can do that already; but I would not call that 'automatic'! – jon Aug 15 '13 at 1:45
  • Hi Jon, I'm not sure if I understand your suggestion -- I've added an image of what I want the output to look like if that helps clarify. Thanks. – WeierstrassSauce Aug 15 '13 at 1:51
  • I don't think there is a build-in latex function that will do this work for you. But your problem would be none for a external scripts like Perl or so. – Ruben Aug 15 '13 at 1:58
4

Although LaTeX is not the best language for this sort of thing, here is a potential solution. It uses the pgffor package.

First, we load the pgffor package and define a counter that will keep track of how many items there are in our list.

\usepackage{pgffor}
\newcounter{SortListTotal}

Then, we define a command that will store the list items.

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

The first argument of \sortitem is the item's number; the second is the item text.

Now, we define a command to print out the list. This command also resets the counter, ready for a new sorted list.

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

These commands are used as in the following:

\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}

Alternatively, if you want to specify the order after storing the items:

\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

These commands are used as in the following:

\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}
  • So would the poster than have to order all 300 entries? I mean \sortitem{3} .... \sortitem{150} do get them in order? – dustin Aug 15 '13 at 3:53
  • Is there a way I could just pass the array [3, 4, 1, 2] and a program would take care of the reordering without me having to write \sortitem{x} for each? – WeierstrassSauce Aug 15 '13 at 3:55
  • @WeierstrassSauce I was wondering the same since that would still be a lot of work. – dustin Aug 15 '13 at 4:00
  • @WeierstrassSauce -- True, but how is writing an array of 300 numbers easy? What happens if you get the 56th, 249th, and 23rd 'numbers' wrong? – jon Aug 15 '13 at 4:01
  • 2
    @WeierstrassSauce Inserting the braces manually for 300 items is clearly not a worthwhile task; most editors have regular-expression based search-and-replace that can do this automatically for you. – ChrisS Aug 15 '13 at 4:27
10

Here's an implementation that satisfies your needs:

enter image description here

\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}

The entire list is "processed" twice. The first time around, each \item is stored in a number-based macro. The second time around, the items are called one by one, in the order that is defined by the comma-separated list in the optional argument to orderenum.

The advantage with this approach is that you can use the regular \item-interface (and you don't have to use (for example), \myitem{...} to capture the items. Also, a test is done to ensure that no optional argument defaults to a regular list. Technically you can also specify a subset of the items in the list to be printed.


Update: For using amsthm's proof environment inside orderenum, it is required to understand the proof is actually set as a single-item list. As such, it uses \item, which orderenum redefines for capturing its contents. So, we need a strategy around it. Due to the nature of the definition of proof, xpatch provides a means to patch it. The following code should be used as replacement in the above example:

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

Now one can successfully use, say

%...
\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}
%...

enter image description here


Adding an update to your question to the ordered list is not difficult. Here's a MWE that includes your definition of exenumerate from enumerating examples:

enter image description here

\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}
  • Is it possible to have nested environments with this? I am unable to add create a proof environment within an item without it throwing errors. My document is a textbook for which each item is a math problem with a proof environment from the amsthm package. – WeierstrassSauce Aug 15 '13 at 6:57
  • @WeierstrassSauce: I've updated the answer to provide support for nested proofs from amsthm. The changes include moving \let\olditem\item outside the orderenum environment body, using xpatch and performing a patch on \proof to use \olditem instead of \item. – Werner Aug 15 '13 at 7:17
  • What about comment environments from the comment package? – WeierstrassSauce Aug 16 '13 at 7:19
  • Also, have a look at my most recent edit above. – WeierstrassSauce Aug 16 '13 at 7:26
  • @WeierstrassSauce: I've updated my answer to incorporate your exenumerate definition. – Werner Aug 23 '13 at 6:02
3

Here are two simple approaches. One relies on datatool; the other on manually forcing enumerate to print items with whatever number you choose.

\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}

Alternatively, if you want the .csv to be sorted automatically by (say) the 'Order' field, you can add \DTLsort{Order=ascending}{externalcsv}%; that is:

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

... As for how to create a 'numbered' list from the existing enumerate input, there are a variety of ways. Using sed you could take only the enumerate lines (so each one beings \item This is ...), call it input.csv and then do:

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

This should yield output.csv, where each line is

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

But this will not be a sorted list: it assigns the line number according to the actual line number from the input file.

  • Thank you! What you have is unfortunately a little different from what I would like -- I want the items to also be presented in increasing numerical order. Also, I have each entry in the list put already in an enumerate environment -- is there a way the csv generation can occur without modifying this? – WeierstrassSauce Aug 15 '13 at 4:16
  • 1
    @WeierstrassSauce -- do you mean that (basically) you want the output put in the 'correct' order regardless of whatever order the list has been input in (that is, in the .tex file, the list is 'unordered', but the output should be correctly ordered)? – jon Aug 15 '13 at 4:22
  • Exactly, that is right. – WeierstrassSauce Aug 15 '13 at 4:22
  • 1
    @WeierstrassSauce -- I extended the answer a little. I still think a .csv is the way to go (mainly for future 'editability'); but as to how you should create that, there really are a huge variety of ways. I'd use my editor (emacs) or a utility like sed, but that is personal preference and, mainly, limited knowledge about other methods!. – jon Aug 15 '13 at 4:53
1

Others have given answers that show you how to do it in (La)TeX, but given you have 300 entries this is likely to considerably slow document compilation. Here's another possibility.

First, save all your items in a datatool database like this:

\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}

This creates a new file called enumdata.dbtex. (You can change this by modifying the definition of \dbname.) Now instead of editing your items in your .tex file, you can edit them in datatooltk. Note that the above example doesn't produce any text, it just gathers the data and saves it to the .dbtex file. (I've only just uploaded datatooltk to CTAN so it may take a few days to propagate through all the mirrors.)

The .dbtex file created from the above example looks like the below when loaded into datatooltk:

Image of data in datatooltk

You can either move the rows up and down by dragging on the row number button or you can add a new column that you can use as a sort key. For example:

Image of data with new column

The data can now be sorted by datatooltk via the Tools menu. You can edit individual items by double-clicking on the cell which will open the cell editor:

Image of cell editor

Once you've finished, save the database, and now you can display the items in your document like this:

\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}

The document now looks like:

Image of sorted items

Now, when you're working on the rest of your document, you're not slowed down by TeX having to process your unsorted items.

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.