csv 是一种棘手的格式,通常不建议使用不完整的解析器读取 csv 数据。当然,这从未阻止任何人这样做。
但是,如果要按书去做,咒语是这样的。
package require csv
package require struct::matrix
创建一个矩阵数据结构,它将保存来自 csv 文件的数据,并使我们能够使用它:
::struct::matrix m
m
现在是当前命名空间中的命令(您可以将命名空间添加到名称中以在另一个命名空间中创建它)。完成矩阵后,您应该调用m destroy
.
您还可以让模块命名您的矩阵命令并通过变量使用它:
set m [::struct::matrix]
现在您有了一个矩阵,您可以将 csv 文件的内容加载到其中:
set ch [open holiday.csv]
::csv::read2matrix $ch m , auto
chan close $ch
(您可以检查它m serialize
(我添加了一些换行符以提高可读性):)
3 3 {
{{Dec 25} Christmas {US Holiday }}
{{Jan 1} {New Year} {US Holiday }}
{{Jan 19} {Martin Luther King} {US Holiday}}
}
要搜索给定日期:
proc findDate date {
m search column 0 $date
}
要在第三列中搜索给定的字符串:
proc findStr str {
m search -glob column 2 $str*
}
(由于列中的某些值有垃圾尾随空格,我们需要按string match
规则 ( -glob
) 搜索,而不是默认的完全匹配。)
这两个命令都返回搜索出现的单元格列表。单元格由一对列/行值指定,例如{0 2}
用于第一列、第三行中的匹配。
如果我们只想找出给定的日期是否出现在文件中,这个谓词会做:
proc hasDate date {
expr {[llength [findDate $date]] > 0}
}
但是,如果我们想确定日期所在的行确实包含美国假期,我们还需要检查第三列。有很多方法可以做到这一点。对于其中一个,我首先需要一个辅助函数来将单元描述符列表转换为行号列表:
proc getRowNums cells {
lmap cell $cells {lindex $cell 1}
}
现在我可以像这样检查日期和字符串:
proc hasDateAndString {date str} {
set r1 [getRowNums [findDate $date]]
set r2 [getRowNums [findStr $str]]
# do any rows overlap?
foreach r $r1 {
if {$r in $r2} {
return true
}
}
return false
}
这通过检查两个行列表是否共享任何值来工作。如果他们不这样做,则该日期不指定美国假期。
另一种方法是逐行遍历矩阵并检查每一行的相关项:
proc hasDateAndString {date str} {
for {set row 0} {$row < [m rows]} {incr row} {
lassign [m get row $row] dateVal - strVal
if {$date eq $dateVal && [string match $str* $strVal]} {
return true
}
}
return false
}
对于我查看的每一行,我使用提取值列表m get row $row
并将lassign
这些值提取到我可以检查的变量中。
注意:struct::matrix
不是很好用。人们说它很慢,更糟糕的是它不太擅长隐藏底层细节。在某些情况下,使用普通 Tcl I/O 读取 csv 文件的工作量较少::csv::split
,用于从每一行获取字段并在使用后将它们写回::csv::join
以再次将它们转换为 csv 字符串。
文档:chan、csv、expr、for、foreach、if、lassign、llength、lmap、open、package、proc、return、set、string、struct::matrix
Tcl 8.4 和 8.5 的 lmap 替换