Matrizes associativas

Matrizes associativas

Eu estava tentando entender o artigo dado em Armazenando uma matriz de strings em um comando mas não consegui encontrar a solução (óbvia) para o seguinte problema:

Tenho informações sobre compositor, título, subtítulo e alguns outros metadados indexados por um ID de string numérica, por exemplo, 00034, 00035, 02354, 12335 (todos têm 5 dígitos com zeros à esquerda) e quero armazenar isso em uma matriz associativa ( ou talvez matriz?) e ser capaz de recuperar essas informações em vários locais de um documento, por exemplo

\data{00034}{composer}

e também em várias 'manipulações de texto', por exemplo

\data{00034}{composer}: \data{00034}{title}, \emph{\data{00034}{subtitle}}

Isso é possível usando LaTeX? TIA!

Edição 1: esqueci de mencionar que qualquer bloco de comandos "definições" ou "população" é gerado por uma exportação de banco de dados, portanto, blocos grandes de comandos "definir", conforme mostrado nas muitas soluções abaixo, não são um problema.

Edição 2: @Marijn sugeriu outro artigo, mas embora possa ser útil para alguns TeX-Wizard, não foi para mim, é mais de baixo nível. As respostas aqui são excelentes e ajudam muito.

Responder1

insira a descrição da imagem aqui

\documentclass{article}

