2

I'm trying to make the following code work correctly. I'm using command line call to python to check whether a number is in a list (no clue how to do this in pure LaTeX(?)). I then want to check whether the result is 'True' or 'False'.

\documentclass{article}

\newcommand{\mysequence}{[2,3,5,7,11,13,17,19,23,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101]}
\makeatletter
\newcommand\isprime[1]{\@@input|"python -c print((#1)in\mysequence)"}
\makeatother

\newcommand{\compare}[1]{%
\def\test{\isprime{#1}}
\def\true{True}
\def\false{False}

Test: \test\par
\ifx\test\true
#1 is PRIME! :D
\else
#1 is not prime ;(
\fi
\vspace{10pt}
}%

\begin{document}
\compare{3} % 3 is prime
\par
\compare{4} % 4 is not prime
\end{document}

As is, I get a correct value of \test, but the \ifx comparison does not seem to expand the \@@input and I get a wrong result. The output looks like that:

enter image description here

I believe I could fix this by using \edef instead of \def, but we I try i make LaTeX very angry and it goes a little bit too far past my knowlegde of it to fix it.

2

If you really want a solution that goes through an external program, you can get inspiration from this answer of egreg and do:

\documentclass{article}
\usepackage{etoolbox}
\usepackage{pgffor}

\makeatletter

\newcommand{\mysequence}{%
  [2,3,5,7,11,13,17,19,23,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101]%
}

\newcommand*{\isInList@i}[2]{%
  \begingroup
  \endlinechar=\m@ne\everyeof{\noexpand}%
  \edef\x{\@@input|"python3 -c 'print(#2 in #1)'" }%
  \expandafter
  \endgroup
  \expandafter
  \ifstrequal\expandafter{\x}{True}{%
    #2 is in the list}{%
    #2 is not in the list}.%
}

\newcommand*{\isInList}[1]{%
  \expandafter\isInList@i\expandafter{\mysequence}{#1}%
}

\makeatother

\setlength{\parindent}{0pt}

\begin{document}
\foreach \i in {1,...,23} {%
  \isInList{\i}\par
}
\end{document}

Screenshot

(I didn't want to say “prime” here because the list contained in \mysequence is limited; that would lead to say, for instance, that 103 isn't a prime number...)

Note for the passersby: compiling this requires giving the -shell-escape option to LaTeX (pdflatex, lualatex, xelatex, etc.); otherwise, it will refuse to run the shell command python3 -c '...' contained in the code (this security measure ensures that classes, packages and documents don't run arbitrary shell commands behind your back).

  • Cool! Thats quite in line with my original approach. Could you explain the \endlinechar=\m@ne\everyeof{\noexpand}% line? Also, why is the chain of \noexpands starting within the group. Could you not get rid of the first \expandafter after the \edef? – MarcinKonowalczyk Jun 24 at 8:09
  • 1
    \m@ne is a shorthand for -1 defined in plain.tex (\countdef\m@ne=22 \m@ne=-1). It is faster and has lighter memory usage than typing -1 directly. So, the first thing is identical to \endlinechar=-1 \everyeof{\noexpand}. This part is rather tricky. I got it from egreg's answer and you can find some explanations here and there (the latter about \scantokens, but it's all closely related). Basically, newline characters from the input... – frougon Jun 24 at 9:38
  • ... (here, from the output of the shell command which becomes input for TeX) are turned into the character with code given by \endlinechar, unless this is negative or greater than 255 (TeXbook p. 48). If you play with \endlinechar=67 % TeX's internal code for C and \edef\x{\@@input|"python3 -c 'print(chr(10), chr(65), chr(10), chr(66))'" }, you should see Runaway definition? ->CACBC ! File ended while scanning definition of \x. The end-of-line produced by Python (chr(10)) got turned into C. But it seems vey tricky to get this working without errors when \endlinechar... – frougon Jun 24 at 9:43
  • ... is between 0 and 255. The rest is easier to explain (all from me). The chain of \expandafters starting inside the group expands \x while its definition is still active (\endgroup hasn't gotten into TeX's stomach yet, i.e. hasn't been “executed”), so that the first argument of \ifstrequal will be exactly the output of the shell command when TeX later expands \ifstrequal. Then the \expandafters disappear (they have been expanded), \endgroup becomes the next token to process by TeX. As it is unexpandable, it gets executed, which ends the group and restores \x to the status... – frougon Jun 24 at 9:50
  • ... it had before \begingroup. So, these \expandafters ensure that everything is quite clean after the \endgroup has been digested by TeX. If you remove the first \expandafter after the \edef, \endgroup will be executed before TeX got any chance to expand the \x in the first argument of \ifstrequal. So, when \ifstrequal is expanded, its first argument will be the control sequence token \x (which is not what you want; moreover, the definition of \x from the \edef will already be lost since \endgroup was digested earlier in this scenario). – frougon Jun 24 at 9:59
2

Set up to use the OP's syntax for \mysequence, though I think space separated has certain advantages over comma separated, were I to do it.

\documentclass{article}
\usepackage{listofitems}
\newcommand{\mysequence}{[2,3,5,7,11,13,17,19,23,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101]}
\newcommand\compare[1]{%
  \setsepchar{,#1,||[#1,||,#1]}%
  \readlist\mylist{\mysequence}%
  \ifnum\listlen\mylist[]>1 #1 is PRIME!\else #1 is not prime\fi
}

\begin{document}

\compare{2}

\compare{3} % 3 is prime

\compare{4} % 4 is not prime

\compare{101}
\end{document}

enter image description here

SUPPLEMENT

To handle the case where the argument can also be macro (e.g., \thepage, at least in the arabic case) that fully expands to an integer, try this:

\documentclass{article}
\usepackage{listofitems}
\newcommand{\mysequence}{[2,3,5,7,11,13,17,19,23,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101]}
\newcommand\compare[1]{\edef\tmp{#1}\expandafter\compareaux\expandafter{\tmp}}
\newcommand\compareaux[1]{%
  \setsepchar{,#1,||[#1,||,#1]}%
  \readlist\mylist{\mysequence}%
  \ifnum\listlen\mylist[]>1 #1 is PRIME!\else #1 is not prime\fi
}

\begin{document}

\compare{2}

\compare{3} % 3 is prime

\compare{4} % 4 is not prime

\compare{101}

\compare{\thepage}
\end{document}

If one did not want to rely on \edef, but was satisfied to limit the macro to a single expansion, one could instead define

\newcommand\compare[1]{\expandafter\compareaux\expandafter{#1}}
  • This is significanlty simpler... It does not work, however, when I try to \compare{\thepage} (?). I realise its probably something to do with a completly different part of latex which I also don't understand. – MarcinKonowalczyk Jun 20 at 17:25
  • 1
    @MarcinKonowalczyk That is because \thepage does not contain a number, but a command: \csname @arabic\endcsname \c@page. You need to do two extra things: 1) fully expand \thepage as in \edef\tmp{\thepage} and 2) Provide the number, not the macro to \compare, as in \expandafter\compare\expandafter{\tmp}. – Steven B. Segletes Jun 20 at 17:30
  • @MarcinKonowalczyk Please see my SUPPLEMENT. – Steven B. Segletes Jun 20 at 17:34
  • That works perfectly. Thank you! I dont undertand the use of \expandafter\command\expandafter{\argument}, but I guess I'll go back to the \expandafter tutorial again. :) – MarcinKonowalczyk Jun 20 at 17:42
  • I was hesitant to accept the answer, as it technically circumvents the probelm rather than solving it. I feel, however, that it would be plainly unfair after an excellent answer and very much on-topic discussion of expansion. – MarcinKonowalczyk Jun 20 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.