将范围智能扩展为数字列表

将范围智能扩展为数字列表

我想知道是否有一种简单的方法可以将一些输入文本(例如“3-7”)扩展为“3,4,5,6,7”?

就上下文而言,我正在使用\foreach并希望能够编写

\foreach \x in {3-7, 9, 14, 52}

并已\x运行至3、4、5、6、7、9、14和52。

或者,如果有一个简单的方法

\foreach \x in {1,3,...7}

导致\x被定义为“1、3、4、5、6 和 7”而不是标准的“1、3、5 和 7”,那就太好了。

编辑:我认为我的解释并不清楚,但我个人了解如何\foreach在 TeX 中执行这些命令,但希望有一个简单的输入结构供非 TeX 用户使用,即输入\makeProblems{1, 3-7, 9, 14, 52}并将相关问题作为输出。

答案1

只需编写少量代码,您就可以制作一个解析器。我\makeProblems{<integer list>}{<code>}为您定义了一个,其中<integer list>是一个以逗号分隔的数字列表,其中被解析为介于和<x>-<y>之间的整数列表(包括)。该函数解析数字列表,然后遍历生成的列表,并使当前数字可用为。例如:<x><y><code>#1

\makeProblems{1,3-7, 9, 14, 52}{Do something with #1.\par}

印刷:

在此处输入图片描述

代码很长,因为当函数接受用户输入时,函数会格外小心以确保<integer list>不包含错误输入。

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_ryanj_list_tl
\NewDocumentCommand \makeProblems { m +m }
  {
    \tl_clear:N \l_ryanj_list_tl
    \exp_args:Nx \clist_map_function:nN {#1} \__ryanj_parse_item:n
    \tl_map_inline:Nn \l_ryanj_list_tl {#2}
  }
\cs_new_protected:Npn \__ryanj_add_item:n #1
  { \tl_put_right:Nn \l_ryanj_list_tl { {#1} } }
\cs_new_protected:Npn \__ryanj_parse_item:n #1
  {
    \__ryanj_if_number:nTF {#1}
      { \__ryanj_add_item:n {#1} }
      {
        \str_if_in:nnTF {#1} {-}
          { \exp_args:Nf \__ryanj_parse_range:n { \tl_to_str:n {#1} } }
          { \msg_error:nnn { ryanj } { invalid-number } {#1} }
      }
  }
\cs_new_protected:Npn \__ryanj_parse_range:n #1
  { \__ryanj_parse_range:nw {#1} #1 \q_mark }
\cs_new_protected:Npn \__ryanj_parse_range:nw #1#2-#3 \q_mark
  {
    \__ryanj_validate_number:nn {#1} {#2}
    \__ryanj_validate_number:nn {#1} {#3}
    \int_step_function:nnnN {#2} { 1 } {#3} \__ryanj_add_item:n
    \use_none:n \q_stop
  }
\cs_new_protected:Npn \__ryanj_validate_number:nn #1 #2
  {
    \__ryanj_if_number:nF {#2}
      {
        \msg_error:nnnn { ryanj } { invalid-number-in-range } {#2} {#1}
        \use_none_delimit_by_q_stop:w
      }
  }
\msg_new:nnn { ryanj } { invalid-range } { Invalid~range~`#1'. }
\msg_new:nnn { ryanj } { invalid-number } { Invalid~number~`#1'. }
\msg_new:nnn { ryanj } { invalid-number-in-range } { Invalid~number~`#1'~in~range~`#2'. }
\prg_new_conditional:Npnn \__ryanj_if_number:n #1 { T, F, TF }
  {
    \tl_if_empty:oTF
      { \tex_romannumeral:D - 0#1 \exp_stop_f: }
      {
        \tl_if_empty:nTF {#1}
          { \prg_return_false: }
          { \prg_return_true: }
      }
      { \prg_return_false: }
  }
% For older expl3:
\prg_set_protected_conditional:Npnn \str_if_in:nn #1#2 { T , F , TF }
  {
    \use:x
      { \tl_if_in:nnTF { \tl_to_str:n {#1} } { \tl_to_str:n {#2} } }
      { \prg_return_true: } { \prg_return_false: }
  }
\ExplSyntaxOff
\begin{document}
\makeProblems{1,3-7, 9, 14, 52}{Do something with #1.\par}
\end{document}

对于挑剔的哺乳动物,这里有一个理解负数的版本。负数可以自然地用符号输入,例如-4。在数字范围内,第一个-数字后面的第一个被解析为范围指示符,而不是符号,因此1-31,2,31--31,0,-1,-2,-3-1-3-1,0,1,2,3-1--3-1,-2,-3-1---3--是胡言乱语:-)

我不建议使用这种语法,因为它可能会有点令人困惑。我会选择另一个字符来表示范围,然后负数“就可以正常工作”。我还启用了反向范围,因此1-3收益1,2,33-1收益3,2,1

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_ryanj_list_tl
\tl_new:N \l_ryanj_sign_tl
\tl_new:N \l_ryanj_parsing_tl
\bool_new:N \l_ryanj_first_bool
\bool_new:N \l_got_range_bool
\bool_new:N \l_ryanj_ranged_bool
\int_new:N \l_ryanj_rangea_int
\int_new:N \l_ryanj_rangeb_int
\NewDocumentCommand \makeProblems { m +m }
  {
    \tl_clear:N \l_ryanj_list_tl
    \exp_args:Nx \clist_map_function:nN {#1} \__ryanj_parse_item:n
    \tl_map_inline:Nn \l_ryanj_list_tl {#2}
  }
\cs_new_protected:Npn \__ryanj_add_item:n #1
  { \tl_put_right:Nn \l_ryanj_list_tl { {#1} } }
\cs_generate_variant:Nn \__ryanj_add_item:n { V }
\cs_new_protected:Npn \__ryanj_parse_item:n #1
  {
    \tl_clear:N \l_ryanj_sign_tl
    \bool_set_true:N \l_ryanj_first_bool
    \bool_set_false:N \l_ryanj_ranged_bool
    \bool_set_false:N \l_got_range_bool
    \tl_set:Nn \l_ryanj_parsing_tl {#1}
    \__ryanj_parse:w #1 ~ \q_recursion_tail \q_recursion_stop
  }
\cs_new_protected:Npn \__ryanj_parse:w #1
  {
    \tl_if_single_token:nF {#1} { \__ryanj_abort_item:nnw { braced-item } {#1} }
    \quark_if_recursion_tail_stop_do:Nn #1
      { \__ryanj_parse_terminate: }
    \token_if_eq_charcode:NNTF - #1
      {
        \bool_if:NTF \l_ryanj_first_bool
          { \tl_set:Nn \l_ryanj_sign_tl { - } }
          {
            \bool_if:NTF \l_got_range_bool
              { \tl_set:Nn \l_ryanj_sign_tl { - } }
              { \bool_set_true:N \l_got_range_bool }
          }
        \__ryanj_parse:w
      }
      { \__ryanj_grab_number:w #1 }
  }
\cs_new_protected:Npn \__ryanj_grab_number:w #1
  {
    \quark_if_recursion_tail_stop_do:nn {#1} { \msg_error:nnn { ryanj } { invalid-number } {} }
    \__ryanj_if_number:nF {#1} { \__ryanj_abort_item:nnw { invalid-number } {#1} }
    \bool_if:NTF \l_ryanj_first_bool
      { \tex_afterassignment:D \__ryanj_after_first: \l_ryanj_rangea_int }
      { \tex_afterassignment:D \__ryanj_after_second: \l_ryanj_rangeb_int }
        = \l_ryanj_sign_tl #1
  }
\cs_new_protected:Npn \__ryanj_after_first:
  {
    \bool_set_false:N \l_ryanj_first_bool
    \tl_clear:N \l_ryanj_sign_tl
    \__ryanj_parse:w
  }
\cs_new_protected:Npn \__ryanj_after_second:
  {
    \bool_set_true:N \l_ryanj_ranged_bool
    \bool_set_false:N \l_got_range_bool
    \tl_clear:N \l_ryanj_sign_tl
    \__ryanj_parse_terminate:w
  }
\cs_new_protected:Npn \__ryanj_parse_terminate:w #1 \q_recursion_stop
  { \tl_trim_spaces_apply:nN {#1} \__ryanj_ckeck_leftover:n }
\cs_new_protected:Npn \__ryanj_ckeck_leftover:n #1
  {
    \quark_if_recursion_tail_stop:n {#1}
    \msg_error:nnn { ryanj } { invalid-number } {#1}
    \use_none:n \q_recursion_stop
    \__ryanj_parse_terminate:
  }
\cs_new_protected:Npn \__ryanj_parse_terminate:
  {
    \bool_if:NT \l_got_range_bool
      { \msg_error:nnn { ryanj } { invalid-number } { - } }
    \bool_if:NTF \l_ryanj_ranged_bool
      { \__ryanj_inject_range: }
      { \__ryanj_add_item:V \l_ryanj_rangea_int }
  }
\cs_new:Npn \__ryanj_inject_range:
  {
    % To allow reversed ranges:
    \int_compare:nNnT \l_ryanj_rangea_int > \l_ryanj_rangeb_int
      { \tl_set:Nn \l_ryanj_sign_tl { - } }
      { \tl_set:Nn \l_ryanj_sign_tl {   } }
    %
    \int_step_function:nnnN
      { \l_ryanj_rangea_int }
        { \l_ryanj_sign_tl 1 }
      { \l_ryanj_rangeb_int }
      \__ryanj_add_item:n
  }
\cs_new:Npn \__ryanj_abort_item:nnw #1 #2 #3 \q_recursion_stop
  { \msg_error:nnn { ryanj } {#1} {#2} }
\prg_new_conditional:Npnn \__ryanj_if_number:n #1 { T, F, TF }
  {
    \tl_if_empty:oTF
      { \tex_romannumeral:D - 0#1 \exp_stop_f: }
      { \prg_return_true: }
      { \prg_return_false: }
  }
\msg_new:nnn { ryanj } { invalid-number }
  { Invalid~number~`#1'~in~\tl_use:N \l_ryanj_parsing_tl. }
\msg_new:nnn { ryanj } { braced-item }
  { Invalid~braced~item~`#1'~in~\tl_use:N \l_ryanj_parsing_tl. }
\ExplSyntaxOff
\begin{document}
\makeProblems{-1,3-5,-3--5,-3-5,3--5,9,14,52}{Do something with #1.\par}
\end{document}

答案2

\documentclass{article}
\usepackage{listofitems,pgffor}
\newcommand\makeProblems[2]{%
  \setsepchar{,/-}%
  \readlist*\numlist{#1}%
  \def\z##1{#2\par}%
  \foreachitem\zz\in\numlist[]{%
    \ifnum\listlen\numlist[\zzcnt]=1\relax\z{\zz}\else
      \itemtomacro\numlist[\zzcnt,1]\tmpA
      \itemtomacro\numlist[\zzcnt,2]\tmpB
      \foreach\zzz in {\tmpA,...,\tmpB}{%
        \z{\zzz}}%
    \fi
  }%
}
\begin{document}
\makeProblems{1,3-7, 9, 14-16, 52}{Do something with #1.}
\end{document}

在此处输入图片描述

答案3

我映射给定的逗号分隔列表;检查每个项目,如果它包含连字符,则进行循环;无论如何,都会将整数添加到序列中。

最后,使用项目之间的分隔符扩展序列;可选择将此标记列表保存到宏中。

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\expandlist}{om}
 {
  \ryanj_expandlist:n { #2 }
  \IfNoValueTF { #1 }
   {
    \ryanj_expandlist_print:
   }
   {
    \ryanj_expandlist_store:N #1
   }
 }

\tl_new:N \l_ryan_expandlist_tl
\seq_new:N \l__ryan_expandlist_seq

\cs_new_protected:Nn \ryanj_expandlist:n
 {
  \seq_clear:N \l__ryan_expandlist_seq
  \clist_map_function:nN { #1 } \__ryan_expandlist_item:n
  \tl_set:Nx \l_ryan_expandlist_tl
   {
    \seq_use:Nnnn \l__ryan_expandlist_seq {~and~} { ,~ } { ,~and~ }
   }
 }

\cs_new_protected:Nn \__ryan_expandlist_item:n
 {
  \__ryan_expandlist_item:w #1 - - \q_stop
 }

\cs_new_protected:Npn \__ryan_expandlist_item:w #1 - #2 - #3 \q_stop
 {
  \tl_if_blank:nTF { #2 }
   {
    \seq_put_right:Nn \l__ryan_expandlist_seq { #1 }
   }
   {
    \int_step_inline:nnn { #1 } { #2 } { \seq_put_right:Nn \l__ryan_expandlist_seq { ##1 } }
   }
 }

\cs_new:Nn \ryanj_expandlist_print:
 {
  \tl_use:N \l_ryan_expandlist_tl
 }

\cs_new_protected:Nn \ryanj_expandlist_store:N
 {
  \tl_if_exist:NF #1
   {
    \tl_set_eq:NN #1 \l_ryan_expandlist_tl
   }
 }

\ExplSyntaxOff

\begin{document}

\expandlist{3-7, 9, 14, 52}

\expandlist{1}

\expandlist{1,4}

\expandlist{1-2}

\expandlist[\foo]{3-7, 9, 14, 52}

\texttt{\meaning\foo}

\end{document}

在此处输入图片描述

相关内容