我想将三个字符串存储在一个变量中,\mydata
如下所示:
\storedata\mydata{one}{two}{three}
然后我想通过以下方式提取第一个/第二个/第三个字符串:
\getdata[1]\mydata % returns one
\getdata[2]\mydata % returns two
\getdata[3]\mydata % returns three
我该如何定义这些命令?
\newcommand\storedata[4] { \newcommand#1[?] ??? }
\newcommand\getdata[?] ???
答案1
\csname
这是...操作的经典任务\endcsname
。您将控制序列定义\data:\string\mydata:1
为第一个参数、\data:\string\mydata:2
第二个参数等。
请注意,我的解决方案使用了您建议的另一种语法,\storedata
因为我们需要知道参数列表在哪里结束。
\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
编辑:您的自我回答表明您只需要具有三个数据参数的宏\storedata
。 然后使用原始的简单实现\ifcase
是:
\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}}
答案2
实现expl3
使用 实现expl3
;请注意,使用姓名而不是储料箱的控制序列。
\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}
你甚至可以使用\getdata[1+1+1]{mydata}
或使用计数器,比如说
\newcounter{acounter} % in the preamble
\setcounter{acounter}{2} % somewhere in the document
\getdata[\value{acounter}]{mydata}
的第二个参数\storedata
应该用括号括起来,因为否则就不可能知道它在哪里结束。
扩展实现expl3
一个简单的扩展,还允许附加到列表,计算项目数并删除最后一项(可选择将其存储在宏中)。
\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}
对于这两种解决方案,也可以说
\getdata[-1]{mydata}
检索最后一个项目;检索-2
倒数第二个项目,依此类推。因此
\getdata[-1]{mydata}\par
\getdata[-2]{mydata}\par
\getdata[-3]{mydata}\par
\getdata[-4]{mydata}
将打印
四
三二一
一个更简单(但不太灵活)的解决方案expl3
当然,还有一种不太灵活的解决方案,我不推荐,因为序列中的数据比标记列表中的数据更易于检索。但是,这是所有可扩展解决方案中最短的\getdata
。
\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}
“经典”的实现
假设您只有三件物品需要存储。
\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}
宏\getdata
是可扩展的,这不是\ifthenelse
基于的方法。
答案3
以下示例使用\ltx@CarNumth
from 包ltxcmds
从组标记列表中选择一个元素。该列表可以存储为简单的宏:
\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}
评论:
- 解 (
\getdata
) 完全可以展开。 - 数字参数还可以包含类似 的表达式
1+2
。 (内部使用 e-TeX 的\numexpr
表达式。)
答案4
该解决方案扩展了回答擦拭:
如果选择器编号超出范围或者数据未知,则会添加错误消息。
项目数量存储在位置 0。
\getdata
也接受否定论点,从列表末尾开始计算。和 wipet 的解决方案一样,项目数量仅限于 TeX 的最大整数值 (2 31 - 1 = 2147483647)。可以使用包来限制此限制
bigintcalc
,但在实践中,哈希表大小等内存限制很可能在此之前就已达到。的数字参数
\getdata
也接受表达式,因为该参数是通过 e-TeX 的传递的\numexpr
。解决方案(
\getdata
)除了错误情况外都是可扩展的。错误消息之类的消息是不可扩展的。只使用不带反斜杠的名称来代替命令序列。
完整示例:
\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}