Building and using a list

This is essentially in expl3 out of the box. The idea is to map the original list; if an item is not in the remove list, add it to a temporary list and, at the end, reset the original list to the temporary one.

I added just some syntactic sugar to name lists, which allows to have many of them without defining new macros.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
 {% #1 = list name, #2 = items
  % allocate a new list or clear an existing one
  \clist_clear_new:c { l_kessels_#1_clist }
  % set the list
  \clist_set:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\subtractlist}{mm}
 {% #1 = original, #2 = items to subtract
  \kessels_list_subtract:nn { #1 } { #2 }
 }

\NewDocumentCommand{\uselist}{mm}
 {% #1 = list name, #2 = output separator
  \clist_use:cn { l_kessels_#1_clist } { #2 }
 }

\clist_new:N \l__kessels_list_temp_clist

\cs_new_protected:Nn \kessels_list_subtract:nn
 {
  % clear the temporary list
  \clist_clear:N \l__kessels_list_temp_clist
  % map the original list
  \clist_map_inline:cn { l_kessels_#1_clist }
   {
    % if the item doesn't appear in the remove list, add it to the temp one
    \clist_if_in:cnF { l_kessels_#2_clist } { ##1 }
     {
      \clist_put_right:Nn \l__kessels_list_temp_clist { ##1 }
     }
   }
  % reconstitute the original list
  \clist_set_eq:cN { l_kessels_#1_clist } \l__kessels_list_temp_clist
 }

\ExplSyntaxOff

\begin{document}

\definelist{original}{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\definelist{remove}{Whisky,Oscar,Romeo,Delta}

\uselist{original}{, }

\subtractlist{original}{remove}

\uselist{original}{, }

\end{document}

enter image description here

One could think to add \copylist to save a copy of a list and \maplist to define actions on the items.

A more complete implementation. The second argument to \maplist is a template taking each item as #1.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
 {% #1 = list name, #2 = items
  % allocate a new list or clear an existing one
  \clist_clear_new:c { l_kessels_#1_clist }
  % set the list
  \clist_set:cn { l_kessels_#1_clist } { #2 }
 }
\NewDocumentCommand{\prependtolist}{mm}
 {
  \clist_put_left:cn { l_kessels_#1_clist } { #2 }
 }
\NewDocumentCommand{\appendtolist}{mm}
 {
  \clist_put_right:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\subtractlist}{mm}
 {% #1 = original, #2 = items to subtract
  \kessels_list_subtract:nn { #1 } { #2 }
 }

\NewDocumentCommand{\uselist}{mm}
 {% #1 = list name, #2 = output separator
  \clist_use:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\copylist}{mm}
 {
  \clist_clear_new:c { l_kessels_#2_clist }
  \clist_set_eq:cc { l_kessels_#2_clist } { l_kessels_#1_clist }
 }

\NewDocumentCommand{\maplist}{mm}
 {
  \cs_set:Nn \__kessels_list_map:n { #2 }
  \clist_map_function:cN { l_kessels_#1_clist } \__kessels_list_map:n
 }

\clist_new:N \l__kessels_list_temp_clist

\cs_new_protected:Nn \kessels_list_subtract:nn
 {
  % clear the temporary list
  \clist_clear:N \l__kessels_list_temp_clist
  % map the original list
  \clist_map_inline:cn { l_kessels_#1_clist }
   {
    % if the item doesn't appear in the remove list, add it to the temp one
    \clist_if_in:cnF { l_kessels_#2_clist } { ##1 }
     {
      \clist_put_right:Nn \l__kessels_list_temp_clist { ##1 }
     }
   }
  % reconstitute the original list
  \clist_set_eq:cN { l_kessels_#1_clist } \l__kessels_list_temp_clist
 }

\ExplSyntaxOff

\begin{document}

\definelist{original}{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\definelist{remove}{Whisky,Oscar,Romeo,Delta}

\uselist{original}{, }

\copylist{original}{changed}

\subtractlist{changed}{remove}

\uselist{changed}{, }

\maplist{changed}{\fbox{#1\vphantom{Ay}} }

\maplist{original}{\fbox{#1\vphantom{Ay}} }

\end{document}

enter image description here


As Andrew said, expl3 has great high-level tools for many things, including list processing. But expl3 or not, the most important thing is to understand what you are doing, and post a minimal working example when you are stuck (your code doesn't have \end{document}).

So, I'll focus on your “second approach.” You initialize the result with a first item which is \@gobble, and expect it to magically go away? Why would it? It doesn't, and that's what \meaning showed you. The idea was probably to finalize the result with something like \xdef\processedlist{\processedlist} so that the \@gobble eats the comma that follows it, but this is very ugly, as \xdef (like \edef) is likely to cause nasty problems when expanding tokens from the real items (stuff like \textbf, \ref, etc.). \protected@xdef would be less problematic, but still, it would expand things in all items for no good reason.

Here, since the items are only made of non-active character tokens, this approach would work, but this will break with more delicate input, has undesirable side effects, so is bad practice IMO. My recommendation would be not to add the \@gobble item at all and simply check whether \processedlist is empty in order to decide whether to prepend a comma to the newly-added item.

That said, I'll give a way to do with the \@gobble at the end with no unwanted side effects. It is a bit longer than the following code but should be slightly faster, as the code for adding items to \processedlist has one less check to do.

Regardless of the technique used to get rid of the first comma, the algorithm is relatively inefficient, because even when the inner loop has found the current item of #1 in #2, it will continue to check all remaining items of #2. expl3 has \prg_break_point: and \prg_break: to avoid this problem and, more importantly, mapping functions that support this kind of loop breaking (e.g., \clist_map_inline:nn and \clist_map_break:).

\documentclass{article}
\usepackage[T1]{fontenc}        % for the '>' character in horizontal mode

\makeatletter
\newif\ifisinlist

\def\subtractlist#1#2{% #1:original list, #2:remove list
  \def\processedlist{}%
  \@for\tempa:=#1\do{%
    \isinlistfalse
    \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
    \ifisinlist
    \else
      \ifx\processedlist\empty
        \expandafter\gdef\expandafter\processedlist\expandafter{\tempa}%
      \else
         \expandafter\g@addto@macro
         \expandafter\processedlist
         \expandafter{\expandafter,\tempa}%
      \fi
    \fi
  }%
}
\makeatother

\setlength{\parindent}{0pt}
\setlength{\parskip}{1.5ex plus 0.5ex minus 0.2ex}

\begin{document}

\section*{Second macro for subtracting two lists}

\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\def\removelist{Whisky,Oscar,Romeo,Delta}

\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlist{\originallist}{\removelist}|\par
\subtractlist{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

\meaning\processedlist

\end{document}

enter image description here

With the \@gobble

As promised, here is a way to do things correctly with the initial \@gobble:

\documentclass{article}
\usepackage[T1]{fontenc}        % for the '>' character in horizontal mode

\makeatletter
\newif\ifisinlist

\def\startingPoint{\@gobble}

\def\subtractlist#1#2{% #1:original list, #2:remove list
  \global\let\processedlist\startingPoint
  \@for\tempa:=#1\do{%
    \isinlistfalse
    \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
    \ifisinlist
    \else
      \expandafter\g@addto@macro
      \expandafter\processedlist
      \expandafter{\expandafter,\tempa}%
    \fi
  }%
  \ifx\processedlist\startingPoint
    \gdef\processedlist{}%
  \else
    % Expand twice so that the \@gobble eats the comma that follows it.
    \xdef\processedlist{%
      \unexpanded\expandafter\expandafter\expandafter{\processedlist}}%
  \fi
}
\makeatother

\setlength{\parindent}{0pt}
\setlength{\parskip}{1.5ex plus 0.5ex minus 0.2ex}

\begin{document}

\section*{Second macro for subtracting two lists}

\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\def\removelist{Whisky,Oscar,Romeo,Delta}

\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlist{\originallist}{\removelist}|\par
\subtractlist{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

\meaning\processedlist

\end{document}

Same output as above.

Note: if you don't understand the following piece of code:

\xdef\processedlist{%
  \unexpanded\expandafter\expandafter\expandafter{\processedlist}}%

it does the same as:

\expandafter\expandafter
\expandafter\gdef
\expandafter\expandafter
\expandafter\processedlist
\expandafter\expandafter
\expandafter{\processedlist}%

P.S.: I find simple expl3 code more readable than piles of \expandafters used to do very simple things.


A different approach with listofitems. In essence, I use the remove-list as the list separators. Thus, they don't show up in a digested list regurgitation, because they were removed as list separators (along with the excess commas).

\documentclass{article}
\usepackage{listofitems}
\newcommand\subtractlists[2]{%
  \expandafter\setsepchar\expandafter{\expandafter,\expandafter/#2}%
  \readlist*\mylist{#1}%
  \foreachitem\z\in\mylist[]{%
    \ifnum\zcnt>1\relax\ifnum\listlen\mylist[\zcnt]=1\relax,\fi\fi
    \foreachitem\zz\in\mylist[\zcnt]{%
      \ifnum\zzcnt>0\relax\zz\fi}}%
}
\begin{document}
\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
List: \originallist

\def\removelist{Whisky||Oscar||Romeo||Delta}
Remove: \removelist

Result:
\subtractlists{\originallist}{\removelist}
\end{document}

enter image description here

If one insists that the remove list be comma separated (rather than ||), a little extra is needed:

\documentclass{article}
\usepackage{listofitems}
\makeatletter
\newcommand\subtractlists[2]{%
  \setsepchar{,}%
  \readlist\remlist{#2}%
  \def\tmp{,/}%
  \foreachitem\z\in\remlist[]{\ifnum\zcnt=1\else\g@addto@macro\tmp{||}\fi
    \expandafter\g@addto@macro\expandafter\tmp\expandafter{\z}}%
  \expandafter\setsepchar\expandafter{\tmp}%
  \readlist*\mylist{#1}%
  \foreachitem\z\in\mylist[]{%
    \ifnum\zcnt>1\relax\ifnum\listlen\mylist[\zcnt]=1\relax,\fi\fi
    \foreachitem\zz\in\mylist[\zcnt]{%
      \ifnum\zzcnt>0\relax\zz\fi}}%
}
\makeatother
\begin{document}
\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
List: \originallist

\def\removelist{Whisky,Oscar,Romeo,Delta}
Remove: \removelist

Result:
\subtractlists{\originallist}{\removelist}
\end{document}

enter image description here