3

I would like to create a table that shows the evaluations of a formula for some given input list.

My initial idea was to evaluate the formula using the fp package within a \@for loop. This however does not work within a tabular environment.

My current workaround is to use tabto for formatting but that's obviously not a sustainable way when I want to add longer numbers, have nice vertical or horizontal lines, or just center the whole thing.

Minimal working example using the workaround:

\documentclass{article}

\usepackage{tabto} % just for the workaround
\usepackage{fp}

\begin{document}
\noindent
$a$ \tabto{1cm} $b$ \tabto{2cm} $a+b$\\         % table header
\makeatletter
\FPset{a}{1}
\@for \b:={1, 1.5, 2, 42}\do{
    \FPeval{\result}{round(a+b:2)}
    \a \tabto{1cm} \b \tabto{2cm} \result \\    % table row
}
\makeatother
\end{document}
  • Note that I'm not looking for a 1..N loop but one that iterates a given list of floating-point values.
  • fp is not a hard requirement, suggest an alternative if it gets in the way.
  • I would appreciate a solution working with pdflatex instead of e.g. something Lua-based.

The Question: How do I generate something like this as an actual table?

2 Answers 2

3

Perhaps by using the pgfplotstable package

 \documentclass{article}
\usepackage{pgfplotstable}
\usepackage{booktabs}
\pgfplotsset{compat=1.18}
\pgfplotstableset{
    create on use/a/.style={create col/set={1}}, % define the a column
    create on use/b/.style={create col/set list={1, 1.5, 2, 42}}, % define the b column
    create on use/tempresult/.style={
         create col/expr={(\thisrow{a}+\thisrow{b})/2}}, % result=(a+b)/2 % define the result column for the temporary table
 %
    create on use/result/.style={
        create col/copy column from table={\temptable}{tempresult}} % define the result column as a copy of the tempresult from \temptable
}

 % create two new tables \temptable and \mytable
\pgfplotstablenew[columns={a,b,tempresult}]{4}\temptable % 4 rows

\pgfplotstablenew[columns={result}]{4}\mytable

\begin{document}

\section {all columns} % 
%
% typeset the temporary table if needed
\pgfplotstabletypeset[every last row/.style={after row=\bottomrule},
    every head row/.style={before row=\toprule,after row=\midrule},
    columns/temp/.style={column name={result}}]\temptable

\section{Only the result column}
%
% typeset the final table
\pgfplotstabletypeset[every last row/.style={after row=\bottomrule},
    every head row/.style={before row=\toprule,after row=\midrule},]\mytable

\end{document}

enter image description here

