如何从文本字符串创建以逗号分隔的字符列表?

如何从文本字符串创建以逗号分隔的字符列表?

Hello World!我如何将存储到宏中的字符串拆分\def\mystring{Hello Word!}为以逗号分隔的字符列表(包括空格),该列表可用作\foreach循环的参数(\foreach \char in {\myCSlist})以循环遍历每个字符(如同\foreach \char in {H,e,l,l,o,\space,W,o,r,l,d,!})

编辑 1 (3 月 21 日):我为什么想要这个?

一开始,我没有解释为什么我希望解决方案生成一个逗号分隔的字符列表(例如\myCSlist),该列表可以用作 的参数\foreach。这是因为我想\node使用 为每个字符创建一个 Tikz pic。类似这样的内容(来自这里):

\newcommand{\hsp}{.5}
\tikzset{symbols/.pic={%
    \foreach \s[count=\n from 0] in {\myCSlist}{%
    \pgfmathsetmacro{\myangle}{360*rnd}%
    \node[rotate=\myangle] at (\hsp*\n,0){\s};%
}}}%

第一次尝试

我尝试过\markletters埃格尔(使用 xparse 和 expl3)但结果似乎不适合循环foreach

\markletters我将宏的结果打印 - 从 更改(##1)##1,,以获得以逗号分隔的列表,但这也导致以,逗号分隔的列表末尾出现不需要的逗号。

这里有一个 M(Non)WE:

\documentclass{article}
\usepackage{tikz}% For the \foreach loop
\usepackage{xparse}

% egreg's \markletters macro : https://tex.stackexchange.com/a/359204/262081
\ExplSyntaxOn
\NewDocumentCommand{\markletters}{om}
 {
  \IfNoValueTF{#1}
   {
    \kessels_markletters:nn { #2 } { \tl_use:N \l_kessels_marked_letters_tl }
   }
   {
    \kessels_markletters:nn { #2 } { \tl_set_eq:NN #1 \l_kessels_marked_letters_tl }
   }
 }

\tl_new:N \l_kessels_unmarked_letters_tl
\tl_new:N \l_kessels_marked_letters_tl

\cs_new_protected:Nn \kessels_markletters:nn
 {
  \tl_set:Nn \l_kessels_unmarked_letters_tl { #1 }
  \tl_replace_all:Nnn \l_kessels_unmarked_letters_tl { ~ } { \textvisiblespace }
  \tl_clear:N \l_kessels_marked_letters_tl
  \tl_map_inline:Nn \l_kessels_unmarked_letters_tl
   {
    \tl_put_right:Nn \l_kessels_marked_letters_tl { ##1, }
   }
  #2
 }
\ExplSyntaxOff

\def\mystring{Hello World!}

\begin{document}
\markletters[\foo]{\mystring}%
\foreach \char in {\foo}{%
    <\char>%
}%
\end{document}

得出以下结果:

在此处输入图片描述

但我更喜欢这个结果(请注意“Hello”和“World”之间的空格):

在此处输入图片描述

答案1

这可以使用包来完成xstring。代码还使用了\foreach,它是的一部分,但如果您的文档不使用 tikz,则tikz可以加载。pgffor

在此处输入图片描述

\documentclass{article}

\usepackage[T1]{fontenc} % to use accented characters and others
\usepackage{xstring}
\usepackage{pgffor} % in case you're not using tikz

\newcommand{\markletters}[1]{\StrLen{#1}[\strlen]\foreach \chr in {1,...,\strlen}{$<$\StrChar{#1}{\chr}$>$}}

\begin{document}

\markletters{Hello World!}

\markletters{äöü ÄÖÜ // ß}

\end{document}

答案2

这是一个基于 LuaLaTeX 的解决方案。它完全支持 utf8 编码,也就是说,输入字符串可能包含 utf8 编码的字符,而不仅仅是 ascii 编码的字符。

该代码由一个名为的 Lua 函数DoString和一个名为的 LaTeX 实用程序宏组成\DoString,它接受 1 个参数(一个字符串)并将其传递给 Lua 函数进行进一步处理。

在此处输入图片描述

% !TEX TS-program = lualatex
\documentclass{article}
\directlua{
function DoString ( s )
  for i = 1,string.utflength(s) do 
    tex.sprint ( '<' .. unicode.utf8.sub ( s , i , i ) .. '>' )
  end
end
}
%% Define a LaTeX utility macro:
\newcommand\DoString[1]{\directlua{DoString("#1")}}

\begin{document}
\ttfamily
\DoString{Hello World!}

\DoString{äöü ÄÖÜ // ß}
\end{document}

附录解决 OP 的后续问题:要按照评论中的建议创建 tikz 节点,我建议您(a)加载包luacode(用于\luaexec宏)和(b)将\directlua{...}块更改为

\luaexec{
function DoString ( s )
  for i = 1,string.utflength(s) do 
    tex.sprint ( '\\node{' .. unicode.utf8.sub ( s , i , i ) .. '};' )
  end
end
}

答案3

这里我使用了一个标记循环。没有提到输入中出现的宏或组(只有字符和空格),因此这些应该从输入中排除,除非 OP 建议如何处理它们。

如果希望在 for 类循环中使用此列表,则可以将逗号分隔的内容读入列表后,\foreachitem从包中使用。listofitems\mystring

\documentclass{article}
\usepackage{tokcycle,listofitems}
\Characterdirective{\addcytoks{,#1}}
\Spacedirective{\addcytoks{,#1}}
\makeatletter
\newcommand\markletters[1]{%
  \tokcyclexpress{#1}%
  \expandafter\expandafter\expandafter\def
  \expandafter\expandafter\expandafter\mystring
  \expandafter\expandafter\expandafter
  {\expandafter\@gobble\the\cytoks}%
}
\makeatother
\begin{document}
\markletters{Hello World!}

(\detokenize\expandafter{\mystring})

\readlist\mylist\mystring

\foreachitem\z\in\mylist{$<$\z$>$}

\end{document}

在此处输入图片描述

当然,整个过程可以在 token 循环中更简单地完成,这样listofitems甚至不需要。而且,令我惊讶的是,如果我在 lualatex 下编译,它可以正确处理 UTF-8 输入

\documentclass{article}
\usepackage{tokcycle}
\newcommand\markletters[1]{%
  \tokencycle{$<$##1$>$}{}{}{$<$##1$>$}#1\endtokencycle%
}
\begin{document}
\markletters{Hello World! äöü ÄÖÜ // ß}
\end{document}

在此处输入图片描述

补充

根据 OP 评论,这里有一个版本(基于第一种方法)可以处理宏和组。但是,需要参数的宏必须小心处理,因为每个标记/组后面都会插入逗号。

在此实现中,群组内容仅作为单个实体回显。如果想要按标记细分群组内容,tokcycle可以这样做,但此实现中没有这样做。

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tokcycle,listofitems}
\makeatletter
\stripgroupingtrue
\newcommand\markletters[1]{%
  \tokcycle{\addcytoks{,##1}}{\addcytoks{,}\groupedcytoks{##1}}%
    {\addcytoks{,##1}}{\addcytoks{,##1}}{#1}%
  \expandafter\expandafter\expandafter\def
  \expandafter\expandafter\expandafter\mystring
  \expandafter\expandafter\expandafter
  {\expandafter\@gobble\the\cytoks}%
}
\makeatother
\begin{document}
\markletters{Hello World! \today{} is a great day}

(\detokenize\expandafter{\mystring})

\readlist\mylist\mystring

\foreachitem\z\in\mylist{$<$\z$>$}

\mylist[7] is the 7th token

\detokenize\expandafter\expandafter\expandafter{\mylist[14]}
 is the 14th token
\end{document}

在此处输入图片描述

答案4

由于我们无法通过未分隔的宏参数吸收单个空格,因此我们必须先通过宏预处理给定的宏\replspaces。然后您可以使用\insertcommas在标记之间插入逗号,但不在最后一个标记后插入逗号。或者用\insertangles替换每个。token<token>

\def\afterfi#1#2\fi{\fi#1}
\def\replspaces#1{\edef#1{\expandafter\replspacesA#1 \end}}
\def\replspacesA #1 #2{#1\ifx\end#2\else { }\afterfi{\replspacesA#2}\fi}
\def\insertcommas#1{\edef#1{\expandafter\insertcommasA#1\end}}
\def\insertcommasA#1#2{#1\ifx\end#2\else,\afterfi{\insertcommasA{#2}}\fi}
\def\insertangles#1{\edef#1{\expandafter\insertanglesA#1\end}}
\def\insertanglesA#1{\ifx\end#1\else<#1>\expandafter\insertanglesA\fi}

% test:

\def\mystring{Hello World!}

\replspaces\mystring
\insertcommas\mystring
\meaning\mystring  % macro:-> H,e,l,l,o, ,W,o,r,l,d,!

\def\mystring{Hello World!}

\replspaces\mystring
\insertangles\mystring
\meaning\mystring  % macro:-> <H><e><l><l><o>< ><W><o><r><l><d><!>

\bye

编辑您第一个问题是:如何H,e,l,l,o, ,w,o,r,d,!从宏创建宏Hello World!。我回答正确。

现在,您的第二个问题是:如何在\foreachTikz 中使用此类宏。我不知道如何有效地将扩展参数设置为\foreachTikz 中不太实用的参数(您可以使用\expanded{\unexpanded {\foreach ...}{\mystring}}但看起来很糟糕)。最好不要\foreach在 Tikz 中使用,而是定义您自己的\myforeach

\def\replspaces#1{\edef#1{\expandafter\replspacesA#1 \end}}
\def\replspacesA #1 #2{#1\ifx\end#2\else { }\afterfi{\replspacesA#2}\fi}
\def\myforeach#1{\expandafter\myforeachA#1\end}
\def\myforeachA#1{\ifx\end#1\else\body{#1}\expandafter\myforeachA\fi}

\def\body#1{<#1>}
\def\mystring{Hello World!}

\replspaces\mystring 
\myforeach\mystring % prints: <H><e><l><l><o>< ><W><o><r><l><d><!>

\bye

相关内容