Comprobar si hay una entrada coincidente dentro de un archivo

Comprobar si hay una entrada coincidente dentro de un archivo

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 \isfruitde esta manera que la macro lee el archivo mientras enlinecharestá 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 \xifinlistexterna del \isfruitcomando para darle al usuario \isfruitla 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.luapara cargar el contenido de un archivo, la commandstabla para la separación del espacio de nombres de los comandos lua y commands.testcasela función para proporcionar la funcionalidad do-if-else. La coincidencia real se realiza mediante string.findla función. Yo uso elPatrón de fronterapara hacer coincidir los límites de las palabras.

información relacionada