Python Lists

lua-users home
wiki

Python

[Python] is a popular language that contains lists and dictionaries. Lua's table is a multipurpose container that is somewhere in between but in itself lacks functionality as a list. The functions table.insert, table.remove and table.getn from the Lua standard library give tables list functionality (or more specifically termed arrays in Lua). The listing below emulates Python's list type. Section 5.1 [1] of the Python Tutorial documents Python's list functionality.

Implementation

We create a class table, List, containing methods. We use Lua's index metamethod to call the List methods (for background info see ClassesAndMethods).

This version has been rewritten for Lua 5.1 (although the original has been kept) with overloads added for the concatenation and equality operators. For slices, the start and finish are now an inclusive range, which is more Lua-ish. There is also a slice_assign method for the Python construct s[i1:i2] = seq.

-- SteveDonovan

Examples

The code below allows you do things like:

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)

This interactive session demonstrates some of the operations. Note that these list objects can be called as if they were functions, which is a shortcut for the slice method.


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

Older Example for Lua 4.0

The code below allows you do things like:

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

Lists by comprehension in MetaLua

Another handy feature of python lists, which can also be found in Haskell[2], is the specification of lists by comprehension: generating lists through cartesian products and filtering is made easier and more readable. Metalua comes with a sketchy implementation of this features, plus a couple of others idioms such as list sampling:

-{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

An alternative implementation of this syntax (without the filter clause) can be found in LuaMacros -- SteveDonovan

A question of Lua Style

There are several functions like List.index which throw an error, rather than returning false. This is appropriate Python style, but more awkward in Lua because the built-in exception handling mechanism is more clumsy. So should these methods return a boolean, rather? We can depend on nil not being a valid value, unlike in Python.

-- SteveDonovan


See also: ClassesAndMethods , SampleCode , PythonDictionaries
RecentChanges · preferences
edit · history
Last edited November 27, 2007 1:49 pm GMT (diff)