6

In this answer we learnt how to remove the n-th element from a comma-separated list.

How can I remove an element from a list by its value, rather than its position? For example: if my global variable \citylist currently has the value derby,leicester,nottingham, then after executing \removecity{leicester} I would like it to have the value derby,nottingham.

I already have an \addcity macro (which I made by adapting a macro by David Carlisle):

\newcommand\addcity[1]{%
  \ifx\citylist\@empty\else\g@addto@macro\citylist{,}\fi
  \g@addto@macro\citylist{#1}
}

2 Answers 2

9

In simple cases, you can use LaTeX's \@removeelement:

\documentclass{article}

\newcommand*{\citylist}{derby,leicester,nottingham}

\makeatletter
\@expandtwoargs\@removeelement{leicester}\citylist\citylist
\makeatother

\typeout{\citylist}

\begin{document}
\end{document}

Result:

derby,nottingham

Simple means, the list is normalized without optional spaces around the comma. The list elements does not contain commas and macros.

A variant with package kvsetkeys:

\documentclass{article}

\usepackage{kvsetkeys}

\makeatletter
\newcommand*{\addcity}[1]{%
  \ifx\citylist\@empty\else\g@addto@macro\citylist{,}\fi
  \g@addto@macro\citylist{#1}
}

\newcommand*{\removecity}[1]{%
  \def\unwanted@city{#1}%
  \edef\citylist{\expandafter}%
  \expandafter\comma@parse\expandafter{\citylist}\@removecity
}
\newcommand*{\@removecity}[1]{%
  \def\param@city{#1}%
  \ifx\param@city\unwanted@city
  \else
    \addcity{#1}%
  \fi
}
\makeatother

\newcommand*{\citylist}{derby,leicester,nottingham}
\removecity{leicester}
\typeout{\citylist}

\begin{document}
\end{document}

Then spaces do not disturb, e.g.:

\newcommand*{\citylist}{derby, leicester, nottingham}

The city names may contain commands and fragile macros, they are not expanded.

With the following definition for \addcity, the city names might even contain commas:

\newcommand*{\addcity}[1]{%
  \ifx\citylist\@empty\else\g@addto@macro\citylist{,}\fi
  \g@addto@macro\citylist{{#1}}
}

Example for a list:

{derby},{leicester},{berlin, west},{berlin, east}

7

The expl3 suite can manage comma separated lists in many ways.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\createlist}{mm}
 {
  \clist_new:c { g_wickerson_data_#1_clist }
  \clist_gset:cn { g_wickerson_data_#1_clist } { #2 }
 }
\NewDocumentCommand{\addtolist}{mm}
 {
  \clist_gput_right:cn { g_wickerson_data_#1_clist } { #2 }
 }
\NewDocumentCommand{\removefromlist}{mm}
 {
  \clist_gremove_all:cn { g_wickerson_data_#1_clist } { #2 }
 }
\NewDocumentCommand{\purgelist}{m}
 {
  \clist_gremove_duplicates:c { g_wickerson_data_#1_clist }
 }
\NewDocumentCommand{\printlist}{m}
 {
  \clist_use:cnnn { g_wickerson_data_#1_clist } { ,~ } { ,~ } { ,~ }
 }
\cs_generate_variant:Nn \clist_use:Nnnn { c }
\ExplSyntaxOff

%%% working with a specific list without needing two args
\newcommand{\addcity}[1]{\addtolist{city}{#1}}
\newcommand{\removecity}[1]{\removefromlist{city}{#1}}
\newcommand{\printcity}{\printlist{city}}

\createlist{city}{derby,leicester} % initialization

\begin{document}

\addcity{nottingham}

Cities: \printcity

\bigskip

\removecity{leicester}

Cities: \printcity

\end{document}

However, comma lists are not the most efficient data type: sequences are better. And the internal implementation shouldn't be a concern.

The same is achieved in a faster way with

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\createlist}{mm}
 {
  \seq_new:c { g_wickerson_data_#1_seq }
  \seq_gset_from_clist:cn { g_wickerson_data_#1_seq } { #2 }
 }
\NewDocumentCommand{\addtolist}{mm}
 {
  \seq_gput_right:cn { g_wickerson_data_#1_seq } { #2 }
 }
\NewDocumentCommand{\removefromlist}{mm}
 {
  \seq_gremove_all:cn { g_wickerson_data_#1_seq } { #2 }
 }
\NewDocumentCommand{\purgelist}{m}
 {
  \seq_gremove_duplicates:c { g_wickerson_data_#1_seq }
 }
\NewDocumentCommand{\printlist}{m}
 {
  \seq_use:cnnn { g_wickerson_data_#1_seq } { ,~ } { ,~ } { ,~ }
 }
\cs_generate_variant:Nn \seq_use:Nnnn { c }
\ExplSyntaxOff

%%% working with a specific list without needing two args
\newcommand{\addcity}[1]{\addtolist{city}{#1}}
\newcommand{\removecity}[1]{\removefromlist{city}{#1}}
\newcommand{\printcity}{\printlist{city}}

\createlist{city}{derby,leicester} % initialize

\begin{document}

\addcity{nottingham}

Cities: \printcity

\bigskip

\removecity{leicester}

Cities: \printcity

\end{document}
6
  • Thanks very much @egreg. Unfortunately, when I run your first program, I get the error "Command '\clist_use:Nnnn' not yet defined!", and from your second program I get "Command '\seq_use:Nnnn' not yet defined!". I'm running pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011) -- do I need to update? Commented Apr 19, 2013 at 10:07
  • 1
    @JohnWickerson You definitely need to update. TeX Live 2011 is very outdated with respect to expl3. Note that TL 2013 is in testing phase, but it's surely worthy to upgrade to TL 2012.
    – egreg
    Commented Apr 19, 2013 at 10:10
  • Thanks @egreg. I'm now on pdfTeX Version 3.1415926-2.4-1.40.13 (TeX Live 2012), but I still get an error, albeit now with a slightly more fashionable wording: "Control sequence \clist_use:Nnnn undefined." Commented Apr 19, 2013 at 10:24
  • sounds more than strange. can you post the log file somewhere? Commented Apr 19, 2013 at 13:25
  • @JohnWickerson You have to run tlmgr and update fully.
    – egreg
    Commented Apr 19, 2013 at 15:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .