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?;旧峡梢灾苯釉谥悄苣J较绿幚磉@些常見的文件格式?(那些遵循同一模式的文件):?'?/etc/fstab?','/?proc/XXXX/status','ssh_config'?和?'pdatedb.conf'。
請注意,?config.read可以傳入類文件對象的參數;如果它不是一個字符串,并支持read方法,才可以使用。例如,若要從字符串中讀取一個配置,請使用stringio.open?.
總結
以上是生活随笔為你收集整理的lua工具库penlight--06数据(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让JavaScript回归函数式编程的本
- 下一篇: MySQL Cluster 配置详细介绍