0

Following up this answer, how to robustly use the values stored in \getRow as coefficients of a polynomial to be printed?

Also, I need to have an argument of the symbol to be used in the polynomial. (it is v here in this MWE)

Please, note that the zero, in a cell, is used as a coefficient of a polynomial term, while the blank cell is not used in the polynomial construction (i.e. case Third here)

enter image description here

\begin{filecontents*}[overwrite]{mycoeffs.csv}
    First , 4 , 0 , 2 , 5
    Second , 0 , 0 , 7 ,  8
    Third , 5 , 0 , 6 ,  
\end{filecontents*}

\documentclass{article} \usepackage{xparse}

\ExplSyntaxOn % Step 1: reading the file \ior_new:N \l__diaa_csv_ior \bool_new:N \l__diaa_csv_str_bool \seq_new:N \l__diaa_csv_tmp_seq

% str mode (bool/star), key column, label, value columns, file \NewDocumentCommand \ReadCSV { s O{1} m O{} m } { \IfBooleanTF {#1} { \bool_set_true:N \l__diaa_csv_str_bool } { \bool_set_false:N \l__diaa_csv_str_bool } \diaa_csv_read:nnnn {#3} {#2} {#4} {#5} }

% label, key column, value columns, file \cs_new_protected:Npn \diaa_csv_read:nnnn #1 #2 #3 #4 { \tl_if_blank:nTF {#3} % Detect number of columns and use 2 to last { \ior_open:NnTF \l__diaa_csv_ior {#4} { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_get:NN } { \ior_get:NN } \l__diaa_csv_ior \l_tmpa_tl

        \ior_close:N \l__diaa_csv_ior
        \seq_set_split:NnV \l_tmpa_seq { , } \l_tmpa_tl
        \seq_clear:N \l__diaa_csv_tmp_seq
        \int_step_inline:nnn { 2 } { \seq_count:N \l_tmpa_seq }
        { \seq_put_right:Nn \l__diaa_csv_tmp_seq {##1} }
    }
    { \msg_error:nnn { diaa } { file-not-found } {#4} }
}
{ \seq_set_split:Nnn \l__diaa_csv_tmp_seq { , } {#3} } % explicit columns

\ior_open:NnTF \l__diaa_csv_ior {#4}
{
    \prop_new:c { g__diaa_csv_#1_prop }
    \__diaa_csv_read:nn {#1} {#2}
    \ior_close:N \l__diaa_csv_ior
}
{ \msg_error:nnn { diaa } { file-not-found } {#4} }

}

\msg_new:nnn { diaa } { file-not-found } { File~`#1'~not~found. }

\cs_generate_variant:Nn \prop_put:Nnn { cxV }

% label, key column \cs_new_protected:Npn __diaa_csv_read:nn #1 #2 { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_map_inline:Nn } { \ior_map_inline:Nn } \l__diaa_csv_ior { \seq_set_split:Nnn \l_tmpa_seq { , } {##1} % split one CSV row \tl_clear:N \l_tmpa_tl \seq_map_inline:Nn \l__diaa_csv_tmp_seq { \tl_put_right:Nx \l_tmpa_tl { { \seq_item:Nn \l_tmpa_seq {####1} } } }

    \prop_put:cxV { g__diaa_csv_#1_prop }
    { \seq_item:Nn \l_tmpa_seq {#2} }
    \l_tmpa_tl
}

}

% Step 2: getting the values % star → global assignment, macro or tl var, value column, key, label \NewDocumentCommand \getValue { s m O{1} m m } { \IfBooleanTF {#1} { \tl_gset:Nx } { \tl_set:Nx } #2 { \diaa_csv_item:nnn {#4} {#3} {#5} } }

% key, value column, label \NewExpandableDocumentCommand \CSVItem { m O{1} m } { \diaa_csv_item:nnn {#1} {#2} {#3} }

\cs_generate_variant:Nn \tl_item:nn { f }

% key, value column, label \cs_new:Npn \diaa_csv_item:nnn #1 #2 #3 { \tl_item:fn { \prop_item:cn { g__diaa_csv_#3_prop } {#1} } {#2} }

% star → global assignment, macro, key, label \NewDocumentCommand \getRow { s m m m } { \prop_get:cnN { g__diaa_csv_#4_prop } {#3} \l_tmpa_tl \IfBooleanTF {#1} { \cs_gset_nopar:Npx } { \cs_set_nopar:Npx } #2 [ ##1 ] { \exp_not:N \str_if_eq:nnTF {##1} { non-empty } { \exp_not:N __diaa_nb_nonempty_items_in_row:nw { 0 } \exp_not:V \l_tmpa_tl \exp_not:n { \q_recursion_tail \q_recursion_stop } } { \exp_not:N \tl_item:nn { \exp_not:V \l_tmpa_tl } {##1} } } }

\cs_new:Npn __diaa_nb_nonempty_items_in_row:nw #1#2 { \quark_if_recursion_tail_stop_do:nn {#2} { \int_eval:n {#1} } \tl_if_empty:nTF {#2} { __diaa_nb_nonempty_items_in_row:nw {#1} } { __diaa_nb_nonempty_items_in_row:nw { #1 + 1 } } } \ExplSyntaxOff

\begin{document}

\ReadCSV{mydata}{mycoeffs.csv}

\getRow\First{First}{mydata}
I need to use \verb|\First| to print $4 \times v^3 + 2 \times v + 5$

\getRow\Second{Second}{mydata}
I need to use \verb|\Second| to print $7 \times v + 8$

\getRow\Third{Third}{mydata}
I need to use \verb|\Third| to print $5 \times v^2 + 6$

\end{document}

Diaa
  • 9,599
  • Let's say the input syntax will be something like \ReadCSV{mydata}{mycoeffs.csv} ... \getPolyFromRow{\First}{First}{mydata} ... $\First$. Do you want the variable name to be an argument of \getPolyFromRow or of \First? And should it be a mandatory argument in braces, or an optional argument in square brackets with some default? – frougon Feb 22 '22 at 18:28
  • @frougon The variable name is an optional argument of \getPolyFromRow with any default one. Please, note the variable may be something like V_x not just a single character. – Diaa Feb 22 '22 at 18:37

1 Answers1

1

I propose the following. To ease understanding, I removed the macros that are not used in this example, but the code below is compatible with the one it is based on (i.e., you can readd the definitions of \getValue, \CSVItem, \getRow and whatever lower-level macros they rely on; this won't cause any conflict).

\begin{filecontents*}{test.csv}
    First ,   4 , 0 ,  2 , 5
    Second ,  0 , 0 ,  7 , 8
    Third ,   5 , 0 ,  6 ,
    Fourth , -5 , 0 , -6 ,
\end{filecontents*}

\documentclass{article} % Uncomment if the LaTeX format is older than 2020-10-01: % \usepackage{xparse}

\ExplSyntaxOn % Reading the file (based on <https://tex.stackexchange.com/a/575055/73317>) \ior_new:N \l__diaa_csv_ior \bool_new:N \l__diaa_csv_str_bool \seq_new:N \l__diaa_csv_tmp_seq

% str mode (bool/star), key column, label, value columns, file \NewDocumentCommand \ReadCSV { s O{1} m O{} m } { \IfBooleanTF {#1} { \bool_set_true:N \l__diaa_csv_str_bool } { \bool_set_false:N \l__diaa_csv_str_bool } \diaa_csv_read:nnnn {#3} {#2} {#4} {#5} }

% label, key column, value columns, file \cs_new_protected:Npn \diaa_csv_read:nnnn #1 #2 #3 #4 { \tl_if_blank:nTF {#3} % Detect number of columns and use 2 to last { \ior_open:NnTF \l__diaa_csv_ior {#4} { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_get:NN } { \ior_get:NN } \l__diaa_csv_ior \l_tmpa_tl

        \ior_close:N \l__diaa_csv_ior
        \seq_set_split:NnV \l_tmpa_seq { , } \l_tmpa_tl
        \seq_clear:N \l__diaa_csv_tmp_seq
        \int_step_inline:nnn { 2 } { \seq_count:N \l_tmpa_seq }
          { \seq_put_right:Nn \l__diaa_csv_tmp_seq {##1} }
      }
      { \msg_error:nnn { diaa } { file-not-found } {#4} }
  }
  { \seq_set_split:Nnn \l__diaa_csv_tmp_seq { , } {#3} } % explicit columns

\ior_open:NnTF \l__diaa_csv_ior {#4}
  {
    \prop_new:c { g__diaa_csv_#1_prop }
    \__diaa_csv_read:nn {#1} {#2}
    \ior_close:N \l__diaa_csv_ior
  }
  { \msg_error:nnn { diaa } { file-not-found } {#4} }

}

\msg_new:nnn { diaa } { file-not-found } { File~`#1'~not~found. }

\cs_generate_variant:Nn \prop_put:Nnn { cxV }

% label, key column \cs_new_protected:Npn __diaa_csv_read:nn #1 #2 { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_map_inline:Nn } { \ior_map_inline:Nn } \l__diaa_csv_ior { \seq_set_split:Nnn \l_tmpa_seq { , } {##1} % split one CSV row \tl_clear:N \l_tmpa_tl \seq_map_inline:Nn \l__diaa_csv_tmp_seq { \tl_put_right:Nx \l_tmpa_tl { { \seq_item:Nn \l_tmpa_seq {####1} } } }

      \prop_put:cxV { g__diaa_csv_#1_prop }
        { \seq_item:Nn \l_tmpa_seq {#2} }
        \l_tmpa_tl
    }

}

\keys_define:nn { diaa / getPolyFromRow } { global-assignment .bool_set:N = \l__diaa_gpfr_global_assignment_bool, global-assignment .default:n = true, global-assignment .initial:n = false, variable .tl_set:N = \l__diaa_gpfr_variable_name_tl, variable .value_required:n = true, variable .initial:n = X, typographical-variant .str_set:N = \l__diaa_gpfr_typographical_variant_str, typographical-variant .value_required:n = true, typographical-variant .initial:n = default, }

% \getPolyFromRow will temporarily store the result in this variable. This % allows us not to lose the result when the group started for \keys_set:nn % ends (if the user chose to perform a local assignment, this must be done % after closing that group). \tl_new:N \g__diaa_gpfr_result_tl

% Options, macro for result, key, datafile label \NewDocumentCommand \getPolyFromRow { O{} m m m } { \group_begin: \keys_set:nn { diaa / getPolyFromRow } {#1}

  % Globally define _gfunc function aliases that perform global or local
  % assignments depending on \l__diaa_gpfr_global_assignment_bool. They
  % will be used *after* we close the current group.
  \bool_set_true:N \l__diaa_gpfr_dtfa_global_aliases_bool
  \__diaa_gpfr_define_tl_func_aliases:

  % Store the result in \g__diaa_gpfr_result_tl for now.
  \bool_set_true:N \l__diaa_gpfr_global_assignment_bool
  \diaa_get_poly_from_row:Nnn \g__diaa_gpfr_result_tl {#3} {#4}
\group_end:

% Use the globally-defined aliases to perform the user-chosen (local or
% global) kind of assignment.
\__diaa_clear_gfunc:N #2    % make sure the tl var #2 is defined
\__diaa_set_eq_gfunc:NN #2 \g__diaa_gpfr_result_tl % set it

}

% True to globally define the aliases and give them _gfunc names rather than % _func \bool_new:N \l__diaa_gpfr_dtfa_global_aliases_bool

\cs_new_protected:Npn __diaa_gpfr_define_tl_func_aliases: { \bool_if:NTF \l__diaa_gpfr_global_assignment_bool { __diaa_gpfr_define_alias:nnN { clear } { N } \tl_gclear_new:N __diaa_gpfr_define_alias:nnN { set_eq } { NN } \tl_gset_eq:NN __diaa_gpfr_define_alias:nnN { put_right } { Nn } \tl_gput_right:Nn } { __diaa_gpfr_define_alias:nnN { clear } { N } \tl_clear_new:N __diaa_gpfr_define_alias:nnN { set_eq } { NN } \tl_set_eq:NN __diaa_gpfr_define_alias:nnN { put_right } { Nn } \tl_put_right:Nn } }

% Locally or globally define an alias for a function. The alias is defined % globally with a gfunc name if \l__diaa_gpfr_dtfa_global_aliases_bool is true. % % #1: stem such as “clear”, “put_right”, etc. % #2: signature of the alias (e.g., “Nn”) \cs_new_protected:Npn __diaa_gpfr_define_alias:nnN #1#2 { \bool_if:NTF \l__diaa_gpfr_dtfa_global_aliases_bool { \cs_gset_eq:cN } { \cs_set_eq:cN } { __diaa_#1_ \bool_if:NT \l__diaa_gpfr_dtfa_global_aliases_bool { g } func:#2 } }

\int_new:N \l__diaa_gpfr_degree_int \tl_new:N \l__dia_gpfr_row_values_tl \cs_generate_variant:Nn __diaa_put_right_func:Nn { Nx } \cs_generate_variant:Nn __diaa_get_poly_from_row_append_monomial:Nnn { NnV }

% Macro for result, key, datafile label \cs_new_protected:Npn \diaa_get_poly_from_row:Nnn #1#2#3 { % Locally define function aliases that perform global or local assignments % depending on \l__diaa_gpfr_global_assignment_bool: __diaa_clear_func:Nn, % __diaa_put_right_func:Nn, etc. \bool_set_false:N \l__diaa_gpfr_dtfa_global_aliases_bool __diaa_gpfr_define_tl_func_aliases:

% Retrieve the coefficients
\prop_get:cnN { g__diaa_csv_#3_prop } {#2} \l__dia_gpfr_row_values_tl

% Let's put (1 + degree) for now in this int variable.
\int_zero:N \l__diaa_gpfr_degree_int
\tl_map_inline:Nn \l__dia_gpfr_row_values_tl
  {
    \tl_if_empty:nT {##1} { \tl_map_break: }
    \int_incr:N \l__diaa_gpfr_degree_int
  }

\__diaa_clear_func:N #1        % initialize #1 as a tl var if necessary
\bool_set_false:N \l_tmpa_bool % true: add + operator if next coeff is &gt; 0

\tl_map_inline:Nn \l__dia_gpfr_row_values_tl % loop over the coefficients
  {
    % Degree of the monomial we're about to output
    \int_decr:N \l__diaa_gpfr_degree_int
    % Early termination condition if the row is not full of coefficients
    \int_compare:nNnT { \l__diaa_gpfr_degree_int } &lt; { 0 }
      { \tl_map_break: }

    \fp_compare:nNnF {##1} = { 0 }
      {
        % Insert a + operator if necessary
        \bool_if:NTF \l_tmpa_bool
          {
            \fp_compare:nNnT {##1} &gt; { 0 }
              { \__diaa_put_right_func:Nn #1 { + } }
          }
          { \bool_set_true:N \l_tmpa_bool }

        % Insert the monomial
        \__diaa_get_poly_from_row_append_monomial:NnV #1 {##1}
          \l__diaa_gpfr_variable_name_tl
      }
  }

}

\msg_new:nnn { diaa } { gpfr-unknown-typo-variant } { Unknown~typographical~variant~for~\token_to_str:N \getPolyFromRow :~`#1'. }

\cs_generate_variant:Nn \msg_error:nnn { nnV }

% Macro, coefficient, variable name \cs_new_protected:Npn __diaa_get_poly_from_row_append_monomial:Nnn #1#2#3 { \str_case_e:nnF { \l__diaa_gpfr_typographical_variant_str } { { default } { \tl_set:Nn \l_tmpa_tl { #2 \times #3 ^ } } { with-braces } { \tl_set:Nn \l_tmpa_tl { #2 \times {#3} ^ } } } { \msg_error:nnV { diaa } { gpfr-unknown-typo-variant } \l__diaa_gpfr_typographical_variant_str }

\__diaa_put_right_func:Nx #1
  {
    \int_case:nnF { \l__diaa_gpfr_degree_int } % depending on the degree...
      {
        { 0 } { \exp_not:n {#2} } % degree 0 → only the coefficient
        { 1 } { \exp_not:n { #2 \times #3 } }
      }
      { % Other degrees
        \exp_not:V \l_tmpa_tl % use the selected variant
        % Use braces in case the exponent has several digits
        { \int_use:N \l__diaa_gpfr_degree_int }
      }
  }

}

\ExplSyntaxOff

\begin{document}

\ReadCSV{mydata}{test.csv}

\getPolyFromRow{\firstPoly}{First}{mydata}% The first polynom is $\firstPoly$.

\getPolyFromRow[variable=v]{\secondPoly}{Second}{mydata}% The second polynom is $\secondPoly$.

\getPolyFromRow[variable=(U+V)]{\thirdPoly}{Third}{mydata}% The third polynom is $\thirdPoly$.

{% Open a group and perform a global assignment \getPolyFromRow[global-assignment, variable=V_x]{\fourthPoly}{Fourth}{mydata}% }% This one (\verb|\fourthPoly|) was assigned globally: $\fourthPoly$.

\getPolyFromRow[global-assignment, variable=V_x, typographical-variant=with-braces]{\fourthPoly}{Fourth}{mydata}% Typographical variant with braces (usually inferior): $\fourthPoly$.

\end{document}

enter image description here

frougon
  • 24,283
  • 1
  • 32
  • 55
  • Clean and neat as usual. Unfortunately, I don't understand the last case of \getPolyFromRow*[V_x]{\fourthPoly}[1]{Fourth}{mydata}. What is the meaning of [1] in it? What is the difference between it and the fourth example? – Diaa Feb 22 '22 at 21:58
  • 1
    See the comment immediately before \NewDocumentCommand \getPolyFromRow .... This [1] selects typographical variant number 1 (i.e., with braces around the variable; the default variant is number 0). You can easily add other variants (numbered 2, 3, 4...) in the last \int_case:nnF of \__diaa_get_poly_from_row:nnnnn. In the last example, the use of variant 1 causes the only exponent to be slightly higher than in the penultimate example (variant 0). This is ${V_x}^2$ versus $V_x^2$. – frougon Feb 22 '22 at 22:02
  • Many thanks. One final question: is the code robust to the number of the coefficients found in each row? Or does it have an upper limit? Please, be informed that I might have different number of coefficients (separators) from each row to another. – Diaa Feb 22 '22 at 22:11
  • 1
    There is no hardcoded limit here except the usual limits inside TeX (most notably here, maybe: the largest integer for TeX, which must be enough for the degree). The number of separators (and therefore coefficients) may vary between rows. – frougon Feb 22 '22 at 22:16
  • One word of caution: unless the second optional argument of \ReadCSV is specified, the CSV reading code autodetects the number of columns from the first row. You mustn't have other rows with more columns. So, either put enough separators in the first row or specify the second optional argument of \ReadCSV to span all columns used in any row. For instance, if in the example you reduce the first row to First , 4 , 0, in order not to sabotage other rows, you would need a call like \ReadCSV{mydata}[2,3,4,5]{test.csv}. – frougon Feb 22 '22 at 22:32
  • I fixed a bug (missing braces) for multi-digit exponents and implemented a key-value interface (this kind of interface makes the LaTeX markup more readable and easier to extend if needed in the future). Also, you can now use floating point values for the coefficients. – frougon Feb 23 '22 at 10:37
  • You need to save your amazingly helpful and delicate answers in some kind of a package. You don't know how much these are life-saving. – Diaa Feb 23 '22 at 13:46
  • Packages drive expectations, and unfortunately, I can't guarantee I'll be able to fulfill them. Make sure you took my last edit because \l_tmpa_tl was used in too many places. I see you removed previous comments, so the problem is solved? If not, I prefer knowing which answers you want to merge because in your pastebin, the indentation had been changed, which prevents me from easy reading and comparing with the code I have on my hard drive. – frougon Feb 23 '22 at 13:57
  • I found out that I use the \getPoly the old way, which caused the errors. The existence of \l_tmpa_tl with \l__diaa__tmpa_tl from previous answers made my code go crazy. However, I guessed that this is a temporary variable, so unifying it makes it work. Many many thanks for all your effort and time. I strongly believe that developing such a code is not an easy task especially when done in your free time :). – Diaa Feb 23 '22 at 14:04
  • 1
    You shouldn't need to rename the variable if you took my last edit. I would be wary of replacing \l_tmpa_tl with \l__diaa__tmpa_tl... If only done in this answer, this should not harm but if done in the code from other answers, I don't guarantee anything. – frougon Feb 23 '22 at 14:09