25

I want to store three strings in a variable \mydata like the following:

\storedata\mydata{one}{two}{three}

Then I want to extract the first/second/third string in the following way:

\getdata[1]\mydata   % returns one
\getdata[2]\mydata   % returns two
\getdata[3]\mydata   % returns three

How can I define these commands?

\newcommand\storedata[4] { \newcommand#1[?] ??? }
\newcommand\getdata[?] ???
  • Can we assume there are always exactly three data items? Do you need the first argument of \getdata to be optional as the LaTeX syntax for [] would suggest? – Joseph Wright Dec 5 '14 at 17:19
  • Yes, I am interested in the most simple solution assuming there are exactly three arguments. As for the optional first argument, this is not necessary. – bcp Dec 5 '14 at 17:45
  • @bcp: I've added something to this effect - a guaranteed three-argument list - to my answer. – Werner Dec 5 '14 at 18:24

11 Answers 11

19

This is classic task for \csname...\endcsname manipulation. You define the control sequence \data:\string\mydata:1 as first parameter, \data:\string\mydata:2 as second parameter etc.

Note that my solution uses another syntax than you suggested for \storedata because we need to know where the list of parameters ends.

\newcount\tmpnum
\def\storedata#1#2{\tmpnum=0 \edef\tmp{\string#1}\storedataA#2\end}
\def\storedataA#1{\advance\tmpnum by1
   \ifx\end#1\else
      \expandafter\def\csname data:\tmp:\the\tmpnum\endcsname{#1}%
      \expandafter\storedataA\fi
}
\def\getdata[#1]#2{\csname data:\string#2:#1\endcsname}

\storedata\mydata{{one}{two}{three}}

A:\getdata[1]\mydata   % returns one
B:\getdata[2]\mydata   % returns two
C:\getdata[3]\mydata   % returns three

\bye

Edit: Your self-answer indicates that you need only the macro \storedata with exactly three data-parameters. Then the simple implementation using \ifcase primitive is:

\def\storedata#1#2#3#4{\def#1{\or#2\or#3\or#4}}
\def\getdata[#1]#2{\ifcase\expandafter#1#2\else\outofrange\fi}
\def\outofrange{\errmessage{\string\getdata: out of range 1..3}}
| improve this answer | |
  • I understand \storedataA, but could you please explain how \storedata works, particularly how the part \storedataA#2\end processes each {..}? – Jonathan Komar Oct 20 '16 at 10:40
15

An implementation with expl3

An implementation with expl3; note that it's easier to use a name instead of a control sequence for the storage bin.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\storedata}{mm}
  {
   \bcp_store_data:nn { #1 } { #2 }
  }

\DeclareExpandableDocumentCommand{\getdata}{O{1}m}
 {
  \bcp_get_data:nn { #1 } { #2 }
 }

\cs_new_protected:Npn \bcp_store_data:nn #1 #2
 {
  % create the sequence if it doesn't exist
  \seq_if_exist:cF { l_bcp_data_#1_seq } { \seq_new:c { l_bcp_data_#1_seq } }
  % populate the sequence
  \seq_set_split:cnn { l_bcp_data_#1_seq } { } { #2 }
 }
\cs_generate_variant:Nn \seq_set_split:Nnn { c }

\cs_new:Npn \bcp_get_data:nn #1 #2
 {
  % retrieve the requested item
  \seq_item:cn { l_bcp_data_#2_seq } { #1 }
 }
\ExplSyntaxOff

\begin{document}

\storedata{mydata}{{one}{two}{three}}

\getdata[1]{mydata}

\getdata[2]{mydata}

\getdata[3]{mydata}

\end{document}

enter image description here

You can even use \getdata[1+1+1]{mydata} or use a counter, say

\newcounter{acounter} % in the preamble

\setcounter{acounter}{2} % somewhere in the document
\getdata[\value{acounter}]{mydata}

The second argument to \storedata should be braced, because otherwise it would be impossible to tell where it ends.

An extended implementation with expl3

A straightforward extension for also allowing appending to the list, counting the items and removing the last item (optionally storing it in a macro).

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\storedata}{mm}
  {
   \bcp_store_data:nn { #1 } { #2 }
  }

\NewDocumentCommand{\appenddata}{mm}
 {
  \bcp_append_data:nn { #1 } { #2 }
 }

\NewExpandableDocumentCommand{\getdata}{O{1}m}
 {
  \bcp_get_data:nn { #1 } { #2 }
 }

\NewExpandableDocumentCommand{\getlength}{m}
 {
  \seq_count:c { l_bcp_data_#1_seq }
 }

\NewDocumentCommand{\removelast}{om}
 {
  \IfNoValueTF { #1 }
   {
    \bcp_remove_last:Nn \l_tmpa_tl { #2 }
   }
   {
    \bcp_remove_last:Nn #1 { #2 }
   }
 }

\cs_new_protected:Npn \bcp_store_data:nn #1 #2
 {
  % create the sequence if it doesn't exist or clear it if it exists
  \seq_clear_new:c { l_bcp_data_#1_seq }
  % append the items
  \__bcp_append_data:nn { #1 } { #2 }
 }

\cs_new_protected:Npn \bcp_append_data:nn #1 #2
 {
  % create the sequence if it doesn't exist, do nothing if it exists
  \seq_if_exist:cF { l_bcp_data_#1_seq }
   { \seq_new:c { l_bcp_data_#1_seq } }
  % append the items
  \__bcp_append_data:nn { #1 } { #2 }
 }

\cs_new_protected:Npn \__bcp_append_data:nn #1 #2
 {
  % append items one at a time
  \tl_map_inline:nn { #2 }
   {
    \seq_put_right:cn { l_bcp_data_#1_seq } { ##1 }
   }
 }

\cs_new:Npn \bcp_get_data:nn #1 #2
 {
  % retrieve the requested item
  \seq_item:cn { l_bcp_data_#2_seq } { #1 }
 }

\cs_new_protected:Nn \bcp_remove_last:Nn
 {
  \seq_pop_right:cN { l_bcp_data_#2_seq } #1
 }

\ExplSyntaxOff

\begin{document}

\storedata{mydata}{{one}{two}}

Length is: \getlength{mydata}

\appenddata{mydata}{{three}{four}}

Length is: \getlength{mydata}

\getdata[1]{mydata}

\getdata[2]{mydata}

\getdata[3]{mydata}

\getdata[4]{mydata}

\removelast{mydata}

Length is: \getlength{mydata}

\removelast[\test]{mydata}

\test % should be 'three'

\end{document}

enter image description here

With both solutions, one can also say

\getdata[-1]{mydata}

to retrieve the last item; with -2 the last but one and so on. So

\getdata[-1]{mydata}\par
\getdata[-2]{mydata}\par
\getdata[-3]{mydata}\par
\getdata[-4]{mydata}

would print

four
three
two
one

A simpler (but less flexible) solution with expl3

Of course there's a much less flexible solution, that I wouldn't recommend, because data in a sequence is better retrievable than in a token list. However, this is the shortest in all presented solutions that have \getdata expandable.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\storedata}{mm}{\tl_set:Nn#1{#2}}
\DeclareExpandableDocumentCommand{\getdata}{O{1}m}{\tl_item:Nn#2{#1}}
\ExplSyntaxOff

\begin{document}

\storedata\mydata{{one}{two}{three}}

\getdata[1]\mydata

\getdata[2]\mydata

\getdata[3]\mydata

\getdata[-1]\mydata

\getdata[-2]\mydata

\getdata[-3]\mydata

\end{document}

A “classical” implementation

This assumes you just have three items to store.

\documentclass{article}

\newcommand\storedata[4]{\def#1{{#2}{#3}{#4}}}

\makeatletter
\providecommand\@firstofthree[3]{#1}
\providecommand\@secondofthree[3]{#2}
\providecommand\@thirdofthree[3]{#3}

\def\getdata[#1]#2{%
  \ifcase#1 \ERROR\or
  \expandafter\@firstofthree#2\or
  \expandafter\@secondofthree#2\or
  \expandafter\@thirdofthree#2\else
  \ERROR\fi}
\makeatother

\begin{document}

\storedata\mydata{one}{two}{three}

\getdata[1]\mydata\par   % returns one
\getdata[2]\mydata\par   % returns two
\getdata[3]\mydata\par   % returns three

\end{document}

The \getdata macro is expandable, which isn't in an \ifthenelse based approach.

| improve this answer | |
  • Your extended expl3 solution is great! Would it be possible to add a method that removes the last (n:th) item from the list and method that returns/expands to number of items in list? – Jarno_C-137 Sep 27 '17 at 15:37
  • 1
    @Jarno_C-137 I did a revamp of it. Removing arbitrary items is more complicated. – egreg Sep 27 '17 at 15:50
10

The following example uses \ltx@CarNumth from package ltxcmds to select an element from a group token list. The list can be stored as simple macro:

\documentclass{article}

\usepackage{ltxcmds}
\makeatletter
\newcommand*{\getdata}[2]{%
  \expandafter\ltx@CarNumth\expandafter{%
    \the\numexpr(#1)\expandafter
  }#2\@nil
}
\makeatother

\newcommand*{\mydata}{{one}{two}{three}}

\newcommand*{\mylongdata}{
  {one} {two} {three} {four} {five} {six} {seven} {eight} {nine} {ten}
  {eleven} {twelve} {thirteen} {fourteen} {fifteen} {sixteen}
  {seventeen} {eighteen} {nineteen} {twenty} {twenty-one} {twenty-two}
  {twenty-three} {twenty-four}
}

\begin{document}
\begin{itemize}
\item \getdata{1}\mydata
\item \getdata{2}\mydata
\item \getdata{3}\mydata
\item \getdata{24}\mylongdata
\end{itemize}
\end{document}

Result

Remarks:

  • The solution (\getdata) is fully expandable.
  • The numeric argument can also contain expressions like 1+2. (Internally e-TeX's \numexpr is used for the expressions.)
| improve this answer | |
8

Here is a LuaLaTeX solution. It is preferable to always keep your data, separately from your code. Also the \data command is preferable to be a name. In the example below is a filename.

\documentclass{article}
    \usepackage{luatextra, filecontents}
    \begin{filecontents*}{numbers.lua}
    local m = m or {}
    m = {
    "one", "two", "three", "four", "five", "six",
    "seven", "eight", "nine", "ten", "eleven",
    "twelve", "thirteen"
    }
    return m
    \end{filecontents*}
    \begin{document}
    \def\getfields[#1]#2{%
      \luadirect{local string = require('string')
        local numbers = require('#2')
        local s = string.split('#1', ',')
        for k,v in pairs(s) do
          tex.print(numbers[tonumber(s[k])]..', ')
       end
    }}

    \getfields[1,3,5,8]{numbers}
    \end{document}

Why I prefer a LuaTeX solution, is that the LuaTeX code is now maturing and offers unlimited opportunities. In the example above, you can structure your data file in any way you want.

| improve this answer | |
  • No problem with the idea of using Lua, but I'd caution athat your first argumetn here is not optional so doesn't follow the usual LaTeX convention. – Joseph Wright Dec 5 '14 at 17:14
  • @JosephWright Thanks for the comment. The first argument was an ugly hack. From a UI point of view provided the data always resides in the same file one needs only \getfields{1,2,3,4}. The beauty of Lua it could even be \getfields{1,...,n}. – Yiannis Lazarides Dec 6 '14 at 9:43
8

This solution extends the answer of wipet:

  • Error messages are added, if the selector number is out of range or the data are unknown.

  • The number of items are stored at position 0.

  • \getdata also accepts negative arguments, which count from the end of the list.

  • As in wipet's solution, the number of items is only limited to TeX's maximum integer value (231 - 1 = 2147483647). This limit could be made unlimited by use of package bigintcalc, but in practice memory limits like the hash table size will be very likely hit before.

  • The numeric argument of \getdata also accepts expressions, because the argument is passed through e-TeX's \numexpr.

  • The solution (\getdata) is expandable except for the error cases. Messages like error messages are not expandable.

  • Just the name without the backslash is used instead of the command sequence.

The full example:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[variablett]{lmodern}

\makeatletter
\newcommand*{\storedata}[2]{%
  \count@=0 %
  \@tfor\@tmp:=#2\do{%
    \advance\count@\@ne
    \expandafter\let\csname data:\the\count@:#1\endcsname\@tmp
  }%
  \expandafter\edef\csname data:0:#1\endcsname{\the\count@}%
}
\newcommand*{\getdata}[2]{%
  \@ifundefined{data:0:#2}{%
    \@latex@error{Undefined data `#2'}\@ehc
  }{%
    \expandafter\@getdata\expandafter{%
      \the\numexpr
        \ifnum\numexpr(#1)<\z@
          \@nameuse{data:0:#2}+1+%
        \fi
        (#1)%
      \relax
    }{#2}{#1}%
  }%
}
\newcommand*{\@getdata}[3]{%
  \ifnum#1<\z@
    \@getdata@error{\the\numexpr(#3)\relax}{#2}%
  \else
    \ifnum#1>\@nameuse{data:0:#2} %
      \@getdata@error{#1}{#2}%
    \else
      \@nameuse{data:#1:#2}%
    \fi
  \fi
}
\newcommand*{\@getdata@error}[2]{%
  \@latex@error{%
    Wrong data selector #1 for `#2',\MessageBreak
    which only contains \@nameuse{data:0:#2} item(s)%
  }\@ehc
}
\makeatother

\storedata{mydata}{{one}{two}{three}}
\storedata{mylongdata}{
  {one} {two} {three} {four} {five} {six} {seven} {eight} {nine} {ten}
  {eleven} {twelve} {thirteen} {fourteen} {fifteen} {sixteen}
  {seventeen} {eighteen} {nineteen} {twenty} {twenty-one} {twenty-two}
  {twenty-three} {twenty-four}
}

\begin{document}
\newcommand*{\test}[2]{%
  \ttfamily #2[#1]: &
  \getdata{#1}{#2}
  \tabularnewline
}
\begin{tabular}{@{}l@{ }l@{}}
  \test{0}{mydata}
  \test{1}{mydata}
  \test{2}{mydata}
  \test{3}{mydata}
  \test{-1}{mydata}
  \test{-2}{mydata}
  \test{-3}{mydata}
  \test{-4}{mydata}
  \hline
  \test{0}{mylongdata}
  \test{1}{mylongdata}
  \test{24}{mylongdata}
  \test{-10}{mylongdata}
\end{tabular}
\end{document}

Result

| improve this answer | |
6

arrayjobx is meant to fulfil this need:

enter image description here

\documentclass{article}
\usepackage{arrayjobx}
\begin{document}

\newarray\mydata
\readarray{mydata}{one&two&three}

\verb|\mydata(1)|: \mydata(1) \par
\verb|\mydata(2)|: \mydata(2) \par
\verb|\mydata(3)|: \mydata(3)
\end{document}

If you will always be using only three items and require the interface you mentioned, then the following will do:

enter image description here

\documentclass{article}
\makeatletter
% http://tex.stackexchange.com/a/42337/5764
\begingroup\lccode`\|=`\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}
\newcommand{\macroname}[1]{\expandafter\removebs\string#1}

\newcommand{\storedata}[4]{%
  \@namedef{\macroname{#1}@1}{#2}% Store first item
  \@namedef{\macroname{#1}@2}{#3}% Store second item
  \@namedef{\macroname{#1}@3}{#4}}% Store third item
\newcommand{\getdata}[2]{\@nameuse{\macroname{#2}@#1}}

\def\getdata[#1]#2{\@nameuse{\macroname{#2}@#1}}
\makeatother

\begin{document}

\storedata{\mydata}{one}{two}{three}

\verb|\getdata[1]\mydata|: \getdata[1]\mydata   % returns one

\verb|\getdata[2]\mydata|: \getdata[2]\mydata   % returns two

\verb|\getdata[3]\mydata|: \getdata[3]\mydata   % returns three

\end{document}

If a more traditional interface like \getdata{2}\mydata is required, then you can use

\newcommand{\getdata}[2]{\@nameuse{\macroname{#2}@#1}}
| improve this answer | |
  • 1
    Differently from wipet's and my answer, \mydata(1) is not fully expandable; to the contrary, our \getdata macros are fully expandable. – egreg Dec 5 '14 at 16:16
6

This makes a very minor adjustment to your own preferred solution, so that you will get some information if you pass, say, \getdata[4].

\documentclass{article}
\usepackage{ifthen}
\newcommand\selectnth[4]{%
  \ifthenelse{\equal{#4}1}{#1}{%
    \ifthenelse{\equal{#4}2}{#2}{%
      \ifthenelse{\equal{#4}3}{#3}{%
        \typeout{\string\selectnth: cannot handle values greater than 3. Please adjust your expectations accordingly.}%
      }%
    }%
  }%
}

\newcommand\storedata[4]{%
  \newcommand#1{%
    \selectnth{#2}{#3}{#4}}}

\newcommand\getdata[2][1]{#2{#1}}
\begin{document}
  \storedata\mydata{one}{two}{three}
  \getdata[1]\mydata   % returns one
  \getdata[2]\mydata   % returns two
  \getdata[3]\mydata   % returns three
  \getdata[4]\mydata   % returns warning
\end{document}

All this does is nest your \ifthenelse conditionals, and add a \typeout message if all conditionals return false. This does not add much complexity, but it may save you from hours searching for the source of a mysterious error later (when you've forgotten what you put in your code - at least, if you are at all like me).

| improve this answer | |
4

REVISED SOLUTION (listofitems package)

What you are asking is exactly what the listofitems package excels at, and much more.

\documentclass{article}
\usepackage{listofitems}
\begin{document}
% DELIMITER CAN BE CHANGED WITH \setsepchar{}, DEFAULT COMMA
% NESTED ITEM LISTS CAN ALSO BE DONE
% * OPTION REMOVES LEADING/TRAILING SPACES
\readlist*\mydata{one, two, three}
\mydata[1]

\mydata[2]

\mydata[3]

The list contains \mydatalen{} items.
\end{document}

enter image description here

ORIGINAL SOLUTION (readarray package)

If you are willing to wrap the data in a group, and space-separate the items, then the \getargsC macro of readarray can do it directily.

\documentclass{article}
\usepackage{readarray}
\begin{document}
\getargsC{{one} {phrase two} {three}}
\narg\ items\par
item2 is \argii, while items 1 and 3 are \argi\ and \argiii.
\end{document}

enter image description here

| improve this answer | |
  • This is an excellent solution which can be enhanced with the multido package. Replace all of your \mydata[n] lines with the single line: \multido{\n=1+1}{\mydatalen{}}{\mydata[\n]\\} for a programmatic solution to the problem. – WesH Sep 16 at 18:07
  • 1
    @WesH Does not need multido. Use listofitems native commands: \foreachitem\n\in\mydata[]{\mydata[\ncnt]\\}. Or more simply, still: \foreachitem\n\in\mydata[]{\n\\} – Steven B. Segletes Sep 16 at 18:12
  • 1
    Thanks, I did not know that was an option for listofitems. I guess I need to RTFM! – WesH Sep 16 at 18:14
  • @WesH Definitely RTFM. Nested parsing is very powerful. See example at tex.stackexchange.com/questions/328432/… – Steven B. Segletes Sep 16 at 18:18
3

If you can accept comma (or almost anything except {}) delimited strings, one can use the xstring package. I added a second argument to \getdata in case you wanted to have more than one array.

\documentclass{article}
\usepackage{xstring}

\newcommand{\mydata}{one,two,three}

\newcounter{comma}
\newcommand{\tempstr}{}% reserve name
\newcommand{\getdata}[2][1]% #1 = index, #2 = array name
{\ifnum#1=1 \StrBefore{#2}{,}[\tempstr]%
\else\setcounter{comma}{#1}\addtocounter{comma}{-1}%
\StrCount{#2}{,}[\tempstr]%
\ifnum\value{comma}=\tempstr\StrBehind[\thecomma]{#2}{,}[\tempstr]%
\else\StrBetween[\thecomma,#1]{#2}{,}{,}[\tempstr]%
\fi\fi\tempstr}

\begin{document}
A:\getdata[1]{\mydata}

B:\getdata[2]{\mydata}

C:\getdata[3]{\mydata}
\end{document}
| improve this answer | |
3

Here is a solution using pgffor :

\documentclass[varwidth,border=7]{standalone}
\usepackage{pgffor}

% the get macro using foreach
\def\get(#1)#2{
  \foreach[count=\i] \element in #2 {
    \ifnum \i = #1 \element\fi
  }
}

\begin{document}
  % store the data
  \newcommand{\data}{one,two,{three, with comma}}

  % use the data
  \begin{enumerate}
    \item \get(1)\data
    \item \get(2)\data
    \item \get(3)\data
  \end{enumerate}
\end{document}

enter image description here

EDIT: Here is another solution using pgfmath parser who defines access to arrays. The following code produce the same result.

\documentclass[varwidth,border=7]{standalone}
\usepackage{pgfmath}

% the get macro using pgfmathparse
\def\get#1{\pgfmathparse{#1}\pgfmathresult}

\begin{document}
  % store the data
  \def\data{{"one","two","three, with comma"}}

  % use the data
  \begin{enumerate}
    \item \get{\data[0]}
    \item \get{\data[1]}
    \item \get{\data[2]}
  \end{enumerate}
\end{document}
| improve this answer | |
1

Thank you for the answers. Guess I was looking for the following simple code:

\newcommand\selectnth[4]{%
\ifthenelse{\equal{#4}1}{#1}{}%
\ifthenelse{\equal{#4}2}{#2}{}%
\ifthenelse{\equal{#4}3}{#3}{}%
}

\newcommand\storedata[4]{%
\newcommand#1{%
\selectnth{#2}{#3}{#4}}}

\newcommand\getdata[2][1]{#2{#1}}
| improve this answer | |
  • What would you like \getdata[4] to do? With this implementation it fails silently. – Matthew Leingang Dec 5 '14 at 20:08
  • Here I assumed that there are exactly three arguments. Indeed, \getdata[4] returns nothing. – bcp Dec 5 '14 at 20:12

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.