lua工具库penlight--06数据(一)
這篇太長了,分了兩部分。(這個(gè)是機(jī)器翻譯之后我又校對了一下,以后的都這樣,人工翻譯太累了。)
讀數(shù)據(jù)文件
首先考慮清楚,你的確需要一個(gè)自定義的文件讀入器嗎?如果是,你能確定有能力寫好嗎?
正確,穩(wěn)健,快速,當(dāng)如先得把第一項(xiàng)處理好。
在Unix世界里常見的數(shù)據(jù)文件夾是配置文件。在Java世界里也被叫做屬性文件。
?#?Read?timeout?in?seconds
read.timeout=10
?
#?Write?timeout?in?seconds
write.timeout=10
?
下面是簡單的Lua的實(shí)現(xiàn):
--?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的實(shí)現(xiàn):
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
?
顯然上面的代碼可以自為文檔,而不需要到處寫注釋。它的速度稍微有點(diǎn)慢
,但是事實(shí)上腳本的速度有I/O決定,因此一下優(yōu)化是必須的。
?
讀無結(jié)夠的文本數(shù)據(jù)
文本經(jīng)常是無結(jié)構(gòu)的。pl.input提供了許多簡化操作的函數(shù)。例如統(tǒng)計(jì)文本
中的單詞數(shù)量:
?--?countwords.lua
require?'pl'
local?k?=?1
for?w?in?input.words(io.stdin)?do
?????k?=?k?+?1
end
print('count',k)
?
或者計(jì)算平均數(shù):
?--?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)
這些腳本可以通過進(jìn)一步排除循環(huán)提高效率。下面這個(gè)例子,使用seq.sum函數(shù)
計(jì)算數(shù)字序列的和。
--?average2.lua
require?'pl'
local?total,n?=?seq.sum(input.numbers())
print('average',total/n)
?
一個(gè)更簡單的例子是,從輸入中提取參數(shù),如下:
--?countwords2.lua
require?'pl'
print('count',seq.count(input.words()))
?
一個(gè)有用的序列生成器要能直接讀取字符串。下面這個(gè)例子計(jì)算文件中每行的數(shù)字和:
--?sums.lua
for?line?in?io.lines()?do
?????print(seq.sum(input.numbers(line))
end
?
讀分隔文件
讀取分隔文件是常有的事,它們或以空格、或者逗號(hào)分隔,或許還有初始的列頭。
如下例:
?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可以提取列,并且可以設(shè)置分隔符(默認(rèn)空格)。
下面是計(jì)算所有事件里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可以提取列,并且可以設(shè)置分隔符(默認(rèn)空格)。
下面是計(jì)算所有事件里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可以使用字段數(shù)或列數(shù),可以從1開始也可以從其它列開始。如果你傳遞一個(gè)字段計(jì)數(shù),你會(huì)得到該計(jì)數(shù)的每個(gè)字段:
?for?id,mag,locX,locY,locZ?in?input.fields?(5)?do
?....
?end
?
input.fields?默認(rèn)會(huì)嘗試將每個(gè)字段轉(zhuǎn)換為數(shù)字。它將跳過不匹配模式的字段,如果任何字段都不能轉(zhuǎn)換為數(shù)字時(shí)會(huì)中止腳本。input.fields第二個(gè)參數(shù)是分隔符,默認(rèn)是空格。如果傳入’?‘,會(huì)匹配?'任意數(shù)目的空格',即?'%s+',你可以使用?任意的Lua?字符串匹配模式。
第三個(gè)參數(shù)是數(shù)據(jù)源,默認(rèn)為標(biāo)準(zhǔn)輸入?(由input.create_getter定義)。它假定數(shù)據(jù)源有一個(gè)read的方法,可以產(chǎn)生下一行,即它是一個(gè)?'類文件'?對象。作為一種特殊情況,一個(gè)字符串,將分成多行:
?>?for?x,y?in?input.fields(2,'?','10?20\n30?40\n')?do?print(x,y)?end
?10??????20
?30??????40
?
注意對于壞的字段,默認(rèn)行為是顯示出錯(cuò)的行號(hào):
?>?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的這種行為是適當(dāng)?shù)?#xff0c;第四個(gè)可選參數(shù)是一個(gè)選項(xiàng)表:?{no_fail=true}意味著嘗試轉(zhuǎn)換后失敗后,僅返回字符串,而不是向?AWK(譯注:一個(gè)*nix下的文本匹配工具)繼續(xù)運(yùn)行。你有責(zé)任檢查返回的字段的類型。{no_convert=true}是否將所有字段都作為字符串返回。
有時(shí)將整個(gè)數(shù)據(jù)放到內(nèi)存中,會(huì)很有用,如提取的列的操作。Penlight專門為閱讀此類型的數(shù)據(jù),提供了一個(gè)靈活的data解析器。例如看起來像這樣的文件:
?x,y
?10,20
?2,5
?40,50
?
data.read將創(chuàng)建如下的表,每一行創(chuàng)建一個(gè)子表:
?>?t?=?data.read?'test.txt'
?>?pretty.dump(t)
?{{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
?
現(xiàn)在,您可以使用提供的方法來分析返回的表。例如,方法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有點(diǎn)小聰明,默認(rèn)情況下它期望第一行是列名稱,除非其中都是何數(shù)字。它會(huì)嘗試推導(dǎo)第一行的列分隔符。有時(shí)它會(huì)猜錯(cuò),當(dāng)然可以顯式指定分隔符。第二個(gè)可選參數(shù)是一個(gè)選項(xiàng)表:?可以重寫delim?(字符串匹配模式),fieldnames(列表或逗號(hào)分隔的字符串),是否轉(zhuǎn)換no_convert?(默認(rèn)是要轉(zhuǎn)換),numfields?(列表中的列的索引)?和thousands_dot?(千位在?Excel?CSV?中的分隔符是?‘.?')
一個(gè)非常強(qiáng)大的功能是在這種數(shù)據(jù)上執(zhí)行類似于?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?代碼都可以出現(xiàn)在CONDITION中;請記住它并不是SQL,您必須使用==?(此警告來自于經(jīng)驗(yàn))。
若想這樣,字段名稱必須是?Lua?的標(biāo)識(shí)符。所以read會(huì)改變字段名,這樣,所有非字母數(shù)字字符替換為下劃線。然而,?original_fieldnames字段總是包含原始字段名。
read可以很好的處理標(biāo)準(zhǔn)?CSV?文件,但是它不會(huì)嘗試成為一個(gè)全面的?CSV?分析器。加上csv=true選項(xiàng),可以處理雙引號(hào)字段,這些字段可以包含逗號(hào),尾隨逗號(hào)也是有意義的。
電子表格程并不總是處理這種數(shù)據(jù)最好的工具,這看起來可能對某些人很奇怪。下面是一個(gè)玩具?CSV?文件?;要認(rèn)識(shí)這個(gè)問題,想象類似下面的數(shù)千行和數(shù)十列:
?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
?
任務(wù)是減少相關(guān)的行和列的數(shù)據(jù)、?或許做一些處理行的數(shù)據(jù),并將結(jié)果寫到一個(gè)新的?CSV?文件。write_row方法使用分隔符將行寫入一個(gè)文件?;Data.select_row類似Data.select,除了它會(huì)循環(huán)訪問行,而不是字段;如果我們處理很多列,這是必要的?!
?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可以傳遞一個(gè)表,并指定查詢?;字段表,定義條件和可選參數(shù)的sort_by函數(shù)。它不是必須的,但如果我們有更復(fù)雜的行條件?(如屬于指定的一組)。
如果不能存到hackery(不知如何翻譯)如全局變量,將不能作為一般條件查詢字符串。
在?1.0.3,您可以指定所選列的顯式轉(zhuǎn)換函數(shù)。例如,這是?Unix?日期戳一個(gè)日志文件:
?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
?
我們想把第一列作為實(shí)際日期對象,這樣convert可以從1列顯式轉(zhuǎn)換字段。(請注意第一次我們必須顯式轉(zhuǎn)換字符串為數(shù)字)。
?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})
?
這給了我們一個(gè)兩列數(shù)據(jù)集,其中的第一列包含日期對象,第二列包含該行的其余部分。查詢可以很容易找出事件發(fā)生的一天:
?q?=?d:select?"Time,Message?where?Time:weekday_name()=='Sun'"
?
數(shù)據(jù)沒有來從文件,也不一定來自于實(shí)驗(yàn)室或會(huì)計(jì)部。在?Linux?上,?ps?aux為您提供在您的機(jī)器上運(yùn)行的所有進(jìn)程的完整列表。直接把ps?aux的結(jié)果送入data.read,并對它執(zhí)行有用的查詢。請注意非標(biāo)識(shí)符字符,如?%會(huì)轉(zhuǎn)化為下劃線:
?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'
?
數(shù)據(jù)文件沒有標(biāo)題,沒有字段名稱,是常見現(xiàn)象。如果所有字段都是數(shù)字,data.read把此類文件為一個(gè)特殊的例外。由于在查詢表達(dá)式中無法使用的列名稱,您可以使用?類AWK的?索引,例如?'?$1、?$2,$1?>?3'。我有一個(gè)小的可執(zhí)行腳本,在我的系統(tǒng)稱為lf這看起來像這樣:
?#!/usr/bin/env?lua
?require?'pl.data'.filter(arg[1])
?
它可用于一般的從數(shù)據(jù)中提取列的篩選器命令。(列規(guī)格可能是表達(dá)式或甚至常量)
?$?lf?'$1,$5/10'?<?test.dat
?
(與?AWK類似,請注意在此命令使用單引號(hào),這樣可以防止shell解釋列索引。如果您在?Windows?上,你必須用在雙引號(hào)引起傳遞給您的批處理文件參數(shù))。
作為教程的資源,看看test-data.lua的使用,以及其他例子的評(píng)論。
?
從read或Data.copy_select查詢返回的數(shù)據(jù),基本上是只是數(shù)組的行:?{{1,2},{3,4}}。所以您可以使用read處理任何類似數(shù)組的數(shù)據(jù)集,或者任何其它類似的函數(shù)處理。尤其是,在array2d函數(shù)中數(shù)據(jù)工作正常。事實(shí)上,這些函數(shù)可以作為方法?;例如array2d.flatten?,可以直接給我們一維列表:
?v?=?data.read('dat.txt'):flatten()
?
LuaMatrix期望數(shù)據(jù)像矩陣一樣正確的形狀:
?>?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打印出查詢生成并動(dòng)態(tài)編譯的實(shí)際迭代器函數(shù)。通過使用代碼生成,我們可以任優(yōu)化查詢的性能。
?>?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
?
?
讀取配置文件
配置模塊提供了把幾種類型的配置文件轉(zhuǎn)換為一個(gè)?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顯示結(jié)果?:
?--?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將產(chǎn)生所有鍵/值對,忽略?#?注釋,并確保鍵名稱是正確的?Lua?標(biāo)識(shí)符,通過非標(biāo)識(shí)符字符替換為?_。如果這些值是數(shù)字,他們將被轉(zhuǎn)換。(所以t.write_timeout的值是數(shù)字?5)。此外,由逗號(hào)分隔的任何值將同樣轉(zhuǎn)換為數(shù)組。
可以一個(gè)反斜杠續(xù)行??紤]下面這行:
?names=one,two,three,?\
?four,five,six,seven,?\
?eight,nine,ten
?
此外支持?Windows?風(fēng)格的?INI?文件。INI?文件的部分結(jié)構(gòu)自然轉(zhuǎn)換為嵌套表在?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
???}
?}
?
你現(xiàn)在可以這樣用t.timeouts.write引用write?timeout.
最后一個(gè)例子顯示config.read?讀取逗號(hào)分隔的文件的靈活性。
?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不是設(shè)計(jì)為讀取所有的?CSV?文件,但打算支持沒有鍵-值對的結(jié)構(gòu),如?'/?etc/passwd'?等一些?Unix?配置文件???。
這個(gè)函數(shù)想成為讀取配置的瑞士軍刀,它無需做出假設(shè),你也可能不喜歡他們(假設(shè))。所以有一個(gè)可選的額外參數(shù),來進(jìn)行一些控制,可能有以下字段:
?{
????variablilize?=?true,
????convert_numbers?=?tonumber,
????trim_space?=?true,
????list_delim?=?',',
????trim_quotes?=?true,
????ignore_assign?=?false,
????keysep?=?'=',
????smart?=?false,
?}
?
variablilize選項(xiàng)即第一個(gè)示例中將write.timeout轉(zhuǎn)化write_timeout?。如果convert_numbers為?true,嘗試轉(zhuǎn)換開始像數(shù)的任何字符串。您可以指定您自己的函數(shù)?(如像?'5224?kb'?的字符串轉(zhuǎn)換為數(shù)字)。
trim_space可確保有沒有開始或結(jié)尾的空白值,list_delim是分割字符?(如可能?Lua?字符串模式?'%s+'.)
例如,在?Unix?中的密碼文件是冒號(hào)分隔:
?t?=?config.read('/etc/passwd',{list_delim=':'})
?
這將產(chǎn)生以下輸出在我的系統(tǒng)?(只有最后兩線所示):
?{
???...
???{
?????"user",
?????"x",
?????"1000",
?????"1000",
?????"user,,,",
?????"/home/user",
?????"/bin/bash"
???},
???{
?????"sdonovan",
?????"x",
?????"1001",
?????"1001",
?????"steve?donovan,28,,",
?????"/home/sdonovan",
?????"/bin/bash"
???}
?}
?
你可以進(jìn)入這一個(gè)更明智的格式,加上判斷哪些用戶名是索引(?tablex.pairmap函數(shù)必須返回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?配置文件可以通過調(diào)整這些參數(shù)讀取。/etc/fstab,選項(xiàng)為{list_delim=‘%s+’,ignore_assign=true}將正確分隔列。在文件里查找’KEY?VALUE’是常見的,如/etc/ssh/ssh_config;?選項(xiàng){keysep=‘?’}使config.read返回一個(gè)表,其中每個(gè)key具有一個(gè)值value。
在?Linux?中的文件procfs通常使用?':’作為分隔符
?>?t?=?config.read('/proc/meminfo',{keysep=':'})
?>?=?t.MemFree
?220140?kB
?
這一結(jié)果是一個(gè)字符串,因?yàn)閠onumber不喜歡它,但把convert_numbers定義為function(s)?return?tonumber((s:gsub('?kB$','')))?end,可以返回實(shí)際數(shù)字。(額外的括號(hào)是必要因此tonumber僅從gsub中獲取的第一個(gè)結(jié)果)。
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選項(xiàng)可以讓config.read替你一個(gè)合理的猜測,例子為tests/test-config.lua?;旧峡梢灾苯釉谥悄苣J较绿幚磉@些常見的文件格式?(那些遵循同一模式的文件):?'?/etc/fstab?','/?proc/XXXX/status','ssh_config'?和?'pdatedb.conf'。
請注意,?config.read可以傳入類文件對象的參數(shù);如果它不是一個(gè)字符串,并支持read方法,才可以使用。例如,若要從字符串中讀取一個(gè)配置,請使用stringio.open?.
總結(jié)
以上是生活随笔為你收集整理的lua工具库penlight--06数据(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让JavaScript回归函数式编程的本
- 下一篇: Service的生命周期