
Tengo un archivo que contiene una lista, como esta:
apple
orange
pear
pineapple
¿Cómo puedo crear una macro que verifique si un elemento está en esa lista? P.ej:
\isfruit{pear}
devuelve "1".\isfruit{carrot}
devuelve "0".
Respuesta1
Si el archivo no es demasiado largo, puedes hacerlo con
\documentclass{article}
\makeatletter
\newread\@readisfruit
\newcommand\isfruit[3][]{%
\begingroup\endlinechar=\m@ne
\openin\@readisfruit=#2
\def\@tempa{#3}%
\def\@result{0}%
\loop\unless\ifeof\@readisfruit
\read\@readisfruit to \@tempb
\ifx\@tempa\@tempb\def\@result{1}\fi
\repeat
\closein\@readisfruit
\edef\x{\endgroup\if!\noexpand#1!\@result\else\edef\noexpand#1{\@result}\fi}\x}
\makeatother
\begin{document}
\isfruit{village.dat}{pear}
\isfruit[\result]{village.dat}{carrot}\show\result
\end{document}
Sin embargo, no es ampliable, por lo que proporcioné un argumento opcional: un nombre de secuencia de control donde almacenar el resultado.
Definición alternativa
Como observa Ahmed Musa, si el archivo es largo, leerlo puede llevar mucho tiempo, especialmente si la prueba se realiza muchas veces. podemos explotararchivo de captura:
\usepackage{catchfile}
\CatchFileDef{\village}{village.dat}{\endlinechar=`| }
\makeatletter
\newcommand{\isfruitA}[3][]{%
\ifcsname loaded@#2\endcsname\else
\expandafter\CatchFileDef\csname loaded@#2\endcsname{#2}{\endlinechar=`| }%
\fi
\begingroup\expandafter\let\expandafter\@tempa\csname loaded@#2\endcsname
\edef\x{\endgroup\noexpand\in@{\unexpanded{#3}|}{\unexpanded\expandafter{\@tempa}}}\x
\ifin@ \def\@result{1}\else \def\@result{0}\fi
\if!\noexpand#1!\@result\else\edef#1{\@result}\fi}
\makeatother
Ahora \isfruitA{village.dat}{pear}
imprimirá 1 (después de haber definido una macro expandiéndose al contenido de village.dat
, con líneas separadas por |
, que suponemos no aparece en las cadenas). si llamamos
\isfruitA[\result]{village.dat}{orange}
el resultado de la prueba (0 o 1) se colocará en la macro \result
. El archivo se leerá solo una vez. Algún paquete comocuerdas xpodría usarse para evitar la engorrosa prueba con \ifin@
.
Respuesta2
Intentaré responder a esta pregunta. Quizás alguien pueda mejorar mi respuesta.
Creé la macro \isfruit
de esta manera que la macro lee el archivo mientras enlinechar
está configurado en -1
. Cada línea será leída y guardada en una lista. Después de esto comparo la lista con un argumento.
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\usepackage{etoolbox}
\newread\InputFruit
\newcommand*\isfruit[2]{%
\begingroup%
\def\MyList{}
\openin\InputFruit=#1
\endlinechar=-1%
\loop\unless\ifeof\InputFruit
\read\InputFruit to \reserveda
\listxadd\MyList{\reserveda}
\repeat
\closein\InputFruit
\xifinlist{#2}{\MyList}{in list}{not in list}
\endgroup%
}%
\begin{document}
\isfruit{fruit.tex}{apple}
\isfruit{fruit.tex}{foo}
\end{document}
Respuesta3
@Marco Daniel: Hay espacios en tu macro que no están anulados por el archivo interno \endlinechar=-1
. Aparecerán en el documento en modo horizontal. Además, la tarea \endlinechar=-1%
debería haber sido \endlinechar=-1 %
. Además, tomaré la bifurcación \xifinlist
externa del \isfruit
comando para darle al usuario \isfruit
la oportunidad de cambiar las dos devoluciones de llamada. Y su separador de lista (como se usa de forma predeterminada en \xifinlist
) puede estar presente en la lista original que se va a probar.
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\begingroup
\catcode`\|=3
\endlinechar=-1
\makeatletter
\gdef\ifinfruitlist#1#2{%
\begingroup
\endlinechar=-1
\def\MyList{}
\openin\@inputcheck=#1 %
\loop\unless\ifeof\@inputcheck
\read\@inputcheck to \reserveda
\edef\MyList{\ifx\MyList\@empty\else
\unexpanded\expandafter{\MyList}|
\fi\unexpanded\expandafter{\reserveda}}
\repeat
\closein\@inputcheck
\@expandtwoargs\in@{|#2|}{|\unexpanded\expandafter{\MyList}|}
\expandafter\endgroup
\ifin@\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\endgroup
\begin{document}
\ifinfruitlist{fruit.tex}{apple}{in list}{not in list}
\ifinfruitlist{fruit.tex}{foo}{in list}{not in list}
\end{document
La solución anterior es inaceptablemente ineficiente. Si la lista tiene diez páginas y el token de interés es el primero en la lista, primero debemos leer el documento completo antes de verificar la presencia de la cadena de prueba. Aquí hay una implementación más eficiente:
\documentclass{article}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\makeatletter
\gdef\ifinfruitlist#1#2{%
\begingroup
\def\reserved@b{#2}%
\endlinechar=-1 %
\openin\@inputcheck=#1 %
\@tempswafalse\@testfalse
\def\do{%
\if@tempswa
\closein\@inputcheck
\else
\ifeof\@inputcheck
\@tempswatrue
\else
\read\@inputcheck to\reserved@a
\ifx\reserved@a\reserved@b
\@testtrue\@tempswatrue
\fi
\fi
\expandafter\do
\fi
}%
\do
\expandafter\endgroup
\if@test\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\makeatother
\begin{document}
\ifinfruitlist{fruit.tex}{pear}{in list}{not in list}
\ifinfruitlist{fruit.tex}{foo}{in list}{not in list}
\end{document}
Respuesta4
LuaTeX es ideal para tal tarea. A continuación se muestra una solución basada en luatex en ConTeXt.
\startbuffer[fruits]
apple
orange
pear
pineapple
\stopbuffer
%% Save the contents of fruits buffer in \jobname-fruits.tmp
\savebuffer[fruits][fruits]
\startluacode
local data = {}
local find = string.find
function commands.doiffruitelse(file, fruit)
if not data[file] then
data[file] = io.loaddata(file) or ""
end
return commands.testcase(find(data[file], "%f[%a]"..fruit.."%f[%A]") ~= nil)
end
\stopluacode
\def\doiffruitelse#1%
{\ctxcommand{doiffruitelse("\jobname-fruits.tmp", "#1")}}
\def\isFruit#1%
{\doiffruitelse{#1}{1}{0}}
\starttext
\startlines
\isFruit{pear}
\isFruit{carrot}
\stoplines
\stoptext
Esto utiliza io:loaddata()
la función from l-io.lua
para cargar el contenido de un archivo, la commands
tabla para la separación del espacio de nombres de los comandos lua y commands.testcase
la función para proporcionar la funcionalidad do-if-else. La coincidencia real se realiza mediante string.find
la función. Yo uso elPatrón de fronterapara hacer coincidir los límites de las palabras.