Suchen nach einem übereinstimmenden Eintrag innerhalb einer Datei

Suchen nach einem übereinstimmenden Eintrag innerhalb einer Datei

Ich habe eine Datei mit einer Liste wie dieser:

apple
orange
pear
pineapple

Wie kann ich ein Makro erstellen, das überprüft, ob ein Element auf dieser Liste steht? Beispiel:

  • \isfruit{pear}gibt "1" zurück.
  • \isfruit{carrot}gibt "0" zurück.

Antwort1

Wenn die Datei nicht zu lang ist, können Sie

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

Es ist jedoch nicht erweiterbar, daher habe ich ein optionales Argument bereitgestellt: einen Steuersequenznamen, in dem das Ergebnis gespeichert werden soll.

Alternative Definition

Wie Ahmed Musa bemerkt, kann das Lesen einer langen Datei sehr zeitaufwändig sein, insbesondere wenn der Test viele Male durchgeführt wird. Wir können Folgendes ausnutzen:Fangdatei:

\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

Jetzt \isfruitA{village.dat}{pear}wird 1 ausgegeben (nachdem ein Makro definiert wurde, das auf den Inhalt von erweitert wird village.dat, wobei die Zeilen durch getrennt sind |, von denen wir annehmen, dass sie nicht in den Zeichenfolgen vorkommen). Wenn wir aufrufen

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

Das Ergebnis des Tests (0 oder 1) wird in das Makro geschrieben \result. Die Datei wird nur einmal gelesen. Einige Pakete wiexstringskönnte verwendet werden, um den umständlichen Test mit zu vermeiden \ifin@.

Antwort2

Ich werde versuchen, diese Frage zu beantworten. Vielleicht kann jemand meine Antwort verbessern.

Ich habe das Makro \isfruitso aufgebaut, dass es die Datei liest, während die enlinecharauf gesetzt ist -1. Jede Zeile wird ausgelesen und in einer Liste gespeichert. Anschließend vergleiche ich die Liste mit einem Argument.

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

Antwort3

@Marco Daniel: In Ihrem Makro gibt es Leerzeichen, die durch das interne nicht aufgehoben werden \endlinechar=-1. Sie werden im Dokument im horizontalen Modus angezeigt. Außerdem hätte die Zuweisung \endlinechar=-1%lauten müssen \endlinechar=-1 %. Außerdem werde ich die Verzweigung von \xifinlistaußerhalb des \isfruitBefehls nehmen, um dem Benutzer \isfruitdie Möglichkeit zu geben, die beiden Rückrufe zu ändern. Und Ihr Listentrennzeichen (wie es standardmäßig in verwendet wird \xifinlist) könnte in der ursprünglichen zu testenden Liste vorhanden sein.

\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

Die obige Lösung ist inakzeptabel ineffizient. Wenn die Liste zehn Seiten lang ist und das betreffende Token das erste in der Liste ist, müssen wir zuerst das gesamte Dokument lesen, bevor wir nach dem Vorhandensein der Testzeichenfolge suchen. Hier ist eine effizientere Implementierung:

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

Antwort4

LuaTeX ist für eine solche Aufgabe ideal geeignet. Unten finden Sie eine auf LuaTeX basierende Lösung in 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

Dies verwendet io:loaddata()die Funktion from, l-io.luaum den Inhalt einer Datei zu laden, die commandsTabelle zur Namensraumtrennung von Lua-Befehlen und commands.testcasedie Funktion, um die Do-if-else-Funktionalität bereitzustellen. Der eigentliche Abgleich erfolgt mithilfe der string.findFunktion. Ich verwende dieGrenzmusterum Wortgrenzen anzupassen.

verwandte Informationen