\newcommand\savedata[3]{\expandafter\def\csname data-#1-#2\endcsname{#3}}
\newcommand\data[2]{\csname data-#1-#2\endcsname}

\savedata{00034}{composer}{Beethoven}
\savedata{00034}{title}{Bagatelle No. 25}
\savedata{00034}{subtitle}{Für Elise}

\begin{document}

\data{00034}{composer}: \data{00034}{title}, \emph{\data{00034}{subtitle}}

\end{document}

Responder2

Obviamente, este é um trabalho para listas de propriedades.

Veja o \uselistscomando para ver o porquê: podemos mapear as listas conhecidas; o argumento do meio é um modelo onde #1representa o ID atual.

\documentclass{article}
\usepackage{booktabs}

\ExplSyntaxOn

\NewDocumentCommand{\definelist}{mm}
 {
  \mflxvii_list_define:nn { #1 } { #2 }
 }
\NewDocumentCommand{\addtolist}{mmm}
 {
  \mflxvii_list_add:nnn { #1 } { #2 } { #3 }
 }
\NewExpandableDocumentCommand{\getitem}{mm}
 {
  \mflxvii_list_get:nn { #1 } { #2 }
 }
\NewDocumentCommand{\uselists}{mmm}
 {% #1 = pre code, #2 = body, #3 = post code
  \mflxvii_list_use:nnn { #1 } { #2 } { #3 }
 }

\seq_new:N \g_mflxvii_list_all_seq

% check whether the list already exists
\cs_new_protected:Nn \mflxvii_list_define:nn
 {
  \prop_if_exist:cTF { g_mflxvii_list_#1_prop }
   {% already defined
    \msg_error:nnn { mflxvii } { list-exist } { #1 }
   }
   {% define a new one
    % add to the global list of lists
    \seq_gput_right:Nn \g_mflxvii_list_all_seq { #1 }
    % build the list from the given data
    \prop_new:c { g_mflxvii_list_#1_prop }
    \prop_gset_from_keyval:cn { g_mflxvii_list_#1_prop } { #2 }
   }
 }

\cs_new_protected:Nn \mflxvii_list_add:nnn
 {
  \prop_if_exist:cTF { g_mflxvii_list_#1_prop }
   {% add if existing
    \prop_gput:cnn { g_mflxvii_list_#1_prop } { #2 } { #3 }
   }
   {% list doesn't exist
    \msg_error:nnn { mflxvii } { list-exist } { #1 }
   }
 }

\cs_new:Nn \mflxvii_list_get:nn
 {
  \prop_item:cn { g_mflxvii_list_#1_prop } { #2 }
 }

\cs_new_protected:Nn \mflxvii_list_use:nnn
 {
  \group_begin:
  \cs_set:Nn \__mflxvii_list_use_body:n { #2 }
  % pre code
  #1
  % body
  \seq_map_function:NN \g_mflxvii_list_all_seq \__mflxvii_list_use_body:n
  % post code
  #3
  \group_end:
 }

\ExplSyntaxOff

\definelist{00034}{
  composer=Beethoven,
  title=Bagatelle No.\@ 25,
  subtitle=Für Elise
}
\definelist{00035}{
  composer=Bob Geldof,
  title=The Great Song of Indifference
}
\addtolist{00035}{subtitle}{I don't mind}

\begin{document}

\getitem{00034}{composer}: \getitem{00034}{title}, \getitem{00034}{subtitle}

\bigskip

\uselists{%
  \begin{tabular}{@{}llll@{}}
  \toprule
  ID & composer & title & subtitle \\
  \midrule
}{ #1 & \getitem{#1}{composer} & \getitem{#1}{title} & \getitem{#1}{subtitle} \\
}{%
  \bottomrule\end{tabular}
}

\end{document}

insira a descrição da imagem aqui

Responder3

Parece que você tem uma matriz de registros, de modo que cada elemento da matriz é um registro que possui campos de dados.

Você pode definir uma sequência de controle para cada item de dados:

\documentclass{article}

\ExplSyntaxOn
\cs_new:Npn \StoreData #1#2#3 {
  \cs_new:cpn {ArrayItem:{#1}_RecordField:{#2}} {#3}
}
\cs_new:Npn \Data #1#2 {
  \cs_if_exist_use:cF {ArrayItem:{#1}_RecordField:{#2}} {--data~not~available--}
}
\ExplSyntaxOff

%-------------------------------------------------------------------
\StoreData {00034}{composer}{Beethoven}
\StoreData {00034}{title}{Bagatelle No.\@ 25}
\StoreData {00034}{subtitle}{Für Elise}
%-------------------------------------------------------------------
\StoreData {00035}{composer}{Bob Geldof}
\StoreData {00035}{title}{The Great Song of Indifference}
\StoreData {00035}{subtitle}{I don't mind}
%-------------------------------------------------------------------

\begin{document}

\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}

\end{document}

Você pode usar o pacote l3prop do expl3 e manter uma lista de propriedades onde os nomes das propriedades são combinados a partir de nomes de elementos de array e nomes de campos de elementos de array:

\documentclass{article}

\ExplSyntaxOn
\prop_new:N \g__MyModule_Array_prop
\cs_new:Npn \AddArrayProperty #1#2#3 {
  \prop_gput:Nnn \g__MyModule_Array_prop {ArrayItem:{#1}_RecordField:{#2}} {#3}
}
\cs_new:Npn \Data #1#2 {
  \prop_if_in:NnTF
    \g__MyModule_Array_prop
    {ArrayItem:{#1}_RecordField:{#2}} 
    { \prop_item:Nn \g__MyModule_Array_prop {ArrayItem:{#1}_RecordField:{#2}} }
    {--data~not~available--}
}
\ExplSyntaxOff

%-------------------------------------------------------------------
\AddArrayProperty {00034}{composer}{Beethoven}
\AddArrayProperty {00034}{title}{Bagatelle No.\@ 25}
\AddArrayProperty {00034}{subtitle}{Für Elise}
%-------------------------------------------------------------------
\AddArrayProperty {00035}{composer}{Bob Geldof}
\AddArrayProperty {00035}{title}{The Great Song of Indifference}
\AddArrayProperty {00035}{subtitle}{I don't mind}
%-------------------------------------------------------------------

\begin{document}

\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}

\end{document}

Você pode tentar aninhar \str_case:nnF:

\documentclass{article}

\newcommand\MyArrayOfRecords{%
  {00034}{%
    {composer}{Beethoven}%
    {title}{Bagatelle No.\@ 25}%
    {subtitle}{Für Elise}%
  }%
  {00035}{%
    {composer}{Bob Geldof}%
    {title}{The Great Song of Indifference}%
    {subtitle}{I don't mind}%
  }%
}%


\ExplSyntaxOn
\cs_new:Nn \__MyStuff_Check_Item:nnn {
  \quark_if_no_value:nTF 
    {#1}
    {--#2~not~available--}
    { \str_case:nnF {#3}{#1}{--#3~not~available~in~#2--} }
}
\cs_new:Npn \Data #1#2 {
  \exp_args:Nf
    \__MyStuff_Check_Item:nnn 
      { \exp_args:Nno  \str_case:nnF {#1} { \MyArrayOfRecords } {\exp_not:N \q_no_value} }  
      {#1} 
      {#2}
}
\ExplSyntaxOff

\begin{document}

\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}

\end{document}

Caso os dados estejam disponíveis como .csv-list, o pacoteferramenta de dadospode ser de interesse:

% Have LaTeX create some dummy .csv-files. 
% In a real life scenario the .csv-files could come from the software you use
% for maintaining and exporting your data.

\begin{filecontents*}{MyMusic1.csv}
num,comp,ttl,sbttl
00034,Beethoven,Bagatelle No.\@ 25,Für Elise
00035,Bob Geldof,The Great Song of Indifference,I don't mind
\end{filecontents*}

\begin{filecontents*}{MyMusic2.csv}
sbttl,ttl,comp,num
Der für die Sünde der Welt gemarterte und sterbende Jesus,Brockes-Passion,Georg Friedrich Händel,00047
\end{filecontents*}

\begin{filecontents*}{MyMusic3.csv}
Jean Sibelius,Finlandia,Opus 26,01769
Monty Python,,Worried,00036
\end{filecontents*}

\documentclass{article}
\usepackage{datatool}

% To create new database from a .csv-file with \DTLloaddb set the
% switch \DTLnewdbonloadtrue ; this is the default on the start:
% \DTLnewdbonloadtrue

\DTLloaddb[%
              % MyMusic1.csv has a header row, thus noheader=false:
            noheader=false, 
              %   Let's override the field-names given in the .csv-file's header row by new names 
              %   given in keys={...} in the order in which the old names occur in the header-row:
            keys={ID,composer,title,subtitle}
          ]{MusicDatabase}{MyMusic1.csv}

% To apennd  with \DTLloaddb to an existing database data stemming 
% from a .csv-file set the switch \DTLnewdbonloadfalse:
\DTLnewdbonloadfalse

\DTLloaddb[%
            noheader=false,
            keys={subtitle,title,composer,ID}
          ]{MusicDatabase}{MyMusic2.csv}

\DTLloaddb[%
            noheader=true,%
            keys={composer,subtitle,title,ID},
              % With the last call to \DTLloaddb set the column-headings in case of
              % using facilities of the package datatool for creating tables 
              %(tabular/longtable) from data of the database. You can change this
              % setting laterwards via \DTLsetheader.
            headers={%
              Numerical String ID,Composer,Title,Subtitle%
            }
          ]{MusicDatabase}{MyMusic3.csv}

%----------------------------------------------------------------------------------------
% \GetDataFieldEntryByID{<ID>}%
%                       {<datafield>}%
%                       {<tokens in case only ID does not exist>}%
%                       {<tokens in case only datafield does not exist>}%
%                       {<tokens in case both ID and datafield don't exist>}%
%                       {<tokens in case datafield does exist but does not hold data>}
\newcommand\MyScratchMacro{}%
\newcommand\GetDataFieldEntryByID[6]{%
  \begingroup
  \DTLifhaskey{MusicDatabase}{#2}{%
    \DTLgetvalueforkey{\MyScratchMacro}{ID}{MusicDatabase}{ID}{#1}%
    \DTLifnullorempty{\MyScratchMacro}{%
      \endgroup#3%
    }{%
      \DTLgetvalueforkey{\MyScratchMacro}{#2}{MusicDatabase}{ID}{#1}%
      \DTLifnullorempty{\MyScratchMacro}%
                       {\endgroup#6}%
                       {\expandafter\endgroup\MyScratchMacro}%
    }%
  }{%
    \DTLgetvalueforkey{\MyScratchMacro}{ID}{MusicDatabase}{ID}{#1}%
    \DTLifnullorempty{\MyScratchMacro}{%
      \endgroup#5%
    }{%
      \endgroup#4%
    }%
  }%
}%
%----------------------------------------------------------------------------------------

\begin{document}

% Sort the database. This affects the order in which rows of the database are processed when
% iterating the database from the first to the last row, e.g. in the course if crearing a table
% where all database rows are displayed.
\dtlsort{ID=ascending}{MusicDatabase}{\dtlcompare}%
%
% Have datatool create a table with all database-rows, don't display the subtitle-column:
\begingroup
\scriptsize
\DTLdisplaydb[subtitle]{MusicDatabase}%
\par
\endgroup

\newpage

\GetDataFieldEntryByID{00034}%
                      {composer}%
                      {Error: Database does not have a row with ID 00034}%
                      {Error: Database does not have a key/datafield/column ``composer''}%
                      {Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``composer''}%
                      {Error: The row with the ID 00034 does not have data in key/datafield/column ``composer''}

\GetDataFieldEntryByID{00034}%
                      {title}%
                      {Error: Database does not have a row with ID 00034}%
                      {Error: Database does not have a key/datafield/column ``title''}%
                      {Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``title''}%
                      {Error: The row with the ID 00034 does not have data in key/datafield/column ``title''}

\GetDataFieldEntryByID{00034}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 00034}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 00034 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{00034}%
                      {ID}%
                      {Error: Database does not have a row with ID 00034}%
                      {Error: Database does not have a key/datafield/column ``ID''}%
                      {Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``ID''}%
                      {Error: The row with the ID 00034 does not have data in key/datafield/column ``ID''}


\medskip\hrule\medskip

\GetDataFieldEntryByID{00035}%
                      {composer}%
                      {Error: Database does not have a row with ID 00035}%
                      {Error: Database does not have a key/datafield/column ``composer''}%
                      {Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``composer''}%
                      {Error: The row with the ID 00035 does not have data in key/datafield/column ``composer''}

\GetDataFieldEntryByID{00035}%
                      {title}%
                      {Error: Database does not have a row with ID 00035}%
                      {Error: Database does not have a key/datafield/column ``title''}%
                      {Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``title''}%
                      {Error: The row with the ID 00035 does not have data in key/datafield/column ``title''}

\GetDataFieldEntryByID{00035}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 00035}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 00035 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{00035}%
                      {ID}%
                      {Error: Database does not have a row with ID 00035}%
                      {Error: Database does not have a key/datafield/column ``ID''}%
                      {Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``ID''}%
                      {Error: The row with the ID 00035 does not have data in key/datafield/column ``ID''}


\medskip\hrule\medskip

\GetDataFieldEntryByID{00036}%
                      {composer}%
                      {Error: Database does not have a row with ID 00036}%
                      {Error: Database does not have a key/datafield/column ``composer''}%
                      {Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``composer''}%
                      {Error: The row with the ID 00036 does not have data in key/datafield/column ``composer''}

\GetDataFieldEntryByID{00036}%
                      {title}%
                      {Error: Database does not have a row with ID 00036}%
                      {Error: Database does not have a key/datafield/column ``title''}%
                      {Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``title''}%
                      {Error: The row with the ID 00036 does not have data in key/datafield/column ``title''}

\GetDataFieldEntryByID{00036}%
                      {ID}%
                      {Error: Database does not have a row with ID 00036}%
                      {Error: Database does not have a key/datafield/column ``ID''}%
                      {Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``ID''}%
                      {Error: The row with the ID 00036 does not have data in key/datafield/column ``ID''}


\medskip\hrule\medskip

\GetDataFieldEntryByID{00047}%
                      {composer}%
                      {Error: Database does not have a row with ID 00047}%
                      {Error: Database does not have a key/datafield/column ``composer''}%
                      {Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``composer''}%
                      {Error: The row with the ID 00047 does not have data in key/datafield/column ``composer''}

\GetDataFieldEntryByID{00047}%
                      {title}%
                      {Error: Database does not have a row with ID 00047}%
                      {Error: Database does not have a key/datafield/column ``title''}%
                      {Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``title''}%
                      {Error: The row with the ID 00047 does not have data in key/datafield/column ``title''}

\GetDataFieldEntryByID{00047}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 00047}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 00047 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{00047}%
                      {ID}%
                      {Error: Database does not have a row with ID 00047}%
                      {Error: Database does not have a key/datafield/column ``ID''}%
                      {Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``ID''}%
                      {Error: The row with the ID 00047 does not have data in key/datafield/column ``ID''}


\medskip\hrule\medskip

\GetDataFieldEntryByID{01769}%
                      {composer}%
                      {Error: Database does not have a row with ID 01769}%
                      {Error: Database does not have a key/datafield/column ``composer''}%
                      {Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``composer''}%
                      {Error: The row with the ID 01769 does not have data in key/datafield/column ``composer''}

\GetDataFieldEntryByID{01769}%
                      {title}%
                      {Error: Database does not have a row with ID 01769}%
                      {Error: Database does not have a key/datafield/column ``title''}%
                      {Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``title''}%
                      {Error: The row with the ID 01769 does not have data in key/datafield/column ``title''}

\GetDataFieldEntryByID{01769}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 01769}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 01769 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{01769}%
                      {ID}%
                      {Error: Database does not have a row with ID 01769}%
                      {Error: Database does not have a key/datafield/column ``ID''}%
                      {Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``ID''}%
                      {Error: The row with the ID 01769 does not have data in key/datafield/column ``ID''}

\medskip\hrule\medskip

Error-triggering:

\GetDataFieldEntryByID{00036}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 00036}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 00036 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{00050}%
                      {subtitle}%
                      {Error: Database does not have a row with ID 00050}%
                      {Error: Database does not have a key/datafield/column ``subtitle''}%
                      {Error: Database neither does have row with ID 00050 nor does have a key/datafield/column ``subtitle''}%
                      {Error: The row with the ID 00050 does not have data in key/datafield/column ``subtitle''}

\GetDataFieldEntryByID{00036}%
                      {year}%
                      {Error: Database does not have a row with ID 00036}%
                      {Error: Database does not have a key/datafield/column ``year''}%
                      {Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``year''}%
                      {Error: The row with the ID 00036 does not have data in key/datafield/column ``year''}

\GetDataFieldEntryByID{00050}%
                      {year}%
                      {Error: Database does not have a row with ID 00050}%
                      {Error: Database does not have a key/datafield/column ``year''}%
                      {Error: Database neither does have row with ID 00050 nor does have a key/datafield/column ``year''}%
                      {Error: The row with the ID 00050 does not have data in key/datafield/column ``year''}

\end{document}

informação relacionada