10

I have a block of text that I would like to automatically break in blocks of equal length. Something like what \num does with numbers.

What I need is a definition for what I called \makeBlocks. The MWE below should output: aaaaa bbbbb ccccc ddddd followed by abc def ghi

\documentclass{article}

\newcommand{\makeBlocks}[1]{???}

\begin{document}
\makeBlocks{5}{aaaaabbbbbcccccddddd} 
followed by
\makeBlocks{3}{abcdefghi}
\end{document}
5
  • Is ist something like the following pseudocode? if NextChar equals CurrentChar then put space before NextChar else fi Did i understand this correctly?
    – Johannes_B
    Jan 27, 2014 at 15:06
  • @Johannes_B It has nothing to do with the next charactor. I'll update the MWE accordingly.
    – Geoff
    Jan 27, 2014 at 15:07
  • You want equal length: A space after #1 number of chars?
    – Johannes_B
    Jan 27, 2014 at 15:08
  • Right! To be precise, not equal space, but equal numbers of charactors.
    – Geoff
    Jan 27, 2014 at 15:09
  • 1
    How should \makeBlocks handle the content if the number doesn't divide the length of the string, as in \makeBlocks{4}{abcdefghi}?
    – Werner
    Jan 27, 2014 at 15:24

2 Answers 2

8

Here's an implementation with l3regex:

\documentclass{article}
\usepackage{xparse,l3regex}
\ExplSyntaxOn

