使用宏定义数字范围时 foreach 循环出错

使用宏定义数字范围时 foreach 循环出错

我在实现一个循环来遍历一年中的月份和每个月的日期时遇到了问题。下面显示了我当前方法的一个简单代表性示例。

\documentclass{article} 

\usepackage{pgffor} 
\usepackage{ifthen}

\newcommand\Ndays[1]{\ifthenelse{\equal{#1}{February}}{28}%
                 {\ifthenelse{\equal{#1}{March}}{31}{30}}}   

\begin{document}

\foreach \m in {February,March,April}
  {
  month:\m \par
  \newcommand\Nd{\Ndays{\m}}
  ndays: \Nd \par
  \foreach \n in {1,...,31}
    {
    day:\n \par
    }
  }
\end{document}

显然,每个月的天数都不同,所以我决定通过“Ndays”宏定义一个将每个月与天数关联的字典。现在,我需要在上面的示例中为每个月包含正确的天数。如果我在内部循环中天真地用“\Nd”替换“31”,则使用 pdflatex 编译此代码时会发现错误:

! Undefined control sequence.
<argument> \equal 
                  {\m }{February}
l.23   }

我尝试了不同的宏定义和 \expandafter-s,但没有成功。此外,如果我将宏定义为:

\newcommand\Nd{28}

简单的替换不会引发错误。 对此有什么简单的解决方案和解释吗?

答案1

你可以试试这个。利用(这一次)在组中工作的事实,\foreach因此 LaTeX 不会对 提出投诉\newcommand因此,否则,人们将使用\renewcommand和初始\newcommand\foo{}

\documentclass{article} 

\usepackage{pgffor} 
\usepackage{ifthen}

\newcommand\Ndays[1]{\ifthenelse{\equal{#1}{February}}
                 {\newcommand\Nd{28}}%
                 {\ifthenelse{\equal{#1}{March}}
                     {\newcommand\Nd{31}}%
                     {\newcommand\Nd{30}}}}   

\begin{document}

\foreach \m in {February,March,April}
  {
  month:\m \par
  \Ndays{\m}%
  ndays: \Nd \par
  \foreach \n in {1,...,\Nd}
    {
    day:\n \par
    }
  }
\end{document}

答案2

明显的缺陷(至少对于那些在宏扩展方面有经验的人来说)是,它\Nd没有扩展到天数,而是扩展到了印刷天数。

以下宏以可扩展的方式将日期与月份关联起来(也考虑闰年)。

\documentclass{article}
\usepackage{multicol}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\printmonths}{O{\c_sys_year_int}m}
 {% #1 is an optional year
  % #2 is a comma separated list of months
  \clist_map_inline:nn { #2 }
   {
    \manual_print_month:nn { #1 } { ##1 }
   }
 }
\cs_new_protected:Nn \manual_print_month:nn
 {
  \par
  Month:~#2 \par
  Ndays:~\manual_print_days:nn { #1 } { #2 } \par
  \int_step_inline:nnnn { 1 } { 1 } { \manual_print_days:nn { #1 } { #2 } }
   {
    day:~##1 \par
   }
 }

\cs_new:Nn \manual_print_days:nn
 {
  \str_case:nn { #2 }
   {
    {January}{31}
    {February}{\manual_february:n { #1 }}
    {March}{31}
    {April}{30}
    {May}{31}
    {June}{30}
    {July}{31}
    {August}{31}
    {September}{30}
    {October}{31}
    {November}{30}
    {December}{31}
   }
 }

\cs_new:Nn \manual_february:n
 {
  \bool_if:nTF
   {
    \int_compare_p:n { \int_mod:nn { #1 } { 4 } != 0 }
    ||
    (
     \int_compare_p:n { \int_mod:nn { #1 } { 100 } = 0 }
     &&
     \int_compare_p:n { \int_mod:nn { #1 } { 400 } != 0 }
    )
   }
   { 28 } { 29 }
 }

\ExplSyntaxOff

\begin{document}

\begin{multicols}{3}

\printmonths[2100]{February,March,April}

\end{multicols}

\end{document}

除了二月比较复杂之外,宏很简单:我们将参数映射为逗号分隔的列表。对于每个项目,\manual_print_month:nn都会执行函数,从 1 步进到天数并进行打印。

在此处输入图片描述

您可以检查

\printmonths[2100]{February,March,April}

如下(无闰年)

在此处输入图片描述

\manual_february:n使用惰性求值条件的可能更有效的版本:

\cs_new:Nn \manual_february:n
 {
  \bool_lazy_or:nnTF
   {
    \int_compare_p:n { \int_mod:nn { #1 } { 4 } != 0 }
   }
   {
    \bool_lazy_and_p:nn
     {
      \int_compare_p:n { \int_mod:nn { #1 } { 100 } = 0 }
     }
     {
      \int_compare_p:n { \int_mod:nn { #1 } { 400 } != 0 }
     }
   }
   { 28 } { 29 }
 }

相关内容