完全不同的方法

完全不同的方法

我想抑制列表中的重复条目。列表是用宏定义的逗号分隔的列表,就像我之前在使用或不使用宏对逗号分隔列表进行排序

我目前的方法是使用包裹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}

相关内容