\NewDocumentCommand\makeBlocks{mm}
 {
  \geoff_makeblocks:nn { #1 } { #2 }
 }

\tl_new:N \l_geoff_blocks_input_tl

\cs_new_protected:Npn \geoff_makeblocks:nn #1 #2
 {
  \tl_set:Nn \l_geoff_blocks_input_tl { #2 }
  %\tl_reverse:N \l_geoff_blocks_input_tl
  \regex_replace_all:nnN { (.{0,#1}) } { \1 \c{,} } \l_geoff_blocks_input_tl
  \regex_replace_once:nnN { \c{,} \c{,} \Z } { } \l_geoff_blocks_input_tl
  %\tl_reverse:N \l_geoff_blocks_input_tl
  \tl_use:N \l_geoff_blocks_input_tl
 }

\ExplSyntaxOff

\begin{document}

\makeBlocks{3}{abcdef}

\makeBlocks{3}{abcdefg}

\makeBlocks{3}{abcdefghi}

\makeBlocks{5}{aaaaabbbbbccccc}

\end{document}

enter image description here

This counts starting from the left. If you want to start counting from the right, just reverse twice the token list, by uncommenting the two \tl_reverse:N lines. Here's what you'd get:

enter image description here

If you want a normal space between blocks, just change the two \regex lines into

\regex_replace_all:nnN { (.{0,#1}) } { \1 \cS\  } \l_geoff_blocks_input_tl
\regex_replace_once:nnN { \cS. \cS. \Z } { } \l_geoff_blocks_input_tl

A “full featured” macro that starts either from left or from right (this with the *-variant) and accepts the separator as optional argument (default a space).

\documentclass{article}
\usepackage{xparse,l3regex}
\ExplSyntaxOn

\NewDocumentCommand\makeBlocks{s O{~} m m }
 {
  \IfBooleanTF{#1}
   {
    \geoff_makeblocks:nnnn { #2 } { #3 } { #4 } { \__geoff_from_right: }
   }
   {
    \geoff_makeblocks:nnnn { #2 } { #3 } { #4 } { }
   }
 }

\tl_new:N \l_geoff_blocks_input_tl
\tl_new:N \l_geoff_block_separator_tl

\cs_new_protected:Npn \geoff_makeblocks:nnnn #1 #2 #3 #4
 {
  \tl_set:Nn \l_geoff_block_separator_tl { #1 }
  \tl_set:Nn \l_geoff_blocks_input_tl { #3 }

  #4 % this will be nothing or \tl_reverse:N \l_geoff_blocks_input_tl

  % search blocks of #2 tokens and put the separator after them
  \regex_replace_all:nnN
   { (.{#2}) } % search
   { \1 \u{l_geoff_block_separator_tl} } % replace
   \l_geoff_blocks_input_tl % in this list

  % if the number of tokens is a multiple of #2, one too much
  % separator is inserted, remove it
  \regex_replace_once:nnN
   { \u{l_geoff_block_separator_tl} \Z } % search
   { } % replace
   \l_geoff_blocks_input_tl % in this list

  #4

  \tl_use:N \l_geoff_blocks_input_tl
 }

\cs_new:Npn \__geoff_from_right:
 {
  \tl_reverse:N \l_geoff_blocks_input_tl
 }

\ExplSyntaxOff

\begin{document}

From left: \makeBlocks{3}{abcdef}

From left: \makeBlocks{3}{abcdefg}

From right: \makeBlocks*{3}{abcdefgh}

With different separator: \makeBlocks[ x ]{3}{abcdefghi}

With different number: \makeBlocks{5}{aaaaabbbbbccccc}

\end{document}

enter image description here

3
  • This works as stated, but now I realise it doesn't wrap at the end of the line (for a very long word). I tried wrapping it in a parbox but that didn't help.
    – Geoff
    Jan 27, 2014 at 15:55
  • @Geoff That's because I added \, between blocks. I'll add the modification to get a normal space.
    – egreg
    Jan 27, 2014 at 16:20
  • @Geoff I added a more complete version where you can also choose a different separator.
    – egreg
    Jan 28, 2014 at 0:15
3

You could use the xstring package: enter image description here

Notes:

  • This is an enhanced version of the solution at Using \newcommand to format numbers/strings according to a pattern.
  • There is a limit in this code of 400 characters, so please adjust the ending value of the \foreach as desired.
  • By default the split is done from the left. If you desire the split to be from the right, you need to pass in the first optional parameter with something other than L.
  • The separator is defined in \Separator so a simple \renewcommand would allow you to change that.

Code:

\documentclass{article}
\usepackage{xstring}
\usepackage{pgf,pgffor}

\newcommand*{\StringLength}{}%
\newcommand*{\FirstOutputStringLength}{}%
\newcommand*{\TempStringA}{}%
\newcommand*{\TempStringB}{}%
\newcommand*{\Separator}{\,}%
\newcommand*{\makeBlocks}[3][L]{%
    \edef\TempStringA{#3}%
    \IfStrEq{#1}{L}{%
        \foreach \x in {1,...,100} {%
            \StrLeft{\TempStringA}{#2}%
            \StrGobbleLeft{\TempStringA}{#2}[\TempStringB]%
            \xdef\TempStringA{\TempStringB}%
            \IfStrEq{\TempStringA}{}{\breakforeach}{\Separator}%
        }%
    }{%
        \StrLen{\TempStringA}[\StringLength]%
        \IfEq{\StringLength}{0}{}{%
            \pgfmathtruncatemacro{\FirstOutputStringLength}{mod(\StringLength,#2)}%
            \IfEq{\FirstOutputStringLength}{0}{%
                \xdef\TempStringB{\TempStringA}%
            }{%
                \StrLeft{\TempStringA}{\FirstOutputStringLength}\Separator%
                \StrGobbleLeft{\TempStringA}{\FirstOutputStringLength}[\TempStringB]%
            }%
        }%
        \makeBlocks[L]{#2}{\TempStringB}%
    }%
}
\begin{document}
\begin{tabular}{cll}
    Num of Char    & \multicolumn{1}{c}{Left} & \multicolumn{1}{c}{Right} \\
    5 & \makeBlocks{5}{AB1234567890}   & \makeBlocks[R]{5}{AB1234567890}\\
    5 & \makeBlocks{5}{AB123456789}    & \makeBlocks[R]{5}{AB123456789}\\
    5 & \makeBlocks{5}{AB12345678}     & \makeBlocks[R]{5}{AB12345678}\\
    5 & \makeBlocks{5}{AB1234567}      & \makeBlocks[R]{5}{AB1234567}\\
    5 & \makeBlocks{5}{AB12}           & \makeBlocks[R]{5}{AB12}\\
    5 & \makeBlocks{5}{AB}             & \makeBlocks[R]{5}{AB}\\
    5 & \makeBlocks{5}{A}              & \makeBlocks[R]{5}{A}\\ [2.0ex]
    4 & \makeBlocks{4}{AB1234567890}   & \makeBlocks[R]{4}{AB1234567890}\\
    3 & \makeBlocks{3}{AB123456789}    & \makeBlocks[R]{3}{AB123456789}\\
    2 & \makeBlocks{2}{AB12345678}     & \makeBlocks[R]{2}{AB12345678}\\
    1 & \makeBlocks{1}{AB1234567}      & \makeBlocks[R]{1}{AB1234567}\\
\end{tabular}

\bigskip
\renewcommand{\Separator}{-}%
With different separator: 

\makeBlocks{4}{AB1234567890}

\makeBlocks{1}{AB1234567890}

\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.