Python 清單

lua-users home
wiki

Python

[Python] 是包含清單和字典的熱門語言。Lua 的表格是一個多用途儲存器,介於兩者之間,但本身卻缺乏作為清單的功能。Lua 標準函式庫中的函數 table.inserttable.removetable.getn 能提供表格清單功能(或更具體地稱為 Lua 中的陣列)。以下清單模擬 Python 的清單類型。Python 教學文件的第 5.1 節 [1] 說明了 Python 的清單功能。

實作

我們建立一個類別表格 List,其中包​​含方法。我們使用 Lua 的索引後設方法來呼叫 List 方法(背景資訊請參閱 ClassesAndMethods)。

此版本已針對 Lua 5.1 重新編寫(不過已保留原始版本),並為串接和等式運算子新增了重載。對於切片,起始和結束現在是一個包含式的範圍,更符合 Lua。還有一個 slice_assign 方法,用於 Python 結構 s[i1:i2] = seq

-- SteveDonovan

範例

以下程式碼允許您這樣做

lst = List:new()  -- create an empty List
lst2 = List:new{1,2,"blah",lst}  -- create a list with some items in
lst3 = List {10,20,30}  -- List acts like a 'constructor'
-- Our new methods
lst:append("hello")
lst:extend{"world", 35, 77, "?"}
lst:insert(3,"how?")
lst:remove(35)
q = lst:pop()
print( lst:index("world") )
print( lst:count("?") )
lst:sort()
lst:reverse()
print( lst[-2] )
a = lst:slice(3)
b = lst:slice(-4,-2)
lst:clear()
lst:range(77)

