6

I am trying to incorporate membership checking macros by marmot and Phelype Oleinik (thanks to both of them for their neat solutions) into an argument of another macro:

\singleBox{\convertNumPhelypeOleinik{1}{1}{0}{2}{1.46}}{200}{24}{13} % this fails
\singleBox{\convertNumMarmot{1}{1}{0}{2}{1.46}}{200}{24}{13} % this fails too

which results in the following error message:

Undefined control sequence. ...ypeOleinik{1}{1}{0}{2}{1.46}}{200}{24}{13}
Undefined control sequence. ...tNumMarmot{1}{1}{0}{2}{1.46}}{200}{24}{13}

The following is an MWE that demonstrates this issue. Could someone kindly help me with this?

\documentclass[tikz, border=0mm]{standalone}
\usepackage{tikz,xfp,expl3}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Solution by marmot https://tex.stackexchange.com/users/121799/marmot
% https://tex.stackexchange.com/a/501776/23594
\newif\ifmember
\makeatletter% for \@for see e.g. https://tex.stackexchange.com/a/100684/121799
%from https://tex.stackexchange.com/a/498576/121799
\newcommand{\MemberQ}[2]{\global\memberfalse%
    \@for\next:=#1\do{\ifnum\next=#2\global\membertrue\fi}}
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Solution by Phelype Oleinik https://tex.stackexchange.com/users/134574/phelype-oleinik
% https://tex.stackexchange.com/a/501777/23594
\ExplSyntaxOn
\prg_new_conditional:Npnn \afp_int_ismember:nn #1#2 { p, T, F, TF }
{ \__afp_ismember_loop:nw {#1} #2 , \q_recursion_tail , \q_recursion_stop }
\cs_new:Npn \__afp_ismember_loop:nw #1#2,
{
    \quark_if_recursion_tail_stop_do:nn {#2}
    { \prg_return_false: }
    \int_compare:nNnTF {#1} = {#2}
    { \use_i_delimit_by_q_recursion_stop:nw { \prg_return_true: } }
    { \__afp_ismember_loop:nw {#1} }
}
\ExplSyntaxOff
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\singleBox}[4]{%
    \fill(\fpeval{#1}mm,\fpeval{#2}mm) rectangle
    (\fpeval{#1} mm + \fpeval{#3} mm,\fpeval{#2} mm - \fpeval{#4} mm);
}

\newcommand{\convertNum}[5]{%
    \fpeval{#1<6 ? #3 + 9 + (#4 + #5)*(#1-1) + (#2-1) : 0}
}

\newcommand{\convertNumPhelypeOleinik}[5] %
{
    \ExplSyntaxOn
    \fpeval{ \afp_int_ismember_p:nn {1} {1,2,3,4,5} ? #3 + 9 + (#4 + #5)*(#1-1) + (#2-1) : 0 }
    \ExplSyntaxOff
}

\newcommand{\convertNumMarmot}[5] % 
{
    \MemberQ{1,2,3,4,5,6}{#1}
    \ifmember \fpeval{#3 + 9 + (#4 + #5)*(#1-1) + (#2-1)} \fi
}


\begin{document}

    % Testing membeship using Phelype Oleinik's method
    \ExplSyntaxOn
    \fpeval{ \afp_int_ismember_p:nn {1} {1,2,3,4,5} ? 123 : 321 }\par
    \ExplSyntaxOff

    % Testing membeship using marmot's method
    \MemberQ{1,2,3,4}{2}
    \ifmember 2 is in list \fi

    \MemberQ{1,2,3,4}{5}
    \ifmember 5 is in list\else%
    5 is not in the list\fi

    \begin{tikzpicture}
        \draw[draw=black] (0mm,0mm) rectangle (300 mm, 400 mm);
        \singleBox{100}{200}{24}{13} % This works just fine
        \singleBox{\convertNum{1}{1}{0}{2}{1.46}}{200}{24}{13} % This work just fine

        \singleBox{\convertNumPhelypeOleinik{1}{1}{0}{2}{1.46}}{200}{24}{13} % this fails
        \singleBox{\convertNumMarmot{1}{1}{0}{2}{1.46}}{200}{24}{13} % this fails too
    \end{tikzpicture}

\end{document}
  • 2
    You're placing \ExplSyntaxOn and \ExplSyntaxOff in the wrong place; they should be around the definition of \convertNumPhelypeOleinik, not inside it. The macro proposed by marmot is not expandable, so it cannot appear in the argument to \fpeval. – egreg Jul 28 '19 at 21:44
  • @egreg You're the best! It worked beautifully. – shashashamti2008 Jul 28 '19 at 21:49

2 Answers2

8

The macro proposed by our resident marmot is nice, but cannot be used in \fpeval as it relies to non expandable actions (\memberfalse and \membertrue).

The tags \ExplSyntaxOn and \ExplSyntaxOff are similar to \makeatletter and \makeatother: they have to surround the code where the expl3 syntax is used, not be inside the code.

A better coding:

\documentclass[border=0mm]{standalone}
\usepackage{tikz,xfp,xparse}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Solution by Phelype Oleinik https://tex.stackexchange.com/users/134574/phelype-oleinik
% https://tex.stackexchange.com/a/501777/23594
\ExplSyntaxOn
\prg_new_conditional:Npnn \afp_int_ismember:nn #1#2 { p, T, F, TF }
  {
    \__afp_ismember_loop:nw {#1} #2 , \q_recursion_tail , \q_recursion_stop
  }
\cs_new:Npn \__afp_ismember_loop:nw #1#2,
  {
    \quark_if_recursion_tail_stop_do:nn {#2}
    { \prg_return_false: }
    \int_compare:nNnTF {#1} = {#2}
    { \use_i_delimit_by_q_recursion_stop:nw { \prg_return_true: } }
    { \__afp_ismember_loop:nw {#1} }
  }
\NewExpandableDocumentCommand{\convertNum}{mmmmm}
  {
    \fp_eval:n
      {
         \afp_int_ismember_p:nn {1} {1,2,3,4,5} ? #3 + 9 + (#4 + #5)*(#1-1) + (#2-1) : 0
      }
  }
\ExplSyntaxOff
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\singleBox}[4]{%
    \fill(\fpeval{#1}mm,\fpeval{#2}mm) rectangle
    (\fpeval{#1} mm + \fpeval{#3} mm,\fpeval{#2} mm - \fpeval{#4} mm)
}

\begin{document}

    \begin{tikzpicture}
        \draw[draw=black] (0mm,0mm) rectangle (300 mm, 400 mm);
        \singleBox{100}{200}{24}{13};
        \singleBox{\convertNum{1}{1}{0}{2}{1.46}}{200}{24}{13};
    \end{tikzpicture}

\end{document}
egreg
  • 1,121,712
8

It is true that my macro is "not expandable" BUT I would never ever use \fpeval in coordinates of a tikzpicture. This is because TikZ has a very powerful parser, so you can just pass expressions to TikZ and it parses them automatically, so you can just ignore all this "hey, my stuff is expandable" discussion altogether. In particular, it is rather straightforward to declare a function memberQ that can be parsed like any other function. The resulting code is IMHO much more elegant than a wild mix of \fpeval and ordinary TikZ parsings.

\documentclass[tikz, border=0mm]{standalone}
\makeatletter
\pgfmathdeclarefunction{memberQ}{2}{%
  \begingroup%
    \edef\pgfutil@tmpb{0}%
    \edef\pgfutil@tmpa{#2}%
    \expandafter\pgfmath@member@i\pgfutil@firstofone#1\pgfmath@token@stop
    \edef\pgfmathresult{\pgfutil@tmpb}%
    \pgfmath@smuggleone\pgfmathresult%
  \endgroup}
\def\pgfmath@member@i#1{%
    \ifx\pgfmath@token@stop#1%
    \else
      \ifnum#1=\pgfutil@tmpa\relax%
      \gdef\pgfutil@tmpb{1}%
      %\typeout{#1=\pgfutil@tmpa}
      \fi%
      \expandafter\pgfmath@member@i
    \fi}    
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\singleBox}[4]{%
    \fill({(#1)*1mm},{(#2)*1mm}) rectangle
    ({(#1+#3)*1mm},{(#2-#4)*1mm});
}

\begin{document}
    \begin{tikzpicture}
        \draw[draw=black] (0mm,0mm) rectangle (300 mm, 400 mm);
        \singleBox{100}{200}{24}{13} % This works just fine

        \pgfmathparse{memberQ({1,2,3,4,5,6,7,8,9,10,11},3)}
        \typeout{\pgfmathresult}
        \singleBox{ifthenelse(memberQ({1,2,3,5},3),200,300)}{200}{24}{13} % This works just fine
    \end{tikzpicture}
\end{document}

ADDENDUM: a memberQ function that seems to work for arbitrary lists.

\documentclass[tikz, border=0mm]{standalone}
\makeatletter
\pgfmathdeclarefunction{memberQ}{2}{%
  \begingroup%
    \edef\pgfutil@tmpb{0}%
    \edef\pgfutil@tmpa{#2}%
    \expandafter\pgfmath@member@i\pgfutil@firstofone#1\pgfmath@token@stop
    \edef\pgfmathresult{\pgfutil@tmpb}%
    \pgfmath@smuggleone\pgfmathresult%
  \endgroup}
\def\pgfmath@member@i#1{%
    \ifx\pgfmath@token@stop#1%
    \else
      \edef\pgfutil@tmpc{#1}%
      \ifx\pgfutil@tmpc\pgfutil@tmpa\relax%
      \gdef\pgfutil@tmpb{1}%
      %\typeout{#1=\pgfutil@tmpa}%
      \fi%
      \expandafter\pgfmath@member@i
    \fi}    
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}
 \pgfmathparse{memberQ({3,4,5},3)}\pgfmathresult

 \pgfmathparse{memberQ({3,4,5},1)}\pgfmathresult

 \pgfmathparse{memberQ({"a","b","c"},1)}\pgfmathresult

 \pgfmathparse{memberQ({"a","b","c"},"a")}\pgfmathresult
\end{document}
  • 3
    I just said that your macro cannot go inside \fpeval, Expandability has certain advantages, but as TikZ is written, it is most of the time not required. – egreg Jul 28 '19 at 22:47
  • 2
    @egreg I agree on all this but my point is just that I would avoid mixing xfp and TikZ and would in particular not recommend it for newcomers. But it is true that outside of TikZ and friends all this xparse stuff is just great. –  Jul 28 '19 at 22:49
  • 2
    Happy to see you came out of your burrow, by the way! – egreg Jul 28 '19 at 22:51
  • 2
    @egreg Thanks, but this is only till I awarded all my points, after that I will delete my account. (One can only waste up to 1500 points per day.) Until then, when trapped on a airport, I will occasionally answer questions. BTW, if you have recommendations for original answers that deserve extra points, I am all ears. –  Jul 28 '19 at 22:53
  • 1
    @marmot Deleting your account? [Sobs] Is there any link I missed where you explain why, assuming you decided to publish it (if not, I'll respect your decision, of course)? Thanks for all your great contributions—that's invaluable. Whatever comes next, I wish you the best. – frougon Jul 29 '19 at 10:29
  • @frougon Did you ever have a, err, let's say noteworthy discussion? I bet you did. I am sick of those. I am sick of getting insulted. And I disagree with the actions of one of the moderators. So I quit. –  Jul 29 '19 at 19:30
  • @marmot I'm really sorry you had to go through this. I see you're upset by some decisions of a moderator. I am not in a position to comment, I'm just sorry you had to go through this, hope you might reconsider and again thank you for all your great advice. By the way, like everyone I am a bit angry when I've done hard work and the OP doesn't accept the answer (assuming there was no better one), but in your prev. comment, you managed to find the only insane discussion I had with a user that I suppose had enough tech. background to avoid all the insanity in the first place. Congrats for that! – frougon Jul 29 '19 at 19:32
  • @frougon Well, this is how things are here. I guess on the long run there will be other forums, not run by a company. In the present setting it is not in the interest of the company/this site to reward anyone for posting something original, nor to bother users with the requirement to give credit to the sources of their answers. The only purpose is to involve as many users as they can involve. Anyway, it was really nice interacting with you! –  Jul 29 '19 at 19:39
  • @marmot Thanks, I enjoyed talking to you the same. I've become active here a bit late, but... I've learnt a lot from you, and kept notes. :-) Before deleting and rewriting this comment, I wrote something silly about comp.text.tex. Yes, it is not run by a company. But who is going to ask questions you'll write enlightening answers to? I believe it's kind of desertic now. This leaves only one viable solution: one of these days, the anger will have disminished, you'll think about all the other people (incl. moderators) whom you appreciate here, and maybe you'll reconsider. Hopefully. – frougon Jul 29 '19 at 20:02
  • @frougon Let's see what future brings. (Didn't I see your name on the tikz-pgf repository? Anyway, it was exciting to learn some TeX stuff, other things may be exciting, too... ;-) –  Jul 29 '19 at 20:08
  • @marmot Well, nothing very exciting, but I'm still an amateur in TikZ & PGF. I just manage to do a few things when I can take the time. Yes, there are other things in life than TeX. You can start learning Word and its built-in drawing module now. :-)) – frougon Jul 29 '19 at 20:16
  • @frougon It is still nice that you are doing it. (I may add the function memberQ to pgfmathfunctions.misc.code.tex, if Henri allows it. (It is only a small modification of dim, which has not yet found its way into the manual.) –  Jul 30 '19 at 02:27