3
  • Can you elaborate a bit on how this works? Why is the temp row needed? Do we actually need version 1.18 or what is the working minimum? Is there a reasonably easy way to retrieve the length of list b to pass as an argument to `\pgfplotstablenew? And where can I find documentation on more math operations (sine, pow, ...)?
    – Seriously
    Feb 6, 2022 at 15:18
  • @Seriously. In case where the final table needs to contain only the result column I have created first a temporary table that contains both the input and the result columns then in a second time I have created the final table whose result column is a copy from the previously created table. I apologise if what I am writing is not very clear. English is not my natural language. Tell me and I will give you more explanations about the code. Concerning \pgfplotsset{compat=....} It is a recommendation for the package backward compatibilities just read the corresponding warning after compilation Feb 7, 2022 at 8:24
  • @Seriously. Of course the temporary table is not needed at all if you intend to include both the input and the result columns in the table. Feb 7, 2022 at 8:29
4

Here's a fairly general method for any (up to 9) variables.

\documentclass{article}
\usepackage{booktabs}

\ExplSyntaxOn

\NewDocumentCommand{\tableofvalues}{mmmm}
 {% #1 = list of variables
  % #2 = formula to compute in terms of the variables
  % #3 = formula in xfp terms
  % #4 = set of comma list separated values for the variables
  \seriously_tov_main:nnnn { #1 } { #2 } { #3 } { #4 }
 }
% example call
% \tableofvalues{a,b}{a+b}{round(#1+#2,2)}{
%   {1, 2.3, 4, -2}
%   {1, 1.5, 2, 42}
% }

\tl_new:N \l__seriously_tov_body_tl
\tl_new:N \l__seriously_tov_sgn_tl
\seq_new:N \l__seriously_tov_rows_seq
\seq_new:N \l__seriously_tov_args_seq
\clist_new:c { l__seriously_tov_1_clist }
\clist_new:c { l__seriously_tov_2_clist }
\clist_new:c { l__seriously_tov_3_clist }
\clist_new:c { l__seriously_tov_4_clist }
\clist_new:c { l__seriously_tov_5_clist }
\clist_new:c { l__seriously_tov_6_clist }
\clist_new:c { l__seriously_tov_7_clist }
\clist_new:c { l__seriously_tov_8_clist }
\clist_new:c { l__seriously_tov_9_clist }

\cs_new_protected:Nn \seriously_tov_main:nnnn
 {
  % compute the number of arguments
  \tl_set:Nx \l__seriously_tov_sgn_tl
   {
    \prg_replicate:nn { \clist_count:n { #1 } } { n }
   }
  % make a temporary function to do the computation
  \cs_set:cn { __seriously_tov_compute:\l__seriously_tov_sgn_tl } { \fp_eval:n { #3 } }
  % this is a mouthful, so we define a shorthand
  \cs_set_eq:Nc \__seriously_tov_compute:w { __seriously_tov_compute:\l__seriously_tov_sgn_tl }
  % we want to absorb the values traversing the lists
  \int_step_inline:nn { \clist_count:n { #1 } }
   {
    \clist_set:cx { l__seriously_tov_##1_clist } { \tl_item:nn { #4 } { ##1 } }
   }
  \seq_clear:N \l__seriously_tov_rows_seq
  \seq_clear:N \l__seriously_tov_args_seq
  \int_step_inline:nn { \clist_count:c { l__seriously_tov_1_clist } }
   {
    \tl_clear:N \l_tmpa_tl \tl_clear:N \l_tmpb_tl
    \int_step_inline:nn { \clist_count:n { #1 } }
     {
      \tl_put_right:Nx \l_tmpa_tl { \clist_item:cn { l__seriously_tov_####1_clist } { ##1 } & }
      \tl_put_right:Nx \l_tmpb_tl { { \clist_item:cn { l__seriously_tov_####1_clist } { ##1 } } }
     }
    \tl_put_left:Nn \l_tmpb_tl { \__seriously_tov_compute:w }
    \tl_put_right:Nn \l_tmpb_tl { \\ }
    \seq_put_right:NV \l__seriously_tov_rows_seq \l_tmpa_tl
    \seq_put_right:NV \l__seriously_tov_args_seq \l_tmpb_tl
   }
  % now we can build the table body
  \tl_set:Nx \l__seriously_tov_body_tl
   {
    \seq_mapthread_function:NNN \l__seriously_tov_rows_seq \l__seriously_tov_args_seq \use:nn
   }
  % and we output the table
  $\begin{array}{ *{\clist_count:n { #1 }}{c}c }
  \toprule
  \clist_use:nn { #1 } { & } & #2 \\
  \midrule
  \tl_use:N \l__seriously_tov_body_tl
  \bottomrule
  \end{array}$
 }

\ExplSyntaxOff

\begin{document}

\tableofvalues{a,b}{a+b}{round(#1+#2,2)}{
  {1, 2.3, 4, -2}
  {1, 1.5, 2, 42}
}

\bigskip

\tableofvalues{x,y,z}{xyz}{#1*#2*#3}{
  {0.3,23,92}
  {1,2,3}
  {100,200,-100}
}

\end{document}

enter image description here

What's the idea behind this code? A certain number of variables is declared and we can access their number as \clist_count:n{#1}.

The next task is to define a function that computes the values and which needs to take as many arguments as there are variables. We can therefore exploit expl3 function signatures that will provide the required number of arguments; in the first example of use, we'd get

\cs_set:cn { __seriously_tov_compute:nn } { \fp_eval:n { #3 } }

because we've generated nn via \prg_replicate:nn.

However, using this in the following code would be too verbose, so I define an alias with the :w signature. The replacement text is defined by applying \fp_eval:n to argument #3 that contains the formula in a syntax suitable for such expressions.

The next task is to generate the list of arguments to be passed to \__seriously_tov_compute:w. The final argument to \tableofvalues is expected to contain as many braced lists as there are variables and these lists are supposed to have the same number of items.

These lists are first stored in clist variables using a loop on the number of variables. Next we traverse these lists simultaneously extracting items with the same position from each and we build two sequences. In the example of use, the first sequence will contain the items (braces only for separating them)

{1 & 1 &}
{2.3 & 1.5 &}
{4 & 2 &}
{-2 & 42 &}

and the second sequence will contain

{\__seriously_tov_compute:w {1}{1} \\}
{\__seriously_tov_compute:w {2.3}{1.5} \\}
{\__seriously_tov_compute:w {4}{2} \\}
{\__seriously_tov_compute:w {-2}{42} \\}

(not really: the sequence will actually contain the computed values).

Finally we build the table body by fetching items from each sequence in order with \seq_mapthread_function:NNN with the function \use:nn that simply delivers its arguments one after the other.

Once the table body is ready, the table can be output with a suitable preamble.

2
  • wow that's a lot of code for such a small table. Could you add some more comments and a general explanation how this works?
    – Seriously
    Feb 6, 2022 at 14:58
  • 1
    @Seriously I added some words for describing the idea behind the code.
    – egreg
    Feb 6, 2022 at 15:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .