我想抑制列表中的重复条目。列表是用宏定义的逗号分隔的列表,就像我之前在使用或不使用宏对逗号分隔列表进行排序。
我目前的方法是使用包裹datatool
。对我来说最明显的方法似乎是使用\DTLgetlocation
。但是,如果成员不存在,则会产生以下错误(取消注释 MWE 中的注释行即可看到此错误):
XYZ
包数据工具错误:数据库中没有元素duplicates
也许我不应该使用该datatool
包来解决这个问题,而应该采用一种完全不同的方法?
更新:
下面的代码已更新,通过循环(有和没有宏定义列表)进行额外测试。这也需要在宏中\foreach
添加。\dtlexpandnewvalue
\IfIsInDB
代码:
\documentclass{article}
\usepackage[paperheight=15in]{geometry}% so output fits on "one" page
\usepackage{datatool}
\usepackage{pgffor}% Added for expanded test
\newcommand*{\DBKey}{Value}%
\newcommand*{\InitalizeDuplicatesDB}[1]{%
% #1 = name to be used for this duplicate's DB
\DTLifdbexists{#1}%
{\DTLcleardb{#1}}% DB exists, so just clear it
{\DTLnewdb{#1}}% DB does not exist, so create it
}%
\newcommand*{\AddMemberToDB}[2]{%
% #1 = name of DB
% #2 = member to be added to DB
\DTLnewrow{#1}%
\DTLnewdbentry{#1}{\DBKey}{#2}%
}%
\newcommand*{\IfIsInDB}[4]{%
% #1 = name to be used for this duplicate's DB
% #2 = member to check if in DB (gets added if not)
% #3 = code to execute if member is in DB
% #4 = code to execute if member is not in DB
%
\dtlexpandnewvalue% Added in update.
%
\DTLgetlocation{\RowIndex}{\ColIndex}{#1}{#2}%
%row=\RowIndex \quad col=\ColIndex\par
\ifnum\RowIndex>0\relax%
#3%
\else%
#4%
\AddMemberToDB{#1}{#2}
\fi%
}%
\begin{document}
\InitalizeDuplicatesDB{duplicates}
\AddMemberToDB{duplicates}{ABC}
\AddMemberToDB{duplicates}{DEF}
Current db:\par
\DTLdisplaydb{duplicates}
\medskip
\IfIsInDB{duplicates}{ABC}{ABC is a duplicate}{ABC added DB}\par
\IfIsInDB{duplicates}{DEF}{DEF is a duplicate}{DEF added DB}\par
%\IfIsInDB{duplicates}{XYZ}{XYZ is a duplicate}{XYZ added DB}\par
\medskip
Current db (before foreach - no macro):\par
\DTLdisplaydb{duplicates}
%% Following tests added in update.
%\medskip\par\noindent
%Testing with a non-macro defined list (ABC, DEF should not be ``added", and MMM should only be ``added" once):\par
%\foreach \x in {ABC, DEF, ABC, MMM, MMM, JHM}{%
% \IfIsInDB{duplicates}{\x}{\x~is a duplicate}{\x~added DB}\par
%}%
%
%Current db (after foreach - no macro):\par
%\DTLdisplaydb{duplicates}
%
%\medskip\par\noindent
%Testing with a macro defined list: (ABC, DEF should not be "added", and NNN should only be ``added" once)\par
%\newcommand*{\ListMembers}{ABC, XXX, DEF, NNN, NNN, YYY}%
%\foreach \x in \ListMembers {%
% \IfIsInDB{duplicates}{\x}{\x~is a duplicate}{\x~added DB}\par
%}
%
%Current db (after foreach - macro defn):\par
%\DTLdisplaydb{duplicates}
\end{document}
答案1
循环\foreach
在每个步骤中分组执行其代码,因此当必须“全局”更改某些内容时(就像数据库的情况一样),它们就毫无用处了。
这是使用 Werner 提出的补丁的方法;但我使用了不同的命令来修补,以便不改变的功能\DTLgetlocation
:
\documentclass{article}
\usepackage[paperheight=15in]{geometry}% so output fits on "one" page
\usepackage{datatool}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\foreachin}{ m +m } % long arguments
{
\clist_map_inline:nn { #1 } { #2 }
}
\ExplSyntaxOff
%%% Werner's patch
\usepackage{etoolbox}
\makeatletter
% Patch \DTLgetlocation to gobble the error
\let\ErrorFreeDTLgetlocation\DTLgetlocation
\patchcmd{\ErrorFreeDTLgetlocation}% <cmd>
{\PackageError}% <search>
{\@gobbletwo}% <replace>
{}{}% <success><failure>
\def\@@dtlnovalue{\@dtlnovalue}
%%%
\newcommand*{\DBKey}{Value}
\newcommand*{\InitalizeDuplicatesDB}[1]{%
% #1 = name to be used for this duplicate's DB
\DTLifdbexists{#1}%
{\DTLcleardb{#1}}% DB exists, so just clear it
{\DTLnewdb{#1}}% DB does not exist, so create it
}
\newcommand*{\AddMemberToDB}[2]{%
% #1 = name of DB
% #2 = member to be added to DB
\DTLnewrow{#1}%
\DTLnewdbentry{#1}{\DBKey}{#2}%
}
\newcommand*{\IfIsInDB}[4]{% %New definition!
% #1 = name to be used for this duplicate's DB
% #2 = member to check if in DB (gets added if not)
% #3 = code to execute if member is in DB
% #4 = code to execute if member is not in DB
\ErrorFreeDTLgetlocation{\RowIndex}{\ColIndex}{#1}{#2}%
%row=\RowIndex \quad col=\ColIndex\par
\ifx\RowIndex\@@dtlnovalue
#4%
\AddMemberToDB{#1}{#2}%
\else
#3
\fi
}
\makeatother
\begin{document}
\InitalizeDuplicatesDB{duplicates}
\AddMemberToDB{duplicates}{ABC}
\AddMemberToDB{duplicates}{DEF}
Current db:\par
\DTLdisplaydb{duplicates}
\medskip
\IfIsInDB{duplicates}{ABC}{ABC is a duplicate}{ABC added DB}\par
\IfIsInDB{duplicates}{DEF}{DEF is a duplicate}{DEF added DB}\par
\IfIsInDB{duplicates}{XYZ}{XYZ is a duplicate}{XYZ added DB}\par
\medskip
Current db (before foreach - no macro):\par
\DTLdisplaydb{duplicates}
% Following tests added in update.
\medskip\par\noindent
Testing with a non-macro defined list (ABC, DEF should not be ``added", and MMM should only be ``added" once):\par
\foreachin {ABC, DEF, ABC, MMM, MMM, JHM}{%
\IfIsInDB{duplicates}{#1}{#1~is a duplicate}{#1~added DB}\par
}
Current db (after foreach - no macro):\par
\DTLdisplaydb{duplicates}
\end{document}
如您所见,的语法\foreachin
与 ; 非常相似\foreach\x in
,而不是在代码部分\x
使用。#1
变体
如果你希望将列表\foreachin
作为宏传递给
\newcommand{\mylist}{ABC, DEF, ABC, MMM, MMM, JHM}
\foreachin{\mylist}{\IfIsInDB{duplicates}{#1}{#1~is a duplicate}{#1~added DB}}
然后改变的定义\foreachin
:
\ExplSyntaxOn
\NewDocumentCommand{\foreachin}{ m +m } % long arguments
{
\clist_map_inline:on { #1 } { #2 }
}
\cs_generate_variant:Nn \clist_map_inline:nn {o}
\ExplSyntaxOff
完全不同的方法
你可以datatool
使用房产清单的数据类型expl3
。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\InitializeDuplicatesDB}{m}
{
\prop_gclear_new:c { g_grill_#1_prop }
}
\NewDocumentCommand{\AddMemberToDB}{ m m }
% #1 = name of DB
% #2 = member to be added to DB
{
\prop_gput:cnn { g_grill_#1_prop } { #2 } { Value }
}
\NewDocumentCommand{\IfIsInDB}{mmmm}
% #1 = name to be used for this duplicate's DB
% #2 = member to check if in DB (gets added if not)
% #3 = code to execute if member is in DB
% #4 = code to execute if member is not in DB
{
\prop_get:cnNTF { g_grill_#1_prop } { #2 } \l_tmpa_tl
{ #3 }
{ \AddMemberToDB{#1}{#2} #4 }
}
\NewDocumentCommand{\DTLdisplaydb}{m}
{
\prop_map_inline:cn { g_grill_#1_prop } { ##1\par }
}
\ExplSyntaxOff
\begin{document}
\InitializeDuplicatesDB{duplicates}
\AddMemberToDB{duplicates}{ABC}
\AddMemberToDB{duplicates}{DEF}
Current db:\par
\DTLdisplaydb{duplicates}
\medskip
\IfIsInDB{duplicates}{ABC}{ABC is a duplicate}{ABC added DB}\par
\IfIsInDB{duplicates}{DEF}{DEF is a duplicate}{DEF added DB}\par
\IfIsInDB{duplicates}{XYZ}{XYZ is a duplicate}{XYZ added DB}\par
\medskip
Current db (before foreach - no macro):\par
\DTLdisplaydb{duplicates}
\end{document}
然后可以像以前一样定义循环for each
。当然,在实际应用中,您将添加一个参数\AddMemberToDB
(以替换为每个键提供的公共参数Value
)。
现在来谈谈完全不同的事情
如果您真正需要的是维护一个以逗号分隔的列表,并且一次添加一个元素(除非它已经存在),那么您可以使用 clist 数据类型expl3
。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\NewList}{ m }
{
\clist_new:c { g_pg_#1_clist }
}
\NewDocumentCommand{\AddToList}{ m m }
{
\clist_if_in:cnTF { g_pg_#1_clist } { #2 }
{ #2~is~a~duplicate~in~\texttt{#1}\par }
{
\clist_gput_right:cn { g_pg_#1_clist } { #2 }
#2~added~to~\texttt{#1}\par
}
}
\NewDocumentCommand{\ShowList}{ m }
{
List~\texttt{#1}~contains\par
\clist_map_inline:cn { g_pg_#1_clist } { ##1\par }
}
\NewDocumentCommand{\foreachin}{ m m } % long arguments
{
\clist_map_inline:Vn #1 { #2 }
}
\cs_generate_variant:Nn \clist_map_inline:nn {V}
\ExplSyntaxOff
\setlength{\parindent}{0pt}
\begin{document}
\NewList{duplicates} % allocate a new list
\AddToList{duplicates}{ABC}
\AddToList{duplicates}{DEF}
\ShowList{duplicates}
\AddToList{duplicates}{ABC}
\AddToList{duplicates}{XYZ}
\ShowList{duplicates}
\newcommand{\mylist}{ABC, DEF, ABC, MMM, MMM, JHM}
\foreachin{\mylist}{\AddToList{duplicates}{#1}}
\ShowList{duplicates}
\end{document}
输出将是
如果您不关心添加的条目是否重复并且只希望具有唯一性,那么您可以使用更简单的策略:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\NewList}{ m }
{
\clist_new:c { g_pg_#1_clist }
}
\NewDocumentCommand{\AddToList}{ s m m }
{
\IfBooleanTF{#1}
{
\clist_gput_right:cV { g_pg_#2_clist } #3
}
{
\clist_gput_right:cn { g_pg_#2_clist } { #3 }
}
\clist_gremove_duplicates:c { g_pg_#2_clist }
}
\NewDocumentCommand{\ShowList}{ m }
{
List~\texttt{#1}~contains\par
\clist_map_inline:cn { g_pg_#1_clist } { ##1\par }
}
\begin{document}
\NewList{duplicates}
\AddToList{duplicates}{ABC}
\AddToList{duplicates}{DEF}
\ShowList{duplicates}
\AddToList{duplicates}{ABC}
\AddToList{duplicates}{XYZ}
\ShowList{duplicates}
\newcommand{\mylist}{ABC, DEF, ABC, MMM, MMM, JHM}
\AddToList*{duplicates}{\mylist}
\ShowList{duplicates}
\end{document}
输出将是
为了安全起见,宏\AddToList
有一个 * 变体,用于将宏作为第二个参数。
答案2
不确定用法datatool
,但如果你只是想处理逗号分隔的列表,我认为这个包比你需要的要强大得多。以下纯 TeX 生成
$ tex ll5
This is TeX, Version 3.1415926 (Web2C 2010)
(./ll5.tex
[ABC, DEF, ABC, MMM, MMM, JHM] -> [ABC,DEF,MMM,JHM]
[ABC] -> [ABC]
[] -> []
[xx, yyy, xx, yyy, sss] -> [xx,yyy,sss]
[xx] -> [xx]
[] -> []
)
No pages of output.
Transcript written on ll5.log.
显示被删除的重复项,以及测试的一些边缘情况。
\def\removedups#1#2{%take list in #1 remove duplicates into #2
\begingroup
\def\temp##1{}%
\expandafter\xrem#1,\relax
\expandafter\endgroup
\expandafter\def\expandafter#2\expandafter{\temp}}
\def\xrem#1,#2{%
\expandafter\ifx\csname!!#1\endcsname\xrem\else
\expandafter\let\csname!!#1\endcsname\xrem
\edef\temp{\temp,#1}%
\fi
\ifx\relax#2\else
\expandafter\xrem\expandafter#2%
\fi}
\def\llll{ABC, DEF, ABC, MMM, MMM, JHM}
\removedups\llll\aA
\immediate\write20{[\llll] -> [\aA]}
\def\llll{ABC}
\removedups\llll\aB
\immediate\write20{[\llll] -> [\aB]}
\def\llll{}
\removedups\llll\aC
\immediate\write20{[\llll] -> [\aC]}
\removedups{xx, yyy, xx, yyy, sss}\bA
\immediate\write20{[xx, yyy, xx, yyy, sss] -> [\bA]}
\removedups{xx}\bB
\immediate\write20{[xx] -> [\bB]}
\removedups{}\bC
\immediate\write20{[] -> [\bC]}
\bye
答案3
DTLgetlocation
以。。结束
\newcommand*{\DTLgetlocation}[4]{%
% <snip>
\ifx#1\dtlnovalue
\PackageError{datatool}{There is no element `#4' in
database `#3'}{}%
\fi
}
这会产生错误。一种方法是通过修补来避免此错误\DTLgetlocation
,然后在内部执行等效性测试\IfIsInDB
:
\documentclass{article}
\usepackage{datatool}% http://ctan.org/pkg/datatool
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox
\makeatletter
% Patch \DTLgetlocation to gobble the error
\patchcmd{\DTLgetlocation}% <cmd>
{\PackageError}% <search>
{\@gobbletwo}% <replace>
{}{}% <success><failure>
\makeatother
\newcommand*{\DBKey}{Value}%
\newcommand*{\InitalizeDuplicatesDB}[1]{%
% #1 = name to be used for this duplicate's DB
\DTLifdbexists{#1}%
{\DTLcleardb{#1}}% DB exists, so just clear it
{\DTLnewdb{#1}}% DB does not exist, so create it
}%
\newcommand*{\AddMemberToDB}[2]{%
% #1 = name of DB
% #2 = member to be added to DB
\DTLnewrow{#1}%
\DTLnewdbentry{#1}{\DBKey}{#2}%
}%
\makeatletter
\newcommand*{\IfIsInDB}[4]{%
% #1 = name to be used for this duplicate's DB
% #2 = member to check if in DB (gets added if not)
% #3 = code to execute if member is in DB
% #4 = code to execute if member is not in DB
\DTLgetlocation{\RowIndex}{\ColIndex}{#1}{#2}%
%row=\RowIndex \quad col=\ColIndex\par
\expandafter\ifx\RowIndex\@dtlnovalue% No entry/duplicate found...
#4%
\AddMemberToDB{#1}{#2}
\else% ...a duplicate was found
% \ifnum\RowIndex>0\relax%
#3%
% \fi
\fi%
}%
\makeatother
\begin{document}
\InitalizeDuplicatesDB{duplicates}
\AddMemberToDB{duplicates}{ABC}
\AddMemberToDB{duplicates}{DEF}
Current db:
\DTLdisplaydb{duplicates}
\medskip
\IfIsInDB{duplicates}{ABC}{ABC is a duplicate}{ABC added DB}\par
\IfIsInDB{duplicates}{DEF}{DEF is a duplicate}{DEF added DB}\par
\IfIsInDB{duplicates}{XYZ}{XYZ is a duplicate}{XYZ added DB}\par
\medskip
Current db:
\DTLdisplaydb{duplicates}
\end{document}
\PackageError
从到 的补丁\@gobbletwo
(在 中\DTLgetlocation
)会吃掉前两个参数。但是,最后一个空组{}
不会影响输出。此外,\RowIndex
定义为\@dtlnovalue
,我们可以使用它进行比较来检查是否存在现有项(或不存在)。
答案4
这是强力解决方案:修改\IfIsInDB
为使用 a\DTLforeach
遍历数据库并查看是否找到该值。不是很优雅,但似乎有效。
\documentclass{article}
\usepackage[paperheight=15in]{geometry}% so output fits on "one" page
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{datatool}
\usepackage{pgffor}% Added for expanded test
\newcommand*{\DBKey}{Value}%
\newcommand*{\InitalizeDuplicatesDB}[1]{%
% #1 = name to be used for this duplicate's DB
\DTLifdbexists{#1}%
{\DTLcleardb{#1}}% DB exists, so just clear it
{\DTLnewdb{#1}}% DB does not exist, so create it
}%
\newcommand*{\AddMemberToDB}[3]{%
% #1 = name of DB
% #2 = Key of column to add to
% #3 = member to be added to DB
\DTLnewrow{#1}%
\DTLnewdbentry{#1}{#2}{#3}%
}%
\newtoggle{foundInDB}%
\newcommand*{\IfIsInDB}[5]{%
% #1 = name to be used for this duplicate's DB
% #2 = Key of column to check
% #3 = member to check if in DB (gets added if not)
% #4 = code to execute if member is in DB
% #5 = code to execute if member is not in DB
%
\dtlexpandnewvalue% Added in update.
%
\togglefalse{foundInDB}%
\DTLforeach{#1}{\CurrentMember=#2}{%
\IfEq{#3}{\CurrentMember}{% Found member
\global\toggletrue{foundInDB}%
\dtlbreak% Might as well break out of loop
}{% Haven't found it yet, so keep looking....
}%
}%
%\DTLgetlocation{\RowIndex}{\ColIndex}{#1}{#2}%
\iftoggle{foundInDB}{%
#4%
}{%
#5%
\AddMemberToDB{#1}{#2}{#3}%
}%
}%
\begin{document}
\InitalizeDuplicatesDB{duplicates}
\AddMemberToDB{duplicates}{\DBKey}{ABC}
\AddMemberToDB{duplicates}{\DBKey}{DEF}
Current db:\par
\DTLdisplaydb{duplicates}
\medskip
\IfIsInDB{duplicates}{\DBKey}{ABC}{ABC is a duplicate}{ABC added DB}\par
\IfIsInDB{duplicates}{\DBKey}{DEF}{DEF is a duplicate}{DEF added DB}\par
\IfIsInDB{duplicates}{\DBKey}{XYZ}{XYZ is a duplicate}{XYZ added DB}\par
\medskip
Current db (before foreach - no macro):\par
\DTLdisplaydb{duplicates}
% Following tests added in update.
\medskip\par\noindent
Testing with a non-macro defined list (ABC, DEF should not be ``added", and MMM should only be ``added" once):\par
\foreach \x in {ABC, DEF, ABC, MMM, MMM, JHM}{%
\IfIsInDB{duplicates}{\DBKey}{\x}{\x~is a duplicate}{\x~added DB}\par
}%
Current db (after foreach - no macro):\par
\DTLdisplaydb{duplicates}
\medskip\par\noindent
Testing with a macro defined list: (ABC, DEF should not be "added", and NNN should only be ``added" once)\par
\newcommand*{\ListMembers}{ABC, XXX, DEF, NNN, NNN, YYY}%
\foreach \x in \ListMembers {%
\IfIsInDB{duplicates}{\DBKey}{\x}{\x~is a duplicate}{\x~added DB}\par
}
Current db (after foreach - macro defn):\par
\DTLdisplaydb{duplicates}
\end{document}