lua工具库penlight--06数据(一)
这篇太长了,分了两部分。(这个是机器翻译之后我又校对了一下,以后的都这样,人工翻译太累了。)
读数据文件
首先考虑清楚,你的确需要一个自定义的文件读入器吗?如果是,你能确定有能力写好吗?
正确,稳健,快速,当如先得把第一项处理好。
在Unix世界里常见的数据文件夹是配置文件。在Java世界里也被叫做属性文件。
# Read timeout in seconds
read.timeout=10
# Write timeout in seconds
write.timeout=10
下面是简单的Lua的实现:
-- property file parsing with Lua string patterns
props = []
for line in io.lines() do
if line:find('#',1,true) ~= 1 and not line:find('^%s*$') then
local var,value = line:match('([^=]+)=(.*)')
props[var] = value
end
end
非常简洁,不过用了字符串匹配技巧,对于读者来说不易读懂。
下面是利用Penlight的实现:
require 'pl'
stringx.import()
props = []
for line in io.lines() do
if not line:startswith('#') and not line:isspace() then
local var,value = line:splitv('=')
props[var] = value
end
end
显然上面的代码可以自为文档,而不需要到处写注释。它的速度稍微有点慢
,但是事实上脚本的速度有I/O决定,因此一下优化是必须的。
读无结够的文本数据
文本经常是无结构的。pl.input提供了许多简化操作的函数。例如统计文本
中的单词数量:
-- countwords.lua
require 'pl'
local k = 1
for w in input.words(io.stdin) do
k = k + 1
end
print('count',k)
或者计算平均数:
-- average.lua
require 'pl'
local k = 1
local sum = 0
for n in input.numbers(io.stdin) do
sum = sum + n
k = k + 1
end
print('average',sum/k)
这些脚本可以通过进一步排除循环提高效率。下面这个例子,使用seq.sum函数
计算数字序列的和。
-- average2.lua
require 'pl'
local total,n = seq.sum(input.numbers())
print('average',total/n)
一个更简单的例子是,从输入中提取参数,如下:
-- countwords2.lua
require 'pl'
print('count',seq.count(input.words()))
一个有用的序列生成器要能直接读取字符串。下面这个例子计算文件中每行的数字和:
-- sums.lua
for line in io.lines() do
print(seq.sum(input.numbers(line))
end
读分隔文件
读取分隔文件是常有的事,它们或以空格、或者逗号分隔,或许还有初始的列头。
如下例:
EventID Magnitude LocationX LocationY LocationZ
981124001 2.0 18988.4 10047.1 4149.7
981125001 0.8 19104.0 9970.4 5088.7
981127003 0.5 19012.5 9946.9 3831.2
...
input.fields可以提取列,并且可以设置分隔符(默认空格)。
下面是计算所有事件里x的平均位置:(即上表中的LocationX )
-- avg-x.lua
require 'pl'
io.read() -- skip the header line
local sum,count = seq.sum(input.fields {3})
print(sum/count)
input.fields可以提取列,并且可以设置分隔符(默认空格)。
下面是计算所有事件里x的平均位置:(即上表中的LocationX )
-- avg-x.lua
require 'pl'
io.read() -- skip the header line
local sum,count = seq.sum(input.fields {3})
print(sum/count)
input.fields可以使用字段数或列数,可以从1开始也可以从其它列开始。如果你传递一个字段计数,你会得到该计数的每个字段:
for id,mag,locX,locY,locZ in input.fields (5) do
....
end
input.fields 默认会尝试将每个字段转换为数字。它将跳过不匹配模式的字段,如果任何字段都不能转换为数字时会中止脚本。input.fields第二个参数是分隔符,默认是空格。如果传入’ ‘,会匹配 '任意数目的空格',即 '%s+',你可以使用 任意的Lua 字符串匹配模式。
第三个参数是数据源,默认为标准输入 (由input.create_getter定义)。它假定数据源有一个read的方法,可以产生下一行,即它是一个 '类文件' 对象。作为一种特殊情况,一个字符串,将分成多行:
> for x,y in input.fields(2,' ','10 20\n30 40\n') do print(x,y) end
10 20
30 40
注意对于坏的字段,默认行为是显示出错的行号:
> for x,y in input.fields(2,' ','10 20\n30 40x\n') do print(x,y) end
10 20
line 2: cannot convert '40x' to number
Input.fields的这种行为是适当的,第四个可选参数是一个选项表: {no_fail=true}意味着尝试转换后失败后,仅返回字符串,而不是向 AWK(译注:一个*nix下的文本匹配工具)继续运行。你有责任检查返回的字段的类型。{no_convert=true}是否将所有字段都作为字符串返回。
有时将整个数据放到内存中,会很有用,如提取的列的操作。Penlight专门为阅读此类型的数据,提供了一个灵活的data解析器。例如看起来像这样的文件:
x,y
10,20
2,5
40,50
data.read将创建如下的表,每一行创建一个子表:
> t = data.read 'test.txt'
> pretty.dump(t)
{{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
现在,您可以使用提供的方法来分析返回的表。例如,方法column_by_name返回的列的所有表。
-- testdata.lua
require 'pl'
d = data.read('fev.txt')
for _,name in ipairs(d.fieldnames) do
local col = d:column_by_name(name)
if type(col[1]) == 'number' then
local total,n = seq.sum(col)
utils.printf("Average for %s is %f\n",name,total/n)
end
end
data.read有点小聪明,默认情况下它期望第一行是列名称,除非其中都是何数字。它会尝试推导第一行的列分隔符。有时它会猜错,当然可以显式指定分隔符。第二个可选参数是一个选项表: 可以重写delim (字符串匹配模式),fieldnames(列表或逗号分隔的字符串),是否转换no_convert (默认是要转换),numfields (列表中的列的索引) 和thousands_dot (千位在 Excel CSV 中的分隔符是 ‘. ')
一个非常强大的功能是在这种数据上执行类似于 SQL 的查询:
-- queries on tabular data
require 'pl'
local d = data.read('xyz.txt')
local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
for x,y,z in q do
print(x,y,z)
end
请注意查询的格式限于以下语法:
FIELDLIST [ 'where' CONDITION ] [ 'sort by' FIELD [asc|desc]]
任何有效的 Lua 代码都可以出现在CONDITION中;请记住它并不是SQL,您必须使用== (此警告来自于经验)。
若想这样,字段名称必须是 Lua 的标识符。所以read会改变字段名,这样,所有非字母数字字符替换为下划线。然而, original_fieldnames字段总是包含原始字段名。
read可以很好的处理标准 CSV 文件,但是它不会尝试成为一个全面的 CSV 分析器。加上csv=true选项,可以处理双引号字段,这些字段可以包含逗号,尾随逗号也是有意义的。
电子表格程并不总是处理这种数据最好的工具,这看起来可能对某些人很奇怪。下面是一个玩具 CSV 文件 ;要认识这个问题,想象类似下面的数千行和数十列:
Department Name,Employee ID,Project,Hours Booked
sales,1231,overhead,4
sales,1255,overhead,3
engineering,1501,development,5
engineering,1501,maintenance,3
engineering,1433,maintenance,10
任务是减少相关的行和列的数据、 或许做一些处理行的数据,并将结果写到一个新的 CSV 文件。write_row方法使用分隔符将行写入一个文件 ;Data.select_row类似Data.select,除了它会循环访问行,而不是字段;如果我们处理很多列,这是必要的 !
names = {[1501]='don',[1433]='dilbert'}
keepcols = {'Employee_ID','Hours_Booked'}
t:write_row (outf,{'Employee','Hours_Booked'})
q = t:select_row {
fields=keepcols,
where=function(row) return row[1]=='engineering' end
}
for row in q do
row[1] = names[row[1]]
t:write_row(outf,row)
end
Data.select_row和Data.select可以传递一个表,并指定查询 ;字段表,定义条件和可选参数的sort_by函数。它不是必须的,但如果我们有更复杂的行条件 (如属于指定的一组)。
如果不能存到hackery(不知如何翻译)如全局变量,将不能作为一般条件查询字符串。
在 1.0.3,您可以指定所选列的显式转换函数。例如,这是 Unix 日期戳一个日志文件:
Time Message
1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00
1266840760 closure data 0.000000 1972 1972 0
1266840760 ++ 1266840760 EE 1
1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00
1266840764 closure data 0.000000 1972 1972 0
我们想把第一列作为实际日期对象,这样convert可以从1列显式转换字段。(请注意第一次我们必须显式转换字符串为数字)。
Date = require 'pl.Date'
function date_convert (ds)
return Date(tonumber(ds))
end
d = data.read(f,{convert={[1]=date_convert},last_field_collect=true})
这给了我们一个两列数据集,其中的第一列包含日期对象,第二列包含该行的其余部分。查询可以很容易找出事件发生的一天:
q = d:select "Time,Message where Time:weekday_name()=='Sun'"
数据没有来从文件,也不一定来自于实验室或会计部。在 Linux 上, ps aux为您提供在您的机器上运行的所有进程的完整列表。直接把ps aux的结果送入data.read,并对它执行有用的查询。请注意非标识符字符,如 %会转化为下划线:
require 'pl'
f = io.popen 'ps aux'
s = data.read (f,{last_field_collect=true})
f:close()
print(s.fieldnames)
print(s:column_by_name 'USER')
qs = 'COMMAND,_MEM where _MEM > 5 and USER=="steve"'
for name,mem in s:select(qs) do
print(mem,name)
end
我一直崇拜 AWK 编程语言 ;使用filter中,您可以让lua作为简单的AWK:
-- printxy.lua
require 'pl'
data.filter 'x,y where x > 3'
数据文件没有标题,没有字段名称,是常见现象。如果所有字段都是数字,data.read把此类文件为一个特殊的例外。由于在查询表达式中无法使用的列名称,您可以使用 类AWK的 索引,例如 ' $1、 $2,$1 > 3'。我有一个小的可执行脚本,在我的系统称为lf这看起来像这样:
#!/usr/bin/env lua
require 'pl.data'.filter(arg[1])
它可用于一般的从数据中提取列的筛选器命令。(列规格可能是表达式或甚至常量)
$ lf '$1,$5/10' < test.dat
(与 AWK类似,请注意在此命令使用单引号,这样可以防止shell解释列索引。如果您在 Windows 上,你必须用在双引号引起传递给您的批处理文件参数)。
作为教程的资源,看看test-data.lua的使用,以及其他例子的评论。
从read或Data.copy_select查询返回的数据,基本上是只是数组的行: {{1,2},{3,4}}。所以您可以使用read处理任何类似数组的数据集,或者任何其它类似的函数处理。尤其是,在array2d函数中数据工作正常。事实上,这些函数可以作为方法 ;例如array2d.flatten ,可以直接给我们一维列表:
v = data.read('dat.txt'):flatten()
LuaMatrix期望数据像矩阵一样正确的形状:
> matrix = require 'matrix'
> m = matrix(data.read 'mat.txt')
> = m
1 0.2 0.3
0.2 1 0.1
0.1 0.2 1
> = m^2 -- same as m*m
1.07 0.46 0.62
0.41 1.06 0.26
0.24 0.42 1.05
write将写入矩阵文件。
最后,可以使用全局变量_DEBUG打印出查询生成并动态编译的实际迭代器函数。通过使用代码生成,我们可以任优化查询的性能。
> lua -lpl -e "_DEBUG=true" -e "data.filter 'x,y where x > 4 sort by x'" < test.txt
return function (t)
local i = 0
local v
local ls = {}
for i,v in ipairs(t) do
if v[1] > 4 then
ls[#ls+1] = v
end
end
table.sort(ls,function(v1,v2)
return v1[1] < v2[1]
end)
local n = #ls
return function()
i = i + 1
v = ls[i]
if i > n then return end
return v[1],v[2]
end
end
10,20
40,50
读取配置文件
配置模块提供了把几种类型的配置文件转换为一个 Lua 表的简单方法。考虑的简单示例:
# test.config
# Read timeout in seconds
read.timeout=10
# Write timeout in seconds
write.timeout=5
#acceptable ports
ports = 1002,1003,1004
可以使用config.read读,使用pretty.write显示结果 :
-- readconfig.lua
local config = require 'pl.config'
local pretty= require 'pl.pretty'
local t = config.read(arg[1])
print(pretty.write(t))
lua readconfig.lua test.config的输出是:
{
ports = {
1002,
1003,
1004
},
write_timeout = 5,
read_timeout = 10
}
config.read将产生所有键/值对,忽略 # 注释,并确保键名称是正确的 Lua 标识符,通过非标识符字符替换为 _。如果这些值是数字,他们将被转换。(所以t.write_timeout的值是数字 5)。此外,由逗号分隔的任何值将同样转换为数组。
可以一个反斜杠续行。考虑下面这行:
names=one,two,three, \
four,five,six,seven, \
eight,nine,ten
此外支持 Windows 风格的 INI 文件。INI 文件的部分结构自然转换为嵌套表在 Lua 中:
; test.ini
[timeouts]
read=10 ; Read timeout in seconds
write=5 ; Write timeout in seconds
[portinfo]
ports = 1002,1003,1004
输出为:
{
portinfo = {
ports = {
1002,
1003,
1004
}
},
timeouts = {
write = 5,
read = 10
}
}
你现在可以这样用t.timeouts.write引用write timeout.
最后一个例子显示config.read 读取逗号分隔的文件的灵活性。
one,two,three
10,20,30
40,50,60
1,2,3
它将生成下表:
{
{ "one", "two", "three" },
{ 10, 20, 30 },
{ 40, 50, 60 },
{ 1, 2, 3 }
}
config.read不是设计为读取所有的 CSV 文件,但打算支持没有键-值对的结构,如 '/ etc/passwd' 等一些 Unix 配置文件 。
这个函数想成为读取配置的瑞士军刀,它无需做出假设,你也可能不喜欢他们(假设)。所以有一个可选的额外参数,来进行一些控制,可能有以下字段:
{
variablilize = true,
convert_numbers = tonumber,
trim_space = true,
list_delim = ',',
trim_quotes = true,
ignore_assign = false,
keysep = '=',
smart = false,
}
variablilize选项即第一个示例中将write.timeout转化write_timeout 。如果convert_numbers为 true,尝试转换开始像数的任何字符串。您可以指定您自己的函数 (如像 '5224 kb' 的字符串转换为数字)。
trim_space可确保有没有开始或结尾的空白值,list_delim是分割字符 (如可能 Lua 字符串模式 '%s+'.)
例如,在 Unix 中的密码文件是冒号分隔:
t = config.read('/etc/passwd',{list_delim=':'})
这将产生以下输出在我的系统 (只有最后两线所示):
{
...
{
"user",
"x",
"1000",
"1000",
"user,,,",
"/home/user",
"/bin/bash"
},
{
"sdonovan",
"x",
"1001",
"1001",
"steve donovan,28,,",
"/home/sdonovan",
"/bin/bash"
}
}
你可以进入这一个更明智的格式,加上判断哪些用户名是索引( tablex.pairmap函数必须返回value,key!)
t = tablex.pairmap(function(k,v) return v,v[1] end,t)
得到:
{ ...
sdonovan = {
"sdonovan",
"x",
"1001",
"1001",
"steve donovan,28,,",
"/home/sdonovan",
"/bin/bash"
}
...
}
许多常见的 Unix 配置文件可以通过调整这些参数读取。/etc/fstab,选项为{list_delim=‘%s+’,ignore_assign=true}将正确分隔列。在文件里查找’KEY VALUE’是常见的,如/etc/ssh/ssh_config; 选项{keysep=‘ ’}使config.read返回一个表,其中每个key具有一个值value。
在 Linux 中的文件procfs通常使用 ':’作为分隔符
> t = config.read('/proc/meminfo',{keysep=':'})
> = t.MemFree
220140 kB
这一结果是一个字符串,因为tonumber不喜欢它,但把convert_numbers定义为function(s) return tonumber((s:gsub(' kB$',''))) end,可以返回实际数字。(额外的括号是必要因此tonumber仅从gsub中获取的第一个结果)。
tests/test-config.lua':
testconfig([[
MemTotal: 1024748 kB
MemFree: 220292 kB
]],
{ MemTotal = 1024748, MemFree = 220292 },
{
keysep = ':',
convert_numbers = function(s)
s = s:gsub(' kB$','')
return tonumber(s)
end
}
)
smart选项可以让config.read替你一个合理的猜测,例子为tests/test-config.lua。基本上可以直接在智能模式下处理这些常见的文件格式 (那些遵循同一模式的文件): ' /etc/fstab ','/ proc/XXXX/status','ssh_config' 和 'pdatedb.conf'。
请注意, config.read可以传入类文件对象的参数;如果它不是一个字符串,并支持read方法,才可以使用。例如,若要从字符串中读取一个配置,请使用stringio.open .
lua工具库penlight--06数据(一)的更多相关文章
- lua工具库penlight--01简介
lua的设计目标是嵌入式语言,所以和其它动态语言(如python.ruby)相比其自带的库缺少很多实用功能. 好在有lua社区有Penlight,为lua提供了许多强大的功能,接下来的几篇博客,我会简 ...
- lua工具库penlight--06数据(二)
词法扫描 虽然 Lua 的字符串模式匹配是非常强大,但需要更强大的东西.pl.lexer.scan可以提供标记字符串,按标记机分类数字.字符串等. > lua -lpl Lua 5.1.4 C ...
- lua工具库penlight--07函数编程(二)
列表压缩 列表压缩是以紧凑的方式通过指定的元素创建表.在 Python里,你可以说: ls = [x for x in range(5)] # == [0,1,2,3,4] 在 Lua,使用pl.c ...
- lua工具库penlight--02表和数组
类Python的List lua的优美之处在于把数组和关联数组都用table实现了(Python中叫list和dict,C++中叫vector和map). 一般我们把数字索引的table叫做list. ...
- lua工具库penlight--07函数编程(一)
函数编程 序列 Lua 迭代器 (最简单的形式) 是一个函数,可以多次调用返回一个或多个值.for in语句理解迭代器和循环,直到该函数将返回nil. Lua有标准的序列迭代器 (ipairs和pai ...
- lua工具库penlight--09技术选择
模块化和粒度 在理想的世界,一个程序应该只加载它需要的库.Penlight需要额外100 Kb 的字节码来工作.它是简单但却乏味要加载你需要什么: local data = require 'pl.d ...
- lua工具库penlight--08额外的库(一)
额外的库 在这一节中的库不再被认为是Penlight的核心部分,但在需要时,仍提供专门的功能. 简单的输入的模式 Lua 的字符串模式匹配是非常强大,通常您将不需要传统的正则表达式库.即便如此,有时 ...
- lua工具库penlight--04路径和目录
使用路径 程序不应该依赖于奇葩的系统,这样你的代码会难以阅读和移植.最糟糕的是硬编码的路径, windows和Unix的路径分隔符正好相反.最好使用path.join,它可以帮助你解决这个问题. pl ...
- lua工具库penlight--03字符串
字符串提取函数 这些方法也是从Python借鉴来的,但索引从1开始.stringx定义了一些函数如isalpha和isdigit, 用来判断字母和数字:startswith和endswith可以方便用 ...
随机推荐
- centos7搭建svn服务器并支持http方式访问
因为公司其他人员需要,需要在服务器上搭建svn服务,途中遇到不少问题,做下记录 第一步,安装svn 默认centos7是已经安装了svn即subversion 检查是否安装 rpm -qa subve ...
- WCF 之 初识WCF
在编程中服务的作用越来越大了,.net从2.0的 webservice,到3.5之后的WCF,服务的功能越来越强了.现在先从简单的看起,先看看WCF服务的发布. 现在来看看一步一步发布 WCF 服务. ...
- JMeter 十六:加密处理
假设采用MD5进行加密 JMeter 内置的没有MD5加密方法.网上有说采用__MD5函数的,但是我在 Jmeter 2.13 以及 Jmeter 3.2 版本上都没有找到这个函数,官方文档也没有看到 ...
- 好用的Android屏幕适配
前言 网上关于屏幕适配的文章已经铺天盖地了,为什么还要讲?因为网上现在基本都是使用px适配,即每种屏幕分辨率的设备需要定义一套dimens.xml文件.再加上有些手机还有虚拟按键(例如华为),这样就还 ...
- 使用caffe的HDF5数据完毕回归任务
一直在研究怎样用caffe做行人检測问题.然而參考那些经典结构比方faster-rcnn等,都是自己定义的caffe层来完毕的检測任务. 这些都要求对caffe框架有一定程度的了解.近期看到了怎样用c ...
- [Objective-C A]-知识点锦集
1.@autoreleasepool why1 2.retain O-C内存管理和点语法 1>OC内存管理正常情况要使用大量的retain和relrese操作 2>点语法可以减少使用re ...
- 【VBA编程】14.操作工作簿对象
[访问工作簿] 对已经打开的工作簿,可以通过使用索引号来访问工作簿,也可以通过名称来访问工作簿 [代码区域] Sub 访问工作簿() Dim counter As Integer counter = ...
- Struts2的国际化入门
Struts2的国际化入门 Struts2国际化是建立在Java国际化的基础上的,一样是通过提供不同国家/语言环境的消息资源,然后通过ResourceBundle加载指定Locale对应的资源文件,再 ...
- Drupal启动阶段之三:数据库
Drupal在数据库启动阶段仅仅是简单地包含了database.inc文件,然后再注册类加载器: function _drupal_bootstrap_database() { // Initiali ...
- unity3d协同不同设备的代码
unity3d的脚本代码中,Update()函数每一帧都会运行一次. 假设有这两台设备:一台超级四路泰坦计算机,一台旧手机,它们一起运行一个赛跑游戏,Update()函数每一帧运行一次,游戏中的小人就 ...