
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 \isfruit
so aufgebaut, dass es die Datei liest, während die enlinechar
auf 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 \xifinlist
außerhalb des \isfruit
Befehls nehmen, um dem Benutzer \isfruit
die 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.lua
um den Inhalt einer Datei zu laden, die commands
Tabelle zur Namensraumtrennung von Lua-Befehlen und commands.testcase
die Funktion, um die Do-if-else-Funktionalität bereitzustellen. Der eigentliche Abgleich erfolgt mithilfe der string.find
Funktion. Ich verwende dieGrenzmusterum Wortgrenzen anzupassen.