Generieren einer Tabelle aus Schlüssel-Wert-Paaren

Generieren einer Tabelle aus Schlüssel-Wert-Paaren

Ich möchte automatisch eine Tabelle aus einer durch Kommas getrennten Liste von Schlüssel-Wert-Paaren wie generieren {{x=1,y=2},{x=3,y=1},{x=2,y=3}}. In jeder „Zeile“ sind die „Schlüssel“ immer gleich (sie werden zu den Spaltenüberschriften in der Tabelle), aber die Werte können sich ändern. Ausgehend von dieser speziellen Liste von Schlüssel-Wert-Paaren erzeugt der folgende Code Folgendes:

Bildbeschreibung hier eingeben

das ist genau das, was ich will. Leider führt eine geringfügig kompliziertere Eingabe der Werte, wie beispielsweise {{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}, dazu, dass mein Code kaputt geht. Mein Code erzeugt die erwartete Ausgabe:

Bildbeschreibung hier eingeben

Aber bevor dies geschieht, erhalte ich Kompilierungsfehler wie:

! Ungültige Parameternummer in der Definition von \tableRows. \crcr l.40 ...\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

Gibt es eine bessere/robustere Möglichkeit, dies zu tun?

Hier ist mein Code:

\documentclass{article}
\usepackage{xparse,etoolbox}

\newcounter{tableRow}       % for counting the number of rows in the table
\newcounter{tableCol}       % for counting the number of columns
\newcommand\tableRows{}     % to hold the contents of the table
\newcommand\tableHeader{}   % to hold the header of the table
\newcommand\MakeTable[1]{
  \setcounter{tableRow}{0}  % initialise
  \setcounter{tableCol}{1}
  \renewcommand\tableRows{}
  \renewcommand\tableHeader{}
  \forcsvlist\ProcessRow{#1}% generate table
  \begin{tabular}{*{\arabic{tableCol}}{c}}
    \tableHeader\\\hline\tableRows\\
  \end{tabular}
}
\makeatletter
\newcommand\ProcessRow[1]{% generate table rows using \ProcessEntry
  \addtocounter{tableRow}{1}
  \protected@xappto\tableRows{\Alph{tableRow}}% row label in table
  \forcsvlist\ProcessEntry{#1}
  \protected@xappto\tableRows{\\}
}
% need to extract key-value pairs from input of the form: key=val
\newcommand\ExtractKeyValuePair[2]{\def\Key{#1}\def\Value{#2}}
\DeclareDocumentCommand\ProcessEntry{>{\SplitArgument{1}{=}}m}{% add entries to row
    \ExtractKeyValuePair#1% key-value pairs --> \Key and \Value
    \ifnum\value{tableRow}=1%
       \addtocounter{tableCol}{1}
       \protected@xappto\tableHeader{&\Key}
    \fi
    \protected@xappto\tableRows{&\Value}
}

\begin{document}

  \MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}

  % \MakeTable fails on this example
  \MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\end{document}

Zur Generierung der Tabellenzeilen verwende ich \protected@xapptoaus demAbonnierenPaket. Anfangs habe ich versucht, Token zu verwenden, aber ich hatte Erweiterungsprobleme, was zweifellos an meiner Unwissenheit lag. Mir ist nicht klar, warum ich \protected@xapptoanstelle von brauche \xappto, aber wenn ich verwende, \xapptoschlägt der Code bei beiden Beispielen fehl.

Auch die Art und Weise, wie ich die Schlüssel-Wert-Paare extrahiere, fühlt sich ein wenig übertrieben an: Ich verwende einige Tricks mit \SplitArgumentund \DeclareDocumentCommandaus demxparsePaket, um dies zu tun.

BEARBEITEN

Es stellte sich heraus, dass mein Code größtenteils funktioniert und dass ich ein wenig Pech hatte, da dies \overrightarroweines der ersten Beispiele war, die ich im echten Code verwendet habe, aus dem mein MWE destilliert wurde. Das Problem mit meinem MWE ist, dass es \overrightarrowsich um einzerbrechlichBefehl. Ich kann den Kompilierungsfehler im MWE beheben, indem ich die Zeilen hinzufüge:

\usepackage{fixltx2e}
\MakeRobust{\overrightarrow}

Dies ist jedoch keine vollständige Lösung, da es mit Sicherheit andere fehlerhafte Befehle gibt, die meinen Code beschädigen. Christians Ansatz, die Tabelle zu generieren, anstatt sie zu speichern, ist wahrscheinlich die beste Lösung.

Antwort1

Eine Lösung, die Pakete kvsetkeyszum Parsen der Komma- und Schlüsselwertlisten verwendet. Die Tabellenspezifikation, die Kopfzeile und der Textkörper werden zuerst in Token-Registern erstellt. Dann wird die Tabelle zusammengestellt.

Da alle Zuweisungen lokal in einer Gruppe gehalten werden, \MakeTableist eine Verschachtelung möglich.

Das Paket booktabssorgt \midrulefür eine schönere Zeile unterhalb der Tabellenkopfzeile.

Das Paket alphalphermöglicht \AlphAlpheine alphabetische Nummerierung von mehr als 26 Zeilen.

Vollständiges Beispiel:

\documentclass{article}
\usepackage{booktabs}
\usepackage{kvsetkeys}
\usepackage{alphalph}

\makeatletter

% Resources
\newcount\TableRow
\newtoks\TableSpecification
\newtoks\TableHeader
\newtoks\TableBody

% Helper marco \AddToToks{<token register>}{<appended contents>}
\newcommand{\AddToToks}[2]{#1\expandafter{\the#1#2}}

% Main macro \MakeTable{...}
\newcommand{\MakeTable}[1]{%
  \begingroup
    \MakeTableSpecificationAndHeader{#1}%
    \MakeTableBody{#1}%
    \edef\TableBegin{%
      \noexpand\begin{tabular}{\TableSpecification}%
    }%
    %\TableBegin
    \begin{tabular}{\the\TableSpecification}%
      \the\TableHeader
      \midrule
      \the\TableBody
    \end{tabular}%
  \endgroup
}

\newcommand{\MakeTableSpecificationAndHeader}[1]{%
  \TableSpecification{l}%
  \TableHeader{}%
  \AnalyzeFirstRow{#1}%
  \AddToToks\TableHeader{\tabularnewline}%
}
\newcommand{\AnalyzeFirstRow}[1]{%
  \comma@parse{#1}\FirstRowProcessor
}
\newcommand{\FirstRowProcessor}[1]{%
  \kv@parse{#1}\FirstRowCellsProcessor
  \comma@break
}
\newcommand{\FirstRowCellsProcessor}[2]{%
  \AddToToks\TableSpecification{c}%
  \AddToToks\TableHeader{&#1}%
}

\newcommand{\MakeTableBody}[1]{%
  \TableRow=0 %
  \TableBody{}%
  \comma@parse{#1}\TableRowProcessor
}
\newcommand*{\TableRowProcessor}[1]{%
  \advance\TableRow by 1 %
  \edef\TableNumber{\AlphAlph{\TableRow}}%
  \expandafter\AddToToks\expandafter\TableBody\expandafter{\TableNumber}%
  \kv@parse{#1}\TableRowCellsProcessor
  \AddToToks\TableBody{\tabularnewline}%
}
% Simplified implementation, which requires, that all keys of
% a row are given and have the correct order.
\newcommand{\TableRowCellsProcessor}[2]{%
  \AddToToks\TableBody{&#2}%
}
\makeatother

\begin{document}
  \MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}

  \medskip
  \MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

  \medskip
  \MakeTable{
    {
      xy=\MakeTable{{x=1, y=1}, {x=2, y=2}},
      yz=foo,
    },
    {
      xy=bar,
      yz=\MakeTable{{y=4, z=5}, {y=6, z=7}},
    },
  }
\end{document}

Ergebnis

Antwort2

Hier eine Variante ohne Key-Values ​​(zuerst), das einzige was manuell gemacht werden muss, ist hier die Spaltenanzahl anzupassen.

\documentclass{article}
\usepackage{xparse,etoolbox}

\newcounter{tableRow}       % for counting the number of rows in the table
\newcounter{tableCol}       % for counting the number of columns
\newcommand\tableRows{}     % to hold the contents of the table
\newcommand\tableHeader{}   % to hold the header of the table




\ExplSyntaxOn
\clist_new:N \g_andrew_clist
\int_new:N \l_columncounter_int
\int_new:N \g_list_count


\NewDocumentCommand{\ProcessRow}{m}{%
  \clist_gset:Nn \g_andrew_clist {#1}%
  \int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
  \int_gset:Nn \l_columncounter_int {\c_one}
  \stepcounter{tableRow}%
  \Alph{tableRow}  &
  \prg_replicate:nn { \g_list_count }{%
    \int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
      \clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
    }{
      \clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
    }
    \int_gincr:N \l_columncounter_int 
  }
}


\newcommand\MakeTable[2][3]{%
  \clist_set:Nn \l_tmpa_clist {#2}  
  \setcounter{tableRow}{0}  % initialise
  \setcounter{tableCol}{#1}
  \begin{tabular}{*{\arabic{tableCol}}{c}}
    & x & y \tabularnewline
    \hline
    \clist_map_inline:Nn \l_tmpa_clist {%
      \ProcessRow{##1} \tabularnewline
    }
  \end{tabular}

}


\begin{document}


\ExplSyntaxOff
\MakeTable{ {1,2}, {5,6}, {3,{$\overrightarrow{AB}$}}}

\MakeTable[4]{ {1,2,3}, {4,5,6}, {7,8,{$\overrightarrow{AB}$}}}

\end{document}

Bildbeschreibung hier eingeben

Aktualisieren

\documentclass{article}
\usepackage{xparse,etoolbox}
\usepackage{l3regex}

\newcounter{tableRow}       % for counting the number of rows in the table
\newcounter{tableCol}       % for counting the number of columns

\ExplSyntaxOn
\clist_new:N \g_andrew_argument_clist
\clist_new:N \g_andrew_clist
\clist_new:N \l_andrew_data_clist
\seq_new:N \l_andrew_header_seq

\int_new:N \l_columncounter_int
\int_new:N \g_list_count

\NewDocumentCommand{\ProcessRow}{m}{%
  \clist_gset:Nn \g_andrew_clist {#1}%
  \int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
  \int_gset:Nn \l_columncounter_int {\c_one}
  \stepcounter{tableRow}%
  \Alph{tableRow}  &
  \prg_replicate:nn { \g_list_count }{%
    \int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
      \clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
    }{
      \clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
    }
    \int_gincr:N \l_columncounter_int 
  }
}

\NewDocumentCommand{\ProcessHeader}{m}{%
  \clist_gset_eq:NN \g_andrew_clist #1%
  \int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
  \int_gset:Nn \l_columncounter_int {\c_one}
  & %
  \prg_replicate:nn { \g_list_count }{%
    \int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
      \clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
    }{
      \clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
    }
    \int_gincr:N \l_columncounter_int 
  }
}



\newcommand\MakeTable[1]{%
  \clist_set:Nn \g_andrew_argument_clist {#1} % Store the full clist first
  \clist_set:Nx \l_tmpb_clist {\clist_item:Nn \g_andrew_argument_clist {1}} % Extract the first line to get the header descriptions
  \int_set:Nn \l_tmpa_int {\clist_count:N \l_tmpb_clist}
  \int_incr:N \l_tmpa_int
  \seq_clear:N \l_andrew_header_seq
  \tl_set:Nn \l_tmpa_tl {#1} % Grab the argument again 
  \regex_replace_all:nnN { \w+= }{ } \l_tmpa_tl % Replace the x= values with nothing 
  \clist_set:NV \l_andrew_data_clist  {\l_tmpa_tl} %making a new clist again
  % Get the headers
  \clist_map_inline:Nn \l_tmpb_clist { %
    \tl_set:Nn \l_tmpa_tl {##1}
    \seq_clear:N \l_tmpb_seq
    \seq_set_split:NnV \l_tmpb_seq {=} {\l_tmpa_tl}
    \seq_gput_right:Nx \l_andrew_header_seq {\seq_item:Nn \l_tmpb_seq {1}}
  }
  \clist_set_from_seq:NN \l_tmpb_clist \l_andrew_header_seq
  \setcounter{tableRow}{0}  % initialise
  \setcounter{tableCol}{\int_use:N \l_tmpa_int}
  \begin{tabular}{*{\int_eval:n{\l_tmpa_int+1}}{c}}
    \ProcessHeader{\l_tmpb_clist}  \tabularnewline % Header frist
    \hline
    \clist_map_inline:Nn \l_andrew_data_clist {% 
      \ProcessRow{##1} \tabularnewline
    }
  \end{tabular}
}
\ExplSyntaxOff

\begin{document}
\MakeTable{ {x=1,Foo=2}, {5,6}, {3,{$\overrightarrow{AB}$}}}

\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,{z=$\overrightarrow{AB}$},u=14}}

\end{document}

Bildbeschreibung hier eingeben

Antwort3

Sie möchten nicht wirklich verwenden \protected@xappto, sondern lieber \xapptound \expandonce.

(Ich habe auch den Zeilenende-Schutz behoben.)

\documentclass{article}
\usepackage{xparse,etoolbox}

\newcounter{tableRow}       % for counting the number of rows in the table
\newcounter{tableCol}       % for counting the number of columns
\newcommand\tableRows{}     % to hold the contents of the table
\newcommand\tableHeader{}   % to hold the header of the table
\newcommand\MakeTable[1]{%
  \setcounter{tableRow}{0}% initialise
  \setcounter{tableCol}{1}%
  \renewcommand\tableRows{}%
  \renewcommand\tableHeader{}%
  \forcsvlist\ProcessRow{#1}% generate table
  \begin{tabular}{*{\arabic{tableCol}}{c}}
    \tableHeader\\\hline\tableRows\\
  \end{tabular}%
}
\newcommand\ProcessRow[1]{% generate table rows using \ProcessEntry
  \addtocounter{tableRow}{1}%
  \xappto\tableRows{\Alph{tableRow}}% row label in table
  \forcsvlist\ProcessEntry{#1}%
  \gappto\tableRows{\\}%
}
% need to extract key-value pairs from input of the form: key=val
\newcommand\ExtractKeyValuePair[2]{\def\Key{#1}\def\Value{#2}}
\DeclareDocumentCommand\ProcessEntry{>{\SplitArgument{1}{=}}m}{% add entries to row
    \ExtractKeyValuePair#1% key-value pairs --> \Key and \Value
    \ifnum\value{tableRow}=1
       \addtocounter{tableCol}{1}%
       \xappto\tableHeader{&\expandonce{\Key}}%
    \fi
    \xappto\tableRows{&\expandonce{\Value}}%
}

\begin{document}

\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}

\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

\end{document}

Bildbeschreibung hier eingeben

Eine expl3Implementierung, bei der ich die Schlüssel-Wert-Schnittstelle verwende (oder vielleicht missbrauche).

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\MakeTable}{m}
 {
  \andrew_maketable_main:n { #1 }
 }

\seq_new:N \l__andrew_maketable_cols_seq
\tl_new:N \l__andrew_maketable_body_tl
\int_new:N \l__andrew_maketable_rows_int

\keys_define:nn { andrew/maketable }
 {
  unknown .code:n = { \__andrew_maketable_add:n { #1 } }
 }

\cs_new_protected:Nn \andrew_maketable_main:n
 {
  \seq_clear:N \l__andrew_maketable_cols_seq
  \tl_clear:N \l__andrew_maketable_body_tl
  \int_zero:N \l__andrew_maketable_rows_int
  \clist_map_inline:nn { #1 }
   {
    \__andrew_maketable_add_row:n { ##1 }
   }
  \tl_put_left:Nx \l__andrew_maketable_body_tl
   { & \seq_use:Nn \l__andrew_maketable_cols_seq { & } \exp_not:n { \\ \hline } }
  \begin{tabular}{ c *{ \seq_count:N \l__andrew_maketable_cols_seq } { c } }
  \tl_use:N \l__andrew_maketable_body_tl
  \end{tabular}
 }

\cs_new_protected:Nn \__andrew_maketable_add_row:n
 {
  \int_incr:N \l__andrew_maketable_rows_int
  \tl_put_right:Nx \l__andrew_maketable_body_tl
   {
    \int_to_Alph:n { \l__andrew_maketable_rows_int }
   }
  \keys_set:nn { andrew/maketable } { #1 }
  \tl_put_right:Nn \l__andrew_maketable_body_tl { \\ }
 }

\cs_new_protected:Nn \__andrew_maketable_add:n
 {
  \seq_if_in:NxF \l__andrew_maketable_cols_seq { \l_keys_key_tl }
   {
    \seq_put_right:Nx \l__andrew_maketable_cols_seq { \l_keys_key_tl }
   }
  \tl_put_right:Nn \l__andrew_maketable_body_tl { & #1 }
 }

\ExplSyntaxOff

\begin{document}

\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}

\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,z=$\overrightarrow{AB}$,u=14}}

\end{document}

Die Sequenz verfolgt die Spaltenüberschriften; in der Token-Listenvariable speichere ich die Zeilen.

Natürlich muss die Reihenfolge, wie in der ursprünglichen Implementierung, strikt eingehalten werden, sonst würden Elemente verlegt werden.

Bildbeschreibung hier eingeben

Antwort4

Ein Ansatz mit Paketwerkzeug.

Wie bei anderen Implementierungen muss die Reihenfolge streng eingehalten werden. Wenn die Reihenfolge der Schlüssel beliebig sein soll, ist ein komplizierterer Ansatz erforderlich.

\documentclass{article}
\usepackage{xinttools}

% helper utilities
\newcommand*\AndrewExtractKey {}
\def\AndrewExtractKey #1#2=#3,{#1#2}
\newcommand*\AndrewExtractValue {}
\def\AndrewExtractValue #1=#2#3,{#2#3}
\makeatletter
    \newcommand*\JohnDoeAlph [1]{\@Alph{#1\relax}}
\makeatother

% Main command
\newcommand*\MakeTable [1]{%
  \fdef\AndrewTableKeys{\xintCSVtoList{#1}}%
  %
  \fdef\AndrewTableKeysFirstRow{\xintNthElt{1}{\AndrewTableKeys}}%
  \fdef\AndrewTableKeysNbColumns
      {\xintNthElt{0}{\xintCSVtoList{\AndrewTableKeysFirstRow}}}% 
  %
  \gdef\AndrewTableKeysRowCount{1}%
  \begin{tabular}{c*{\AndrewTableKeysNbColumns}c}
    \xintFor ##1 in {\AndrewTableKeysFirstRow}\do
      {&\AndrewExtractKey ##1,}\\
    \hline
    \xintFor* ##1 in {\AndrewTableKeys}\do
    {%
      \JohnDoeAlph{\AndrewTableKeysRowCount}%
      \xdef\AndrewTableKeysRowCount{\the\numexpr\AndrewTableKeysRowCount+1}%
      \xintFor ##2 in {##1}\do{&\AndrewExtractValue ##2,}\\%
    }%
  \end{tabular}
}

\begin{document}

\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}

\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,z=$\overrightarrow{AB}$,u=14}}

\end{document}

Blockquote

verwandte Informationen