6

I have two strings of equal lengths, let's say abc and def (in reality they can be much longer). Both strings consist only of letters a-z, A-Z (no numbers, no special characters etc.). I need to extract out the characters individually from both strings, and use them pairwise in another macro that I have defined.

For e.g. For the two strings abc and def and some other macro \printchar[2]{#1,#2} that I have defined, I want to be able to pass the characters pair by pair into \printchar, that is,

\printchar{a}{d}
\printchar{b}{e}
\printchar{c}{f}

in perhaps, a for loop of some kind. So I guess the question is two-fold:

  1. How to extract out characters from two input strings of equal lengths, character by character, and
  2. Subsequently use them, pairwise, inside a macro. (and it should loop through all characters in the string).

Pseudo-Code:

\documentclass[]{article}
\newcommand\printchar[2]{#1,#2}
\newcommand\foo[2]{% Takes in two strings
    % <begin loop>
    % Extracts out one character from both strings
    \printchar{#1}{#2}%
    % Continue loop until strings run out of characters
    % <end loop>
}

\begin{document}
    \foo{abc}{def} % Should print a,d,b,e,f
\end{document}

From some searching around, I think maybe the xstring package can do the first part? But I'm not too sure how to integrate it with a for loop to achieve what I want.

4
  • Will both strings always consist of printable characters -- more precisely, characters with catcode "letter" and/or "other"? Or might the strings contain arbitrary characters, incl "special" characters such as % and {?
    – Mico
    May 2, 2017 at 14:35
  • Oh yes, good point. No, they will always be letters. (a-z, A-Z). I'll update it in the question
    – Troy
    May 2, 2017 at 14:36
  • Why the apparent haste to "accept" one of the answers? You can upvote good answers immediately, of course. Site guidelines suggest, though, waiting several hours and maybe even day before accepting the best answer. Hasty acceptance of an answer tends to completely discourage the posting of any further answers.
    – Mico
    May 2, 2017 at 15:26
  • Oops, sorry @Mico. I will keep that in mind in the future.. :x
    – Troy
    May 2, 2017 at 15:31

4 Answers 4

5

Without any packages:

\documentclass[]{article}
\makeatletter
\newcommand\printchar[2]{#1,#2}
\def\parsefoo#1#2,#3#4|{%
    \ifx\relax#1\relax%
    \else%
        \ifx\relax#3\relax%
        \else%
            \printchar{#1}{#3}%
            \ifx\relax#2\relax%
            \else%
                \ifx\relax#4\relax%
                \else%
                    ,\edef\@footmp{#2,#4|}%
                    \expandafter\parsefoo\@footmp%
                \fi%
            \fi%
        \fi%
    \fi%
}
\newcommand\foo[2]{% Takes in two strings
    % <begin loop>
    % Extracts out one character from both strings
    \edef\@footmp{#1,#2|}%
    \expandafter\parsefoo\@footmp%
    %\printchar{#1}{#2}%
    % Continue loop until strings run out of characters
    % <end loop>
}
\makeatother

\begin{document}
    \foo{abc}{def} % Should print a,d,b,e,f
    haha
\end{document}

results

4

The loop will stop when either strings runs out of items.

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\foo}{mmm}
 {% #1 is the macro to use, #2 is the first string, #3 is the second string
  \troy_foo:Nnn #1 { #2 } { #3 }
 }

\seq_new:N \l_troy_foo_first_seq
\seq_new:N \l_troy_foo_second_seq

\cs_new_protected:Nn \troy_foo:Nnn
 {
  % split the first string into items
  \seq_set_split:Nnn \l_troy_foo_first_seq { } { #2 }
  % split the second string into items
  \seq_set_split:Nnn \l_troy_foo_second_seq { } { #3 }
  % map over the two string
  \seq_mapthread_function:NNN \l_troy_foo_first_seq \l_troy_foo_second_seq #1
 }
\ExplSyntaxOff

\newcommand{\printchars}[2]{#1#2\par}

\begin{document}

\foo{\printchars}{abc}{def}

\end{document}

enter image description here

A fully expandable variant:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\loopstrings}{mmm}
 {
  \troy_loopstrings:Nnn #1 { #2 } { #3 }
 }
\cs_new:Nn \troy_loopstrings:Nnn
 {
  \__troy_loopstrings:Nffff #1
   { \tl_head:n { #2 } } { \tl_head:n { #3 } }
   { \tl_tail:n { #2 } } { \tl_tail:n { #3 } }
 }

\cs_new:Nn \__troy_loopstrings:Nnnnn
 {
  \bool_lazy_or:nnF { \tl_if_empty_p:n { #2 } } { \tl_if_empty_p:n { #3 } }
   {
    #1{#2}{#3}
    \troy_loopstrings:Nnn #1 { #4 } { #5 }
   }
 }
\cs_generate_variant:Nn \__troy_loopstrings:Nnnnn { Nffff }
\ExplSyntaxOff

\newcommand{\printchars}[2]{#1#2\par}

\begin{document}

\loopstrings{\printchars}{abcd}{1234}

\loopstrings{\printchars}{abc}{1234}

\loopstrings{\printchars}{abcd}{123}

\end{document}
4
  • Yay, faster than egreg :)
    – Skillmon
    May 2, 2017 at 14:50
  • @Skillmon Even with longer code!
    – egreg
    May 2, 2017 at 14:51
  • Just longer because of the many \ifx to end the loop if one argument runs out of tokens.
    – Skillmon
    May 2, 2017 at 14:52
  • @Skillmon +1 for both of your answers! Both seem to do fine for what I'm working on in reality, but I guess I'll accept @Skillmon's no packages answer, and a reward for being faster than egreg. :)
    – Troy
    May 2, 2017 at 15:10
2

Here's a LuaLaTeX-based solution. (Exact same output as in @egreg's answer, hence no separate screenshot.)

%% to be compiled under LuaLaTeX
\documentclass{article}
\usepackage{luacode} 
\begin{luacode}
  function string_pair ( u , v )
    for i=1,string.len(u) do 
      tex.sprint ( string.sub (u,i,i) .. string.sub (v,i,i) .. "\\par" )
    end
  end
\end{luacode}
\newcommand\foo[2]{\directlua{string_pair(\luastring{#1},\luastring{#2})}}

\begin{document}
\foo{abc}{def}
\end{document} 
1

When I was having fun with strings (Looping over strings) I also made a macro for looping over two strings. However, after the replies I got there it seems that it is not a good solution since it does not use tail recursion. On the other hand, it has the advantage of being easy to understand and therefore I put it here.

\documentclass{article}

\begin{document}

\def\doubleloop<#1#2><#3#4>{%
  \ifx\relax#1 \else
    \ifx\relax#3 \else
      [#1,#3] 
      \doubleloop<#2><#4>
    \fi
  \fi}

\def\loopstrings#1#2{\doubleloop<#1\relax><#2\relax>}

\verb|\loopstrings{abcd}{1234}| \loopstrings{abcd}{1234}

\verb|\loopstrings{abc}{1234}| \loopstrings{abc}{1234}

\verb|\loopstrings{abcd}{123}| \loopstrings{abcd}{123}

\end{document}

enter image description here

This can be turned into a macro using tail recursion:

\documentclass{article}

\begin{document}

\makeatletter
\def\doubleloop<#1#2><#3#4>{%
  \ifx#1\relax
    \expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {\doubleloopaux{#1}{#2}{#3}{#4}}%
}
\def\doubleloopaux#1#2#3#4{%
  \ifx#3\relax
    \expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {[#1,#3]\doubleloop<#2><#4>}%
}
\makeatother

\def\loopstrings#1#2{\doubleloop<#1\relax><#2\relax>}

\verb|\loopstrings{abcd}{1234}| \loopstrings{abcd}{1234}

\verb|\loopstrings{abc}{1234}| \loopstrings{abc}{1234}

\verb|\loopstrings{abcd}{123}| \loopstrings{abcd}{123}

\end{document}
2
  • @egreg: Thanks! I had been trying to convert it to tail recursion without success. I find this a really nice setup. If I understand it well, the \@firstofone just eliminates the braces? May 8, 2017 at 6:54
  • Yes, but they're also necessary in order for \@gobble to work, in case the firs string has run off of items
    – egreg
    May 8, 2017 at 7:02

You must log in to answer this question.

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