Python 列表

lua-users home
wiki

Python

[Python] 是一种流行的语言,包含列表和字典。Lua 的 table 是一种多用途容器,介于两者之间,但本身缺乏作为列表的功能。Lua 标准库中的函数 table.inserttable.removetable.getn 为 table 提供了列表功能(或更确切地说,在 Lua 中称为数组)。下面的列表模拟了 Python 的列表类型。Python 教程的第 5.1 节 [1] 文档记录了 Python 的列表功能。

实现

我们创建了一个名为 List 的 table 类,其中包含方法。我们使用 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 https://www.pythonlang.cn/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 https://www.pythonlang.cn/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 · preferences
edit · history
最后编辑于 2007 年 11 月 27 日下午 2:49 GMT (diff)