-- We can mix them with Lua's library calls
table.insert(lst,"boo")
print(#lst)

這個互動式會議展示了一些運算。請注意,這些清單物件可以像函數一樣被呼叫,這是 slice 方法的捷徑。

C:\lang\lua\projects\libs>lua
Lua 5.1.2  Copyright (C) 1994-2007 Lua.org, PUC-Rio
> require 'pylist'
> a = List {10,20,30}
> b = List {1,2,3}
> = a..b
{10,20,30,1,2,3}
> = a:slice(2,3)
{20,30}
> = a[-1]
30
> = a:index(30)
3
> a:append(40)
> = a
{10,20,30,40}
> = List {1,2} == List{1,2}
true
> = List {1,2} == List{1,4}
false
> = a(2,-1)
{20}
> = a(-2)
{20,30}


-- Emulation of Python lists
-- Nick Trout
-- See http://www.python.org/doc/current/tut/tut.html, section 5.1
-- Note:The comments before some of the functions are from the Python docs
-- and contain Python code.
-- Written for Lua version 4.0
-- Redone for Lua 5.1, Steve Donovan.

local tinsert = table.insert
local tremove = table.remove
local tsort = table.sort
local write = io.write

-- metatable for our list objects
List = {}
List.__index = List
-- we give the metatable its own metatable so that we can call it like a function!
_ListMT = {}
setmetatable(List,_ListMT)
function _ListMT.__call(tbl,arg)
  return List:new(arg)
end

-- Handle the index event
-- note: this is called if something is _not_ present in the table. These are either 
-- negative indices, method names, or bad indices.
function List:__index(i)
  local typ = type(i)
  if typ=="string" then
    local fn = List[i]
    if not fn then error("Bad List function: "..i) end
    return fn
  elseif typ=="number" then
    if i<0 then
      local sz = #self
      if i<-sz then error("List index out of range: "..i) end
      return self[sz+i+1]
    else
      error("Bad list index: "..i)
    end
  end
end

-- The newindex event handles list[i] = val, if i is not present in 
-- the list - used for negative indices!
function List:__newindex(i,val)
  if type(i)=="number" then
    if i<0 then
      local sz = #self
      if i<-sz then error("List index out of range: "..i) end
      self[sz+i+1] = val
    else
      error("Bad list index: "..i)
    end
  end
end

local function simple_table(t)
  return type(t) == 'table' and getmetatable(t) ~= List
end

-- Create a new list. Can optionally pass a list eg. x=List:new{1,2,3}
-- passing another instance of List will cause a _copy_ to be created
-- we pass anything which isn't a simple table to iter() to work out
-- an appropriate iterator
function List:new(t)
  if not t then t={} 
  elseif  not simple_table(t) then
    local tbl = t
    t = {}
    for i,v in iter(tbl) do
      tinsert(t,v)
    end
  end
  setmetatable(t,List)
  return t
end

--Add an item to the end of the list; equivalent to a[len(a):] = [x].
function List:append(i)
  tinsert(self,i)
end

-- Extend the list by appending all the items in the given list;
-- equivalent to a[len(a):] = L.
function List:extend(L)
  assert(type(L)=="table","List:extend expecting a table")
  for i,v in ipairs(L) do tinsert(self,v) end
end

-- Insert an item at a given position. The first argument is the index of the
-- element before which to insert, so a.insert(0, x) inserts at the front of
-- the list, and a.insert(len(a), x) is equivalent to a.append(x).
function List:insert(i, x)
  tinsert(self,i,x)
end

-- equivalent of Python's del s[i]
List.delete = tremove

-- Remove the first item from the list whose value is x.
-- It is an error if there is no such item.
function List:remove(x)
  for i=1,#self do
    if self[i]==x then tremove(self,i) return end
  end
  error("List:remove failed, item not found")
end

-- Remove the item at the given position in the list, and return it.
-- If no index is specified, a.pop() returns the last item in the list.
-- The item is also removed from the list.
function List:pop(i)
  if not i then i = #self end
  return tremove(self,i)
end

-- Return the index in the list of the first item whose value is x.
-- It is an error if there is no such item.
function List:index(x)
  for i=1,#self do
    if self[i]==x then return i end
  end
  error("List:index failed, item not found")
end

-- Return the number of times x appears in the list.
function List:count(x)
  local cnt=0
  for i=1,#self do
    if self[i]==x then cnt=cnt+1 end
  end
  return cnt
end

-- Sort the items of the list, in place.
function List:sort()
  tsort(self)
end

-- Reverse the elements of the list, in place.
function List:reverse()
  local t,n={},#self
  for i=1,n do t[i]=self[n-i+1] end -- reverse
  for i=1,n do self[i]=t[i] end -- copy back
end

local function normalize_slice(self,first,last)
  local sz = #self
  if not first then first=1 end
  if first<0 then first=sz+first+1 end
  -- make the range _inclusive_!
  if not last then last=sz end 
  if last < 0 then last=sz+last end
  return first,last
end

-- Emulate the list slice eg. python_list[first:last]
-- If first or last are negative then they are relative to the end of the list
-- eg. slice(-2) gives last 2 entries in a list,
-- eg. slice(-4,-2) gives from -4th to -2nd
function List:slice(first,last)
  first,last = normalize_slice(self,first,last)
  local t=self:new()
  for i=first,last do tinsert(t,self[i]) end
  return t
end

-- empty the list
function List:clear()
  for i=1,#self do tremove(self,i) end
end

-- Emulate Python's range(x) function which gives you a sequence of 0..x-1
-- Include it in List table for tidyness
function List:range(x)
  local t=self
  if type(t) == 'number' then -- we did not get a self argument - it was a number!
    x = t
    t=List:new()    
  end
  for i=0,x-1 do tinsert(t,i) end
  return t
end

-- Python len(list) is the same as #list
function List:len()
  return #self
end

-- Extended operations --

-- equivalent to del s[i1:i2]
function List:chop(i1,i2)
    i1,i2 = normalize_slice(self,i1,i2)
    for i = i1,i2 do
        tremove(self,i1)
    end
end

-- equivalent to s[idx:idx] = seq
function List:splice(idx,seq)
    idx = idx - 1
    for i,v in ipairs(seq) do
        tinsert(self,i+idx,v)
    end
end

-- general slice assignment s[i1:i2] = seq
function List:slice_assign(i1,i2,seq)
    i1,i2 = normalize_slice(self,i1,i2)
    if i2 >= i1 then self:chop(i1,i2) end
    self:splice(i1,seq)
end

-- concatenation operator ..
function List:__concat(L)
  local ls = self:slice(1,-1)  -- making a copy!
  ls:extend(L)
  return ls
end

-- equality operator ==
function List:__eq(L)
--~   print(#self,#L)
  if #self ~= #L then return false end
  for i = 1,#self do
--~     print(self[i],L[i])
    if self[i] ~= L[i] then return false end
  end
  return true
end

-- how our list should be rendered as a string
-- note: really can't handle list items which are not strings or numbers
function List:__tostring()
  return '{'..table.concat(self,',')..'}'
end

-- can use the call notation to extract slices!
function List:__call(first,last)
  return self:slice(first,last)
end

function List:foreach(fun)
  for i,v in ipairs(self) do
    fun(v)
  end
end

-- capturing the Python concept of 'sequence'. 
-- In particular, this makes strings and file objects to be iterable sequences.
function iter(seq)
    if type(seq) == 'string' then
        local idx = 0
        local n = #seq
        local sub = string.sub
        return function ()
            idx = idx + 1
            if idx > n then return nil
            else
                return idx,sub(seq,idx,idx)
            end
        end
    elseif type(seq) == 'table' then
        return ipairs(seq)
    elseif type(seq) == 'function' then
        return seq()
    elseif type(seq) == 'userdata' and io.type(seq) == 'file' then
        local lines = seq:lines()
        local k = 0
        return function ()
            local line = lines()
            if not line then
              return nil 
            else
                k = k + 1
                return k,line
            end
        end			
    end
end

-- test using: lua pylist.lua
if arg and arg[0]=="pylist.lua" then
  local pr = function(l)
    for i=1,#l do io.write(l[i],' ') end
    print()
  end
  local lst = List:new()
  lst:append(10)
  lst:extend{20,30,40,50}
  assert (lst == List{10,20,30,40,50})
  lst:insert(3,11)  
  lst:remove(40)
  assert (lst == List{10,20,11,30,50})
  local q=lst:pop()
  assert( lst:index(30)==4 )
  assert( lst:count(10)==1 )
  lst:sort()
  lst:reverse()
  assert (lst == List{30,20,11,10})
  assert (lst[-1] == 10)
  assert (lst[-3] == 20)
  lst = List {10,20,30,40,50}
  assert (lst:slice(2) == List{20,30,40,50})
  assert (lst:slice(-2) == List{40,50})
  assert (lst:slice(nil,3) == List{10,20,30})
  assert (lst:slice(2,4) == List{20,30,40})
  assert (lst:slice(-4,-2) == List{20,30})
  lst = List.range(10)
  seq = List{0,1,2,3,4,5,6,7,8,9}
  assert(lst == seq)
  assert (List('abcd') == List{'a','b','c','d'})
  ls = List{10,20,30,40}
  ls:slice_assign(2,3,{21,31})
  assert (ls == List{10,21,31,40})
end

Lua 4.0 的舊範例

以下程式碼允許您這樣做

lst = List:new()  -- create an empty List
lst2 = List:new{1,2,"blah",lst}  -- create a list with some items in

-- Our new methods
lst:append("hello")
lst:extend{"world", 35, 77, "?"}
lst:insert(3,"how?")
lst:remove(35)
q = lst:pop()
print( lst:index("world") )
print( lst:count("?") )
lst:sort()
lst:reverse()
print( lst[-2] )
a = lst:slice(3)
b = lst:slice(-4,-2)
lst:clear()
lst:range(77)

-- We can mix them with Luas syntax
tinsert(lst,"boo")
print(getn(lst))


-- Emulation of Python lists
-- Nick Trout
-- See http://www.python.org/doc/current/tut/tut.html, section 5.1
-- Note:The comments before some of the functions are from the Python docs
-- and contain Python code.
-- Written for Lua version 4.0

List = settag({},newtag())

-- Handle the tagmethod "index"
function List:_index(i)
  local typ = type(i)
  if typ=="string" then
    local fn = List[i]
    if not fn then error("Bad List function: "..i) end
    return fn
  elseif typ=="number" then
    if i<0 then
      local sz = getn(self)
      if i<-sz then error("List index out of range: "..i) end
      return self[sz+i+1]
    else
      error("Bad list index: "..i)
    end
  end
end

settagmethod(tag(List), "index", List._index)

-- Create a new list. Can optionally pass a list eg. x=List:new{1,2,3}
function List:new(t)
  if not t then t={} end
  settag(t,tag(List))
  return t
end

--Add an item to the end of the list; equivalent to a[len(a):] = [x].
function List:append(i)
  tinsert(self,i)
end

-- Extend the list by appending all the items in the given list;
-- equivalent to a[len(a):] = L.
function List:extend(L)
  assert(type(L)=="table","List:extend expecting a table")
  for i=1,getn(L) do tinsert(self,L[i]) end
end

-- Insert an item at a given position. The first argument is the index of the
-- element before which to insert, so a.insert(0, x) inserts at the front of
-- the list, and a.insert(len(a), x) is equivalent to a.append(x).
function List:insert(i, x)
  tinsert(self,i,x)
end

-- Remove the first item from the list whose value is x.
-- It is an error if there is no such item.
function List:remove(x)
  for i=1,getn(self) do
    if self[i]==x then tremove(self,i) return end
  end
  error("List:remove failed, item not found")
end

-- Remove the item at the given position in the list, and return it.
-- If no index is specified, a.pop() returns the last item in the list.
-- The item is also removed from the list.
function List:pop(i)
  local item=i
  if not item then item=getn(self) end
  return tremove(self)
end

-- Return the index in the list of the first item whose value is x.
-- It is an error if there is no such item.
function List:index(x)
  for i=1,getn(self) do
    if self[i]==x then return i end
  end
  error("List:index failed, item not found")
end

-- Return the number of times x appears in the list.
function List:count(x)
  local cnt=0
  for i=1,getn(self) do if self[i]==x then cnt=cnt+1 end end
  return cnt
end

-- Sort the items of the list, in place.
function List:sort()
  sort(self)
end

-- Reverse the elements of the list, in place.
function List:reverse()
  local t,n={},getn(self)
  for i=1,n do t[i]=self[n-i+1] end -- reverse
  for i=1,n do self[i]=t[i] end -- copy back
end

-- Emulate the list slice eg. python_list[first:last]
-- If first or last are negative then they are relative to the end of the list
-- eg. slice(-2) gives last 2 entries in a list,
-- eg. slice(-4,-2) gives from -4th to -2nd
function List:slice(first,last)
  local sz = getn(self)
  if not first then first=1 end
  if first<0 then first=sz+first+1 end
  if not last then last=sz else last=last-1 end
  if last<0 then last=sz+last+1 end
  local t=self:new()
  for i=first,last do tinsert(t,self[i]) end
  return t
end

-- empty the list
function List:clear()
  for i=1,getn(self) do tremove(self,i) end
end

-- Emulate Pythons range(x) function which gives you a sequence of 0..x-1
-- Include it in List table for tidyness
function List:range(x)
  local t=self
  if not t then t=List:new() end
  for i=0,x-1 do tinsert(t,i) end
  return t
end

-- Python len(list) is the same as getn
List.len = getn

-- test using: lua -f pylist.lua -test
if arg and arg[1]=="-test" then
  local pr = function(l) for i=1,getn(l) do write(l[i]) end print() end
  local lst = List:new()
  lst:append("hello ") pr(lst)
  lst:extend{"world ","are ","you ","diddling ","?"} pr(lst)
  lst:insert(3,"how ") pr(lst)
  lst:remove("diddling ") pr(lst)
  local q=lst:pop() pr(lst)
  assert( lst:index("are ")==4 )
  assert( lst:count("are ")==1 )
  lst:sort() pr(lst)
  lst:reverse() pr(lst)
  tinsert(lst,"boo") pr(lst)
  print( getn(lst), lst:len(), List.len(lst) )
  print( lst[1],lst[-1],lst[-3] )
  --print( lst[0] )  -- test errors
  --print( lst[100] )
  --print( lst[-100] )
  write("list: ") pr(lst)
  write("[2:] : ")  pr( lst:slice(2) )
  write("[-2:] : ")  pr( lst:slice(-2) )
  write("[:3] : ")  pr( lst:slice(nil,3) )
  write("[:-2] : ") pr( lst:slice(nil,-2) )
  write("[2:4] : ") pr( lst:slice(2,4) )
  write("[-4:-2] : ") pr( lst:slice(-4,-2) )
  lst:clear() pr(lst)
  pr( List:range(10) )
  lst:range(20) pr(lst)
end

--NickTrout

MetaLua 中使用理解生成清單

python 清單的另一個便利功能也出現在 Haskell[2] 中,即通過理解產生清單:這使得生成笛卡兒積和篩選清單變得簡單易讀。Metalua 對此功能進行了粗略的實作,並加入了一些其他慣用語,例如清單取樣

-{extension "clist"}
-- integers from 2 to 50, by steps of 2:
x = { i for i = 2, 50, 2 }

-- the same, obtained by filtering over all integers <= 50:
y = { i for i = 1, 50 if i%2==0 }

-- prime numbers, implemented in an inefficient way:
local sieve, n = { i for i=2, 100 }, 1
while n < #sieve do
   sieve = { 
      i for i in values(sieve[1 ... n]);
      i for i in values(sieve[n+1 ... #sieve]) if i%sieve[n] ~= 0 }
   n += 1
end
table.print(sieve)

-- FabienFleutot

可以在 LuaMacros 中找到此語法(不含篩選子句)的替代實作 -- SteveDonovan

Lua 風格的問題

有幾個函數類似於 List.index,會引發錯誤,而不是傳回 false。這是適當的 Python 風格,但 Lua 中會比較尷尬,原因是內建例外處理機制的笨拙。因此這些方法是否應該傳回布林值?我們可以依賴 nil 不會是有效值,這點不同於 Python。

-- SteveDonovan


另請參閱:ClassesAndMethodsSampleCodePythonDictionaries
RecentChanges · 偏好設定
編輯 · 歷史記錄
最後編輯時間 2007 年 11 月 27 日 下午 2:49 GMT (diff)