0

I have a string such as 123p456789m333s22z (that denotes a Riichi mahjong hand). I need to invert the order of every letter-number pair within that string, so I get a string like p123m456789s333z22.

Each letter-number pair consists of a single letter from the set mspz and a series of decimal digits. The string is allowed to contain other characters, such as *~? - these should be treated as a part of the number string and not moved with regard to the numbers.

I have been investigating xstring, but I have not reached any conclusive answers. Is anyone aware of a technique that will help me achieve this goal?

2 Answers2

1

With the use of expl3's l3regex module we can do this easily. Input is converted to a string, so that ~ is easily input. The only tokens allowed other than numbers in the number part are *, ~, and ?.

\documentclass[]{article}

\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_herda_hand_tl
\regex_const:Nn \c_herda_invert_regex { ([\d\*\~?]+) ([mpsz]) }
\cs_new_protected:Npn \herda_inverthand:n #1
  {
    \tl_set:Nx \l_herda_hand_tl { \tl_to_str:n { #1 } }
    \regex_replace_all:NnN \c_herda_invert_regex { \2 \1 } \l_herda_hand_tl
    \tl_use:N \l_herda_hand_tl
  }
\NewDocumentCommand \inverthand { m }
  {
    \herda_inverthand:n { #1 }
  }
\ExplSyntaxOff

\begin{document}
\inverthand{123p456789m333s22z}

\inverthand{1~23p456789m333s22z}

\inverthand{1*23p456789m333s22z}

\inverthand{1?23p456789m333s22z}
\end{document}

enter image description here

Skillmon
  • 60,462
1

Just for fun, a fully expandable version that doesn't need any packages. Again, the argument to \inverthand is detokenized. You can set the letters recognized by altering the line

\edef\herda@invertletters{\detokenize{mspz}}

and change the symbols which are also considered as part of the numbers by altering the line

\edef\herda@alsodigit{\detokenize{*~?}}

So here is the complete code:

\documentclass[]{article}

\makeatletter
\edef\herda@alsodigit{\detokenize{*~?}}
\edef\herda@invertletters{\detokenize{mspz}}
\def\herda@qstop{\herda@qstop}
\long\def\herda@fi@firstoftwo\fi\@secondoftwo#1#2{\fi#1}
\long\def\herda@fi@gobble\fi\@firstofone#1{\fi}
\newcommand\herda@ifdigit[1]%>>=
  {%
    \ifnum1<1\noexpand#1 \herda@fi@firstoftwo\fi\@secondoftwo
  }%=<<
% we can make a few assumptions here which help to keep this tokenin
% implementation simple:
%   1. argument 1 will be not empty and a single token
%   2. argument 2 will contain no groups
\long\def\herda@iftokenin@true\fi%>>=
  \herda@iftokenin@#1\herda@qstop\@secondoftwo#2#3{\fi#2}%=<<
\newcommand\herda@iftokenin@[2]%>>=
  {%
    \ifx\herda@qstop#2\herda@fi@gobble\fi
    \@firstofone
    {%
      \ifx#1#2%
        \herda@iftokenin@true
      \fi
      \herda@iftokenin@#1%
    }%
  }%=<<
\newcommand\herda@iftokenin[2]%>>=
  {%
    \herda@iftokenin@#1#2\herda@qstop\@secondoftwo
  }%=<<
\newcommand\herda@invert%>>=
  {%
    \herda@invert@{}%
  }%=<<
\long\edef\herda@invert@#1#2%>>=
  {%
    \unexpanded{\ifx\herda@qstop}#2%
    \unexpanded{\herda@fi@firstoftwo\fi\@secondoftwo}%
    {#1}%
    {%
      \noexpand\herda@ifdigit{#2}
        {\noexpand\herda@invert@{#1#2}}
        {%
          \noexpand\herda@iftokenin#2{\herda@alsodigit}%
            {\noexpand\herda@invert@{#1#2}}
            {%
              \noexpand\herda@iftokenin#2{\herda@invertletters}%
                {#2#1\noexpand\herda@invert@{}}
                {#1#2\noexpand\herda@invert@{}}%
            }%
        }%
    }%
  }%=<<
\newcommand\inverthand[1]%>>=
  {%
    \expandafter\herda@invert\detokenize{#1}\herda@qstop
  }%=<<

\begin{document}
fully expandable:\par
\edef\foo{\inverthand{123p456789m333s22z}}\texttt{\meaning\foo}

\inverthand{1~23p456789m333s22z}

\inverthand{1*23p456789m333s22z}

\inverthand{1?23p456789m333s22z}

\inverthand{1?23d456789m333s22z}
\end{document}

enter image description here

Skillmon
  • 60,462