Проверка соответствия записи в файле

Проверка соответствия записи в файле

У меня есть файл, содержащий список, например такой:

apple
orange
pear
pineapple

Как создать макрос, который проверяет, есть ли элемент в этом списке. Например:

  • \isfruit{pear}возвращает «1».
  • \isfruit{carrot}возвращает «0».

решение1

Если файл не слишком длинный, можно обойтись

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

Однако его нельзя расширить, поэтому я предоставил необязательный аргумент: имя управляющей последовательности, где следует сохранить результат.

Альтернативное определение

Как замечает Ахмед Муса, если файл длинный, его чтение может занять очень много времени, особенно если тест выполняется много раз. Мы можем использоватьфайл захвата:

\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

Теперь \isfruitA{village.dat}{pear}выведет 1 (после определения макроса, расширяющегося до содержимого village.dat, со строками, разделенными |, которые, как мы предполагаем, не появляются в строках). Если мы вызовем

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

результат теста (0 или 1) будет помещен в макрос \result. Файл будет прочитан только один раз. Некоторые пакеты, такие какxstringsможно использовать, чтобы избежать громоздкого теста с \ifin@.

решение2

Я попробую ответить на этот вопрос. Может быть, кто-то сможет улучшить мой ответ.

Я создал макрос \isfruitтаким образом, что макрос читает файл во время того, как enlinecharустановлено значение -1. Каждая строка будет прочитана и сохранена в списке. После этого я сравниваю список с аргументом.

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

решение3

@Marco Daniel: В вашем макросе есть пробелы, которые не аннулируются внутренним \endlinechar=-1. Они будут отображаться в документе в горизонтальном режиме. Более того, назначение \endlinechar=-1%должно было быть \endlinechar=-1 %. Кроме того, я вынесу ветвление за \xifinlistпределы \isfruitкоманды, чтобы дать пользователю \isfruitвозможность изменить два обратных вызова. И ваш разделитель списка (используемый по умолчанию в \xifinlist) может присутствовать в исходном списке для тестирования.

\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

Вышеуказанное решение неприемлемо неэффективно. Если список состоит из десяти страниц, а интересующий нас токен — первый в списке, нам придется сначала прочитать весь документ, прежде чем проверять наличие тестовой строки. Вот более эффективная реализация:

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

решение4

LuaTeX идеально подходит для такой задачи. Ниже представлено решение на основе luatex в 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

Это использует io:loaddata()функцию из l-io.luaдля загрузки содержимого файла, commandsтаблицу для разделения пространства имен команд lua и commands.testcaseфункцию для предоставления функциональности do-if-else. Фактическое соответствие выполняется с помощью string.findфункции. Я используюПограничный узордля соответствия границам слов.

Связанный контент