通用輸入演算法 |
|
func
函式庫的設施,該函式庫旨在讓輸入的工作更直覺。可以在以下位置找到原始碼:檔案:wiki_insecure/func.lua。有兩個特殊的輸入迭代器,numbers()
和 words()
,它們的工作方式類似於非常有用的 io.lines()
迭代器。要列印標準輸入中找到的所有字詞
-- words.lua require 'func' for w in words() do print(w) end
$ lua words.lua < test.txt
func
提供了非常方便的函數 printall()
。它會將序列的所有成員寫入標準輸出。預設情況下,它會以空格分隔每行輸出 7 個項目,但您可以選擇變更這些值。在這種情況下,我們希望每個值在自己的行上printall(words(),'\n')
numbers()
會建立輸入中找到的所有數值的序列。例如,要加總所有輸入數字require 'func' local s for x in numbers() do s = s + x end print(s)
func
定義了一般的 sum()
函數。它會回傳加總值和欄位數目,因此可以輕鬆計算平均值。local s,n = sum(numbers()) print('average value =',s/n)
numbers()
會尋找檔案中看起來像數字的所有內容,並會安全地忽略所有其他內容。因此,它對於高度註解的資料或輸出檔案很有用。這些迭代器會接收一個選用的額外參數,可以是檔案或字串。例如,要列印命令列參數傳遞的檔案中項目的加總值和數目f = io.open(arg[1]) print(sum(numbers(f))) f:close()
copy()
的簡化定義function copy(iter) local res = {} local k = 1 for v in iter do res[k] = v k = k + 1 end return res end
t = copy(numbers '10 20 30') -- will be {10,20,30} s = sum(list(t)) -- will be 60
list()
,它允許表格用作序列。使用這些函數來操作陣列很常見,因此如果您傳遞表格,會自動假設為 list()
。要以特定格式列印數字陣列,可以使用類似 printall(t,' ',5,'%7.3f')
的指令來適當地格式化它們。以下是系統指令 sort 的實作方式,它使用 printall()
函數來輸出序列的每個值。我不能簡單地說 table.foreach(t,print)
,因為該操作會同時傳遞索引和值,因此我實際上還會取得行號!t = copy(io.lines()) table.sort(t) printall(t,'\n') -- try table.foreach(t,print) and see!
sort()
函數後,它就會變成一行式的printall(sort(io.lines()),'\n')
slice()
函數來迭代序列的部分。這是通過一個迭代函數、一個起始索引和一個項目數量來實現的。例如,這是 head
命令的一個簡單版本;它顯示了輸入的前十行。printall(slice(io.lines(),1,10),'\n')
require 'func' print(count(words()))
count()
函數並不太有用。但是它可以用一個函數來選擇要計數的項目。例如,這給我提供了 Lua 文件中有多少個公共函數的粗略了解。(如果我沒有將匹配約束在開頭,它也會選取本地函數和匿名函數)require 'func' print(count(io.lines(),matching '^%s*function'))
matching()
是以下簡單函數。它建立了一個封閉函數(與本地環境綁定的函數),並對序列中的每一個項目進行調用function matching(s) local strfind = string.find return function(v) return strfind(v,s) end end
當然您可以在這些操作中使用任何序列。如果您載入了非常有用的 lfs(Lua 文件系統)函式庫,則 t = copy_if(lfs.dir(path),matching '%.cpp$')
將使用擴展名為 .cpp
的所有文件填滿一個列表。
修改 count()
輸入的另一種有用的方法是使用 unique()
函數
-- number of unique words in a file print(count(unique(words())))
unique()
函數並不是按照通常的方式實現的,後者需要先對序列進行排序。相反,它使用 count_map()
函數建立一個映射表,其中鍵為項目,值為計數。一旦我們有了 keys()
函數(這是 list()
函數的備選函數),其餘操作就很簡單了function unique(iter) local t = count_map(iter) return keys(t) end
table.foreach(count_map(words()),print)
join()
起來會很有用。這將打印出兩個文件之間的差異for x,y in join(numbers(f1),numbers(f2)) do print(x-y) end
{ print $1/$4, $3/$4 }
func
函式庫會為此目的提供迭代器 fields()
。以下是等價的 Lua 程式碼for x,y,z in fields{1,3,4} do print(x/z,y/z) end
print(count(fields{7},greater_than(44000)))
{ if ($7 > 44000) k++ } END { print(k) }
fields()
函數可以用任何輸入分隔符。這會從逗號分隔的檔案中讀取一組值——請注意,傳遞 n 而不傳遞欄位識別碼列表等於 {1,2,...n}for x,y in fields({1,2},',',f) do ...
for x,y in fields(2,',',f) do ... --equivalent--
random()
的轉錄記載,用來建立一個含隨機值的表格> tt = copy(random(10000)) > = sum(tt) 5039.542771691 10000
local t = {} local random = math.random for i = 1,1e6 do t[i] = random() end
words()
和 numbers()
的執行時間。)其優勢在於較少錯誤的程式碼;泛型編程人員認為明確迴圈「繁瑣且容易出錯」,如同 Stroustrup 所述。第二個反對意見是,這會導致奇怪且不自然的程式碼。對於 C++ 來說,這確實有可能發生,這是因為(讓我們面對現實吧)C++ 實際上並未適合函式風格;沒有閉包,而且嚴格的靜態輸入會不斷造成阻礙,導致所有內容都必需為範本。此風格更適合 Lua - 使用 Boost Lambda 函式庫時,用 C++ 執行此程式並不會好讀一半
-- sum of squares of input data using an internal iterator for_each(numbers(),function(v) s = s + v*v end)
-- sum of squares of input data using an external iterator for v in numbers() do s = s + v*v end
f
不是字串,則 words(f)
將使用檔案物件 f
。事實上,f
可以是任何具有 read
方法的物件。此程式碼假設的內容是 f:read()
將會傳回下一行的輸入文字。這裡是一個較複雜的範例,我在其中建立了一個類別 Files
,用來允許我們從檔案列表中讀取內容。其顯而易見的應用程式是模仿 AWK 的行為,讓命令列中的每個檔案都成為標準輸入的一部分。Files = {} function Files.create(list) local files = {} files.list = {} local n = table.getn(list) for i = 1,n do files.list[i] = list[i] end files.open_next = Files.open_next files.read = Files.read files:open_next() return files end function Files:open_next() if self.f then self.f:close() end local nf = table.remove(self.list,1) if nf then self.f = io.open(nf) return true else self.f = nil return false end end function Files:read() local ret = self.f:read() if not ret then if not self:open_next() then return nil end return self.f:read() else return ret end end
Files.create()
中有一個傳統的複製表格迴圈。Lua 程式會傳遞一個名為 arg
的全域表格,其中包含命令列引數 arg[1]
、arg[2]
等。但其中也有 arg[0]
,也就是腳本名稱,還有 arg[-1]
即為實際的程式名稱。有問題的明確迴圈就是要確定我們不會複製那些欄位!files = Files.create(arg) printall(words(files))
func
都是該主題下直接的變型;函式和迭代器當作閉包使用。PiL [ref?] 的第 7.1 節很好地說明了這些問題,而我使用 allwords
範例當作 words()
和 numbers()
的基礎。fields()
最初是用一種天真的方式實作,輪流擷取每個欄位,但過後則透過建立自訂正規表示式改用一通呼叫 string.find()
來實作。例如,如果需要以逗號分隔的欄位 1 和 3,那麼 regexp 看起來就像這樣 - 欄位定義為 不是 逗號的任何內容,我們使用 () 擷取所需的欄位。'%s*([^,]+),[^,]+,([^,])'
func
作業與提供迭代器的任何函式庫一起使用。這通常會大幅簡化程式碼。例如,以下是 luasql 如何使用它的方式。想一下對查詢結果的所有列進行存取的標準方式cur = con:execute 'SELECT * FROM [Event Summaries]' mag = -9 row = cur:fetch({},'a') while row do if row.Magnitude > mag then mag = row.Magnitude end row = cur:fetch(row,'n') end cur:close()
我只要建立一個能持續追蹤 row
的迭代器,就能讓這個過程變得簡單
function rows(cursor) local row = {} return function() return cursor:fetch(row,'a') end end for row in rows(cur) do if row.Magnitude > mag then mag = row.Magnitude end end
cursor:fetch
兩次,而要尋找一個區域的 row
。我們也可以實作一個等同於 fields
的函式function column(fieldname,cursor) local row = {} return function() row = cur:fetch(row,'a') if not row then return nil else return row[fieldname] end end end local minm,maxm = minmax(column('Magnitude',cur))
print(count(column('Magnitude',cur),greater_than(2)))
-- SteveDonovan