26

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[?] ???
bcp
  • 447
  • 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 05 '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 05 '14 at 17:45
  • @bcp: I've added something to this effect - a guaranteed three-argument list - to my answer. – Werner Dec 05 '14 at 18:24

11 Answers11

21

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}}
wipet
  • 74,238
  • 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
16

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.

egreg
  • 1,121,712
  • 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.)
Heiko Oberdiek
  • 271,626
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.

yannisl
  • 117,160
  • 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 05 '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}. – yannisl Dec 06 '14 at 09: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

Heiko Oberdiek
  • 271,626
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}}
Werner
  • 603,163
  • 2
    Differently from wipet's and my answer, \mydata(1) is not fully expandable; to the contrary, our \getdata macros are fully expandable. – egreg Dec 05 '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).

cfr
  • 198,882
5

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

  • 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 '20 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 '20 at 18:12
  • 1
    Thanks, I did not know that was an option for listofitems. I guess I need to RTFM! – WesH Sep 16 '20 at 18:14
  • @WesH Definitely RTFM. Nested parsing is very powerful. See example at https://tex.stackexchange.com/questions/328432/split-input-at-hyphen-or-space/328575#328575 – Steven B. Segletes Sep 16 '20 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}
John Kormylo
  • 79,712
  • 3
  • 50
  • 120
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}
Kpym
  • 23,002
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}}
bcp
  • 447