我有一个包含多个列的 CSV 文件。其中一列称为 summits。此列包含多个 summits,格式如下:
Summit name (elevation m)
例如
Mount Everest (8848m)
我想要做的是创建一个表格,按降序列出所有独特的峰会。例如像这样:
我已经能够使用 datatool 包创建一个包含唯一条目的列表。
\DTLforeach*{tours}{\Summit=summits}{%
\expandafter\DTLifinlist\expandafter{\Summit}{\uniquesummit}%
{}
{%
\ifdefempty{\uniquesummit}%
{\let\uniquesummit\Summit}%
{%
\eappto\uniquesummit{,\Summit}%
}%
}%
}
剩下的是:
- 区分山顶名称和海拔
- 排序
- 创建表
我以为我已经知道如何创建表格了这个答案,但在我的例子中,所有峰会都在第一行,而不是分散在多行中。我猜原因是我的列表形式略有不同?但为什么呢?
以下是一个可以更好地描述我的问题的 MWE。请注意,在实际的 CSV 文件中,; 是一个制表符。我无法让它在 MWE 中使用制表符:
\documentclass{article}
\usepackage{filecontents}
\usepackage{datatool}
\begin{filecontents*}{summits.csv}
title;summits;description
test1;Matterhorn (4500m);description1
test2;Mount Everest (8848m);description2
test3;K2 (8611m), Kangchenjunga (8586m);description3
test4;Mount Everest (8848m);description4
\end{filecontents*}
\begin{document}
\DTLsetseparator{;}%
\DTLloaddb{tours}{summits.csv}
\newcommand*{\uniquesummit}{}
\DTLforeach*{tours}{\Summit=summits}{%
\expandafter\DTLifinlist\expandafter{\Summit}{\uniquesummit}%
{}{
\ifdefempty{\uniquesummit}%
{\let\uniquesummit\Summit}%
{%
\eappto\uniquesummit{,\Summit}%
}%
}%
}
% Shows that I can find the unique summits
List of unique summits: \uniquesummit.
\bigskip
% This I would like to generate based on the summits.csv
\begin{tabular}{l l l}
1 &Mount Everest & 8848\\
2 & K2 & 8611\\
3 & Kangchenjunga & 8586\\
4 & Matterhorn & 4500\\
\end{tabular}
\end{document}
答案1
以下用于expl3
数据处理。其工作方式如下:
首先读取文件的第一行并解析标题。找到名为的列summits
,以便在进一步处理时我们知道哪一列是感兴趣的。
接下来逐行读取文件并提取第 $n$ 列(名为 的列summits
)。用逗号分隔该列(以获取该行中的所有峰),然后通过正则表达式提取每个峰的名称和高度。将找到的值放入单个seq
变量中。
之后,我们从中删除重复项seq
,并按山的高度进行排序(降序)。
然后输出表格。seq
现在已排序的表格中的每个元素都被赋予了\readmountains_output_single:n
每行的格式。
这一切都由宏完成\readmountains
,它接受一个可选参数和一个强制参数。强制参数是文件名,可选参数是列分隔符(默认为 Tab,如果^^I
你想直接从 TeX 中输入,但可选参数希望分隔符转义,因此你需要提供\^^I
一个 Tab,这是默认值)。
(正在进行一些错误检查,因此
\documentclass[]{article}
\begin{filecontents*}{\jobname.csv}
title;summits;description
test1;Matterhorn (4500m);description1
test2;Mount Everest (8848m);description2
test3;K2 (8611m), Kangchenjunga (8586m);description3
test4;Mount Everest (8848m);description4
\end{filecontents*}
\ExplSyntaxOn
\ior_new:N \g_readmountains_file_ior
\seq_new:N \l_readmountains_mountains_seq
\seq_new:N \l_readmountains_extract_seq
\int_new:N \g_readmountains_output_int
\int_new:N \l_readmountains_col_int
\int_new:N \l_readmountains_catcode_int
\int_new:N \l_readmountains_line_int
\seq_new:N \l_readmountains_csv_seq
\scan_new:N \s_readmountains_end
\msg_new:nnn { readmountains } { no-summits }
{ Column ~ `summits' ~ not ~ found. ~ Aborting. }
\msg_new:nnn { readmountains } { short-line }
{
Short ~ line ~ encountered ~ in ~ line ~
\int_use:N \l_readmountains_line_int. ~ Aborting.
}
\msg_new:nnn { readmountains } { malformatted-summit }
{
Malformatted ~ summit ~ encountered ~ in ~ line ~
\int_use:N \l_readmountains_line_int. ~ Aborting.
}
\NewDocumentCommand \readmountains { O{\^^I} m }
{
\readmountains:Nn #1 {#2}
}
\cs_new_protected:Npn \readmountains:Nn #1#2
{
\readmountains_read_file:NnN #1 {#2} \l_readmountains_mountains_seq
\seq_remove_duplicates:N \l_readmountains_mountains_seq
\readmountains_sort:N \l_readmountains_mountains_seq
\readmountains_output:N \l_readmountains_mountains_seq
\use_none:n \s_readmountains_end
}
\cs_new_protected:Npn \readmountains_read_file:NnN #1#2#3
{
\int_set:Nn \l_readmountains_catcode_int { \char_value_catcode:n { `#1 } }
\char_set_catcode_other:N #1
\ior_open:Nn \g_readmountains_file_ior {#2}
\seq_clear:N #3
\ior_get:NN \g_readmountains_file_ior \l_tmpa_tl
\exp_args:NNx \seq_set_split:NnV \l_readmountains_csv_seq
{ \char_generate:nn { `#1 } { 12 } }
\l_tmpa_tl
\int_zero:N \l_readmountains_col_int
\seq_map_inline:Nn \l_readmountains_csv_seq
{
\int_incr:N \l_readmountains_col_int
\str_if_eq:nnT { summits } {##1} { \seq_map_break: }
}
\str_if_eq:eeF
{ \seq_item:Nn \l_readmountains_csv_seq \l_readmountains_col_int }
{ summits }
{
\msg_error:nn { readmountains } { no-summits }
\readmountains_use_none_delimit_by_s_end:w
}
\int_set:Nn \l_readmountains_line_int \c_one_int
\ior_map_inline:Nn \g_readmountains_file_ior
{
\int_incr:N \l_readmountains_line_int
\exp_args:NNx \seq_set_split:Nnn \l_readmountains_csv_seq
{ \char_generate:nn { `#1 } { 12 } }
{##1}
\int_compare:nNnT
\l_readmountains_col_int > { \seq_count:N \l_readmountains_csv_seq }
{
\msg_error:nn { readmountains } { short-line }
\ior_map_break:n { \readmountains_use_none_delimit_by_s_end:w }
}
\exp_args:Nnnx
\seq_set_split:Nnn \l_readmountains_csv_seq {,}
{ \seq_item:Nn \l_readmountains_csv_seq \l_readmountains_col_int }
\seq_map_inline:Nn \l_readmountains_csv_seq
{
\tl_if_empty:nF { ####1 }
{
\regex_extract_once:nnNF { (.*)\s\((\d+)m\) } { ####1 }
\l_readmountains_extract_seq
{
\msg_error:nn { readmountains } { malformatted-summit }
\readmountains_use_none_delimit_by_s_end:w
}
\seq_push:Nx #3
{
{ \seq_item:Nn \l_readmountains_extract_seq {2} }
{ \seq_item:Nn \l_readmountains_extract_seq {3} }
}
\use_none:n \s_readmountains_end
}
}
}
\ior_close:N \g_readmountains_file_ior
\char_set_catcode:nn { `#1 } \l_readmountains_catcode_int
}
\cs_new_protected:Npn \readmountains_sort:N #1
{
\seq_sort:Nn #1
{
\int_compare:nNnTF { \use_ii:nn ##1 } < { \use_ii:nn ##2 }
\sort_return_swapped:
\sort_return_same:
}
}
\cs_new_protected:Npn \readmountains_output:N #1
{
\int_gzero:N \g_readmountains_output_int
\begin { tabular } { r l r }
\seq_map_function:NN #1 \readmountains_output_single:n
\end { tabular }
}
\cs_new_protected:Npn \readmountains_output_single:n #1
{
\int_gincr:N \g_readmountains_output_int
\int_use:N \g_readmountains_output_int .
& \use_i:nn #1
& \use_ii:nn #1
\\
}
\cs_new:Npn \readmountains_use_none_delimit_by_s_end:w #1 \s_readmountains_end
{}
\ExplSyntaxOff
\begin{document}
% since the file was written with ; as the delimiter use that
\readmountains[\;]{\jobname.csv}
\end{document}