1

Following up this answer, how to extend the code of its second method to store each row value columns in a list or array and be able to index into it?

In other words, I need another macro (along with \getValue) that stores all the values of the columns of the same row (except the first column that has the row name) in the following way:

\getRow{<row name located in the first column>}{<list/array name of the whole row value columns>}

For example, consider the following file

\begin{filecontents*}{test.csv}
First Parameter , 7 , 9  ,  ,
Third Parameter , 5 , 10 ,  ,
Fifth Parameter , 3 , 6  ,  , 44
\end{filecontents*}
  • I can have the value of 44 by first storing the row of Fifth Parameter in \myList by \getRow{Fifth Parameter}{\myList}. Then \myList[4] will get me the value 44.

  • The first value of the row (e.g. 3) can be accessed by \myList or \myList[1].

Diaa
  • 9,599

1 Answers1

2

I've added a \getRow command to the “Second method” of the existing code, as you asked. Its syntax is:

\getRow[*] {\macro} {key} {label}
  • The star form performs a global assignment to \macro (otherwise, the assignment is local).

  • The label must identify a file you've previously read using \ReadCSV (it corresponds to the first mandatory argument of \ReadCSV).

  • The key is used to select a particular row from that file (it has same meaning as with \getValue and \CSVItem).

After such a \getRow call, \macro is safe to use in expansion-only contexts (e.g., inside \edef, \write, \num from siunitx, etc.): it wraps the desired value using the \unexpanded e-TeX primitive, just like \tl_item:nn does.

Negative indices are supported and count from the end:

  • \macro[-1] expands to the last item of the row stored in \macro;

  • \macro[-2] expands to its predecessor;

  • etc.

As a special case, \macro[non-empty] expands to the number of non-empty items in the row stored inside \macro.

\begin{filecontents*}{test.csv}
Third Parameter  , 7 , 9          ,
First Parameter  , 5 , {foo, bar} ,
Second Parameter , 3 , 6          , 44
\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}

% Use default column for the key (1). The second empty optional argument (list % of value columns) means we want to autodetect the value columns; then, the % first column is for keys and all other columns are used as value columns. \ReadCSV{mydata}{test.csv}

\getValue\rdPar{Second Parameter}{mydata} \rdPar % 3

\getValue\rdPar[2]{Second Parameter}{mydata} \rdPar % 6

\getValue\rdPar[3]{Second Parameter}{mydata} \rdPar % 44

\getValue\rdPar{Third Parameter}{mydata} \rdPar % 7

\edef\rdPar{\CSVItem{First Parameter}{mydata}}% \rdPar % 5

\edef\rdPar{\CSVItem{First Parameter}[2]{mydata}}% \rdPar % foo, bar

\edef\rdPar{\CSVItem{First Parameter}[3]{mydata}}% \ifx\rdPar\empty \textlangle empty\textrangle \else \rdPar \fi

\getRow{\RowA}{First Parameter}{mydata} \getRow{\RowB}{Second Parameter}{mydata} \getRow{\RowC}{Third Parameter}{mydata}

\RowA[1]'',\RowA[2]'', \RowA[3]'' ($\RowA[non-empty]$ non-empty)\par\RowB[1]'', \RowB[2]'',\RowB[3]'' ($\RowB[non-empty]$ non-empty)\par \RowC[1]'',\RowC[2]'', ``\RowC[3]'' ($\RowC[non-empty]$ non-empty)

{% Enter a group and perform a global assignment \getRow*{\globallyDefined}{First Parameter}{mydata}% }% Leave the group \globallyDefined[1]'',\globallyDefined[2]'', ``\globallyDefined[3]''

\edef\zzz{abc'\RowA[2]'def} %\show\zzz % prints \zzz=macro:->abc'foo, bar'def. \edef\zzz{\RowA[non-empty]} %\show\zzz % prints \zzz=macro:->2.

\end{document}

enter image description here

frougon
  • 24,283
  • 1
  • 32
  • 55
  • You are my latex savior for long time :). I am on my phone but I can't hold this question till tomorrow XD: do you mean that I can use something like \SI{\fpeval{myList[1]^2 * \mylist[2]}} without problems? Since this is what I actually need to do. – Diaa Dec 15 '20 at 00:31
  • Another feature I hope you consider since it was missing in the old code: i would like to get a warning in my latex editor when the macro handled by \getValue has an empty value (i.e. the accessed cell in the CSV file is empty) and an error halting the compilation when I try to index into a non-existent row or column. – Diaa Dec 15 '20 at 00:38
  • Regarding your first comment, if you add \usepackage{siunitx} and \usepackage{xfp} to my example, then $d = \SI{\fpeval{\RowB[1]^2 * \RowB[2]}}{\meter}$ typesets d = 54 m (because (3^2)×6 = 54). Regarding your second comment, these could be added later (no time now, sorry, have to go to bed). – frougon Dec 15 '20 at 00:45
  • I highly appreciate your consideration and swift help. – Diaa Dec 15 '20 at 09:15
  • If you don't mind me extending my wish list along with warning and error XD: How can I get the number of non-empty elements in the list/array? Also, how can I robustly access the last element by using something like \myList[end] or \myList[-1] without having to explicitly write its index? – Diaa Dec 15 '20 at 10:24
  • If you would like me to ask a new question about my last wishes, please let me know. Thanks and have a nice day :) – Diaa Dec 15 '20 at 10:34
  • Negative indices (not only -1) already work, because this is \tl_item:nn doing the job. For the rest, I'll reply later. – frougon Dec 15 '20 at 10:46
  • FYI, when trying to \getValue non-existent parameter, I stuck in infinite loop of compilation. – Diaa Dec 16 '20 at 00:43
  • I've added suppot for \macro[non-empty]: this expands to the number of non-empty items in the row stored inside \macro. For the rest, you should ask a new question, and I probably won't have time to deal with it soon: several functions, several special cases in each func, and no time here. Sorry. – frougon Dec 16 '20 at 00:50
  • You did me a great help I won't forget. Be safe :) – Diaa Dec 16 '20 at 00:52
  • Hello frougon, I apologize for disturbing, but I would be grateful if you don't mind helping me find something malfunctioning in some piece of relevant code in case you have a couple of minutes. Thanks – Diaa Dec 26 '20 at 09:31
  • Hello, Diaa. The best way is probably to reduce your problem to a minimal example, then post it as a new question. Once this is done, feel free to post a link to that question here. – frougon Dec 26 '20 at 15:11
  • Kindly, find it here https://tex.stackexchange.com/q/576462/2288. Thanks in advance – Diaa Dec 26 '20 at 17:16
  • Is there an option to count all the elements (including the empty ones) in the same row (e.g. \macro[length]) the same way you did with \macro[non-empty]? – Diaa Feb 20 '22 at 22:07
  • Yes, but if your lines all have the same number of elements (1 + number of separators), I think it would be cleaner to offer this at higher level (\ReadCSV could store the number in a well-known macro, maybe). Is it the case in your data files? – frougon Feb 22 '22 at 13:00
  • No, each line has its own number of elements, however, all the lines have the same number of separators like this https://i.ibb.co/MDk9yZq/image.png so, I think my question doesn't make sense now :). – Diaa Feb 22 '22 at 17:27
  • However, what made me ask you is to find a solution to this related problem https://tex.stackexchange.com/q/634676/2288 by finding a way to loop over the coefficients extracted by \getRow to construct a polynomial. – Diaa Feb 22 '22 at 17:28
  • I'm looking at the linked question. – frougon Feb 22 '22 at 18:30