Detectando subíndice en el argumento del comando

Detectando subíndice en el argumento del comando

He definido un comando lowque agrega un subíndice a un argumento:

\newcommand{\low}[1]{{#1}_{l_{\mathcal{A}}}}

Sin embargo, si el argumento de lowcontiene subíndices en sí (por ejemplo, en el caso de \low{\low{\Sigma}}), no es fácil ver que los subíndices introducidos por el exterior lowpertenecen a todo el argumento y no solo al primer subíndice. Por lo tanto, me gustaría introducir paréntesis automáticamente si el argumento de lowcontiene un subíndice (así \low{\low{\Sigma}}debería verse así \low{(\low{\Sigma})}).

Para lograr esto intenté lo siguiente usando el paquete xifthen:

\newcommand{\low}[1]{\ifthenelse{\isin{_}{#1}}{{(#1)}_{l_{\mathcal{A}}}}{{#1}_{l_{\mathcal{A}}}}}

Sin embargo, para mi sorpresa, este comando sólo algunas veces inserta los paréntesis. En particular, no lo hace en el ejemplo dado anteriormente. ¿Por qué sucede esto y qué puedo hacer para solucionarlo?

EDITAR: Parece que el problema es que \isinno desenrolla las definiciones de los comandos. @egreg ya proporcionó una respuesta que me permite verificar si hay llamadas anidadas \lowpero no funciona con argumentos que contienen otros comandos con subíndices. ¿Alguien tiene una solución que funcione para argumentos arbitrarios?

Respuesta1

En algunos casos, aplicar \protected@edefy \@onelevel@sanitizeantes de comprobar si hay un "stringified" _puede funcionar:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xifthen}

\makeatletter    
\DeclareRobustCommand\DetectUnderscore[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \@onelevel@sanitize\@tempa
  \expandafter\expandafter\expandafter\endgroup
  \expandafter\expandafter\expandafter\ifthenelse
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter\isin
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\string_%
  \expandafter}%
  \expandafter{%
  \@tempa}}{{(#1)}}{{#1}}%
}%
\newcommand\low[1]{%
  \DetectUnderscore{#1}%
  _{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma_b} \qquad 
\low{\low{\Sigma_b}} \qquad
\low{\low{\low{\Sigma_b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma_d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

ingrese la descripción de la imagen aquí

\expandafterhace que el siguiente token, si es expandible, se expanda exactamente una vez antes de que el siguiente token se expanda si es expandible. (La)TeX considera que el trabajo está \expandafterterminado cuando se realiza la expansión del penúltimo token. Por lo tanto, puede usar cadenas/secuencias de \expandafterpara que (La)TeX "salte" sobre k tokens para expandir primero el (k+1)-ésimo token.

\@onelevel@sanitize\macrocambia la definición de \macropara que \macroescupe una secuencia de tokens de caracteres del código de categoría 12 (otro) que se parece a la secuencia de tokens que habría sido "escupido" antes \macrode aplicar \@onelevel@sanitize. Es casi como redefinir \macrolo que se obtiene al aplicar la definición \stringa cada token \macro.

\protected@edefdefine una macro pero antes de hacerlo, expande todos los tokens expandibles del texto de definición, excepto aquellos que están definidos mediante \DeclareRobustCommandel token o están precedidos por él \protect. Se podría decir: \protected@edef"desenrolla" las definiciones de los tokens contenidos en su texto de definición antes de realizar la asignación.

\@tempaes una macro borrador que se define mediante \protected@edefla expansión del argumento #1con todas las definiciones #1"desenrolladas".

La \ifthenelse{\isin...}prueba no encuentra _que estén anidados entre llaves, ya que las llaves suelen tener una función especial. Por lo tanto, \@onelevel@sanitizese aplica para convertir todas las fichas, y por tanto también las llaves, en fichas de caracteres ordinarias e inofensivas del código de categoría 12 (otros) que no perturben la \ifthenelse{\isin...}prueba.


Y aquí hay una rutina que no verifica el guión bajo (en cadena) del código de categoría 12 (otro) a través dexifentos's \ifthenelse{\isin...}-thingie pero busca tokens del código de categoría 8 (subíndice) sin cadena.

La rutina se llama a sí misma de forma recursiva al examinar los tokens que forman el argumento.

La rutina aún no amplía el argumento; esto aún debe hacerse mediante \protected@edef.

\documentclass{article}
\usepackage{amsmath}

\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%.............................................................................
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
  {\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%.............................................................................
%% Check whether brace-balanced argument starts with a token of
%% category code 8 (subscript)
%%.............................................................................
%% \UD@CheckWhetherFirstTokenHasCatcodeSubscript{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that 
%%                         <argument which is to be checked> has a first
%%                         token of catcode 8>}%
%%                      {<Tokens to be delivered in case that
%%                         <argument which is to be checked> does not have
%%                         a first token of catcode 8>}%
%%
\newcommand\UD@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingSpace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@@CheckWhetherFirstTokenHasCatcodeSubscript
        \UD@ExtractFirstArg{#1}%
      }%
    }%
  }%
}%
\newcommand\UD@@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \expandafter\ifcat_#1%
  \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
  {\UD@exchange{ }{\expandafter}\UD@firstoftwo}%
  {\UD@exchange{ }{\expandafter}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument does contain underscore/tokens of 
%% category code 8 (subscript), no matter if nested in braces or not.
%%
%% \UD@CheckWhetherSubscriptTokens{<Argument which is to be checked>}%
%%                  {<Tokens to be delivered in case that 
%%                     <argument which is to be checked> contains
%%                     some token(s) of catcode 8 (subscript)>}%
%%                  {<Tokens to be delivered in case that
%%                     <argument which is to be checked> contains
%%                     no token of catcode 8 (subscript)>}%
%%
%%-----------------------------------------------------------------------------
\newcommand\UD@CheckWhetherSubscriptTokens{\romannumeral0\UD@@CheckWhetherSubscriptTokens}%
\newcommand\UD@@CheckWhetherSubscriptTokens[3]{%
  \UD@CheckWhetherNull{#1}{ #3}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@removespace#1}{#2}{#3}%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherSubscriptTokens
        \UD@ExtractFirstArg{#1}{ #2}{%
          \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }{%
        \UD@CheckWhetherFirstTokenHasCatcodeSubscript{#1}{ #2}{%
           \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }%
    }%
  }%
}%

\DeclareRobustCommand\UD@CallUD@CheckWhetherSubscriptTokensOnExpansion[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \expandafter\endgroup
  \expandafter\UD@CheckWhetherSubscriptTokens\expandafter{\@tempa}{{(#1)}}{{#1}}%
}%

\newcommand\low[1]{%
  \UD@CallUD@CheckWhetherSubscriptTokensOnExpansion{#1}_{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

% Let`s use | and \myunderscore in the same way as _ :

\catcode`\|=8

\let\myunderscore=_

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma\myunderscore b} \qquad 
\low{\low{\Sigma\myunderscore b}} \qquad
\low{\low{\low{\Sigma|b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma|d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

ingrese la descripción de la imagen aquí

Tenga en cuenta que esta rutina no tiene como objetivo detectar _tokens de caracteres (encadenados) del código de categoría 12 (otros), sino detectar todos los tokens de caracteres (sean explícitos o implícitos) del código de categoría 8 (subíndice).

Respuesta2

Una idea (no una solución sencilla) es colocar el argumento dentro de un cuadro y verificar su altura con la altura de un carácter que supone que no es demasiado alto para necesitar paréntesis, pero tampoco demasiado corto para agregar paréntesis a su \Sigma.

Y adivina cuál será nuestro argumento por defecto: \Sigma... P

El código (que contiene algunas pruebas) es este:

\documentclass{article}
\def\DefLowArg{$\Sigma$}
\let\oldDefLowArg\DefLowArg
\newsavebox{\myAbox}
\newsavebox{\myBbox}
\newcommand{\low}[2][\DefLowArg]{\savebox\myAbox{\vbox{#1}}\savebox\myBbox{\vbox{\ensuremath{#2}}}
\ifdim\dimexpr\ht\myAbox+\dp\myAbox<\dimexpr\ht\myBbox+\dp\myBbox\relax
\left({#2}\right)_{l_{\mathcal{A}}}
\else {#2}_{l_{\mathcal{A}}}\fi
}
\begin{document}

\[\low{\low{\Sigma}}\]

\[\low{\Sigma}\]
\[
\low{\sum_{i=3}^5 F(x)}
\]

\[
\low{\frac{F(x)}{x+5}}
\]

\[\low{F_x}\]

\[\low[1/4]{F(x)}\]

\[\low{x^2}\]

\[
\low{G_x}
\]

These commands may be should add without parentheses

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]

{\bfseries Solution 1 Add an tall optional argument in the command like: \verb|\low[/]{F(x)}|}

\[
\low[/]{g(z)}
\]
\[
\low[/]{F(x)}
\]

{\bfseries Solution 2 Change the Default argument \verb|\DefLowArg| to something tall enough (return with \verb|\let\DefLowArg\oldDefLowArg|):}

\xdef\DefLowArg{/}

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]
\let\DefLowArg\oldDefLowArg


{\bfseries And back to default}

\[
\low{F(X)}
\]

\end{document}

Eso produce:

ingrese la descripción de la imagen aquí

PD: Por supuesto, se deben agregar soluciones manuales en casos especiales, pero de todos modos, en su comando, estoy seguro de que tendrá excepciones para muchos casos.

información relacionada