Verificando uma entrada correspondente em um arquivo

Verificando uma entrada correspondente em um arquivo

Eu tenho um arquivo contendo uma lista, assim:

apple
orange
pear
pineapple

Como posso criar uma macro que verifique se um item está nessa lista. Por exemplo:

  • \isfruit{pear}retorna "1".
  • \isfruit{carrot}retorna "0".

Responder1

Se o arquivo não for muito longo, você pode fazer isso

\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}

Porém, não é expansível, então forneci um argumento opcional: um nome de sequência de controle onde armazenar o resultado.

Definição alternativa

Como observa Ahmed Musa, se o arquivo for longo, a leitura pode ser muito demorada, principalmente se o teste for realizado muitas vezes. Podemos explorararquivo catch:

\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

Agora \isfruitA{village.dat}{pear}irá imprimir 1 (após ter definido uma macro expandindo para o conteúdo de village.dat, com linhas separadas por |, que assumimos não aparecer nas strings). Se ligarmos

\isfruitA[\result]{village.dat}{orange}

o resultado do teste (0 ou 1) será colocado na macro \result. O arquivo será lido apenas uma vez. Alguns pacotes comostringspoderia ser usado para evitar o teste complicado com \ifin@.

Responder2

Vou tentar responder a esta pergunta. Talvez alguém consiga melhorar minha resposta.

Eu criei a macro \isfruitdesta forma que a macro lê o arquivo enquanto enlinecharestá definido como -1. Cada linha será lida e salva em uma lista. Depois disso comparo a lista com um 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}

Responder3

@Marco Daniel: Existem espaços na sua macro que não são anulados pelo internal \endlinechar=-1. Eles aparecerão no documento no modo horizontal. Além disso, a atribuição \endlinechar=-1%deveria ter sido \endlinechar=-1 %. Além disso, usarei a ramificação \xifinlistfora do \isfruitcomando, para dar ao usuário \isfruita oportunidade de alterar os dois retornos de chamada. E seu separador de lista (usado por padrão em \xifinlist) pode estar presente na lista original a ser testada.

\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

A solução acima é inaceitavelmente ineficiente. Se a lista tiver dez páginas e o token de interesse for o primeiro da lista, primeiro devemos ler o documento inteiro antes de verificar a presença da string de teste. Aqui está uma implementação mais 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}

Responder4

LuaTeX é ideal para tal tarefa. Abaixo está uma solução baseada em luatex no 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

Isso usa io:loaddata()a função from l-io.luapara carregar o conteúdo de um arquivo, a commandstabela para separação de namespace dos comandos lua e commands.testcasea função para fornecer a funcionalidade do-if-else. A correspondência real é feita usando string.finda função. Eu uso oPadrão de Fronteirapara corresponder aos limites das palavras.

informação relacionada