Notes - Lua programming (2nd Edition) Ch11~13

11 data structure

array

Use an integer to index the table

a = {}
-- The size is defined indirectly when initializing the array
for i = 1, 1000 do
  a[i] = 0
end
-- Generally, 1 is used as the starting index of the array
-- It's OK not to, but some Lua Library and length operators are invalidated
print(#a)

-- Support list initialization
squares = {1, 4, 9, 16, 25}

Matrix and multidimensional array

Use the table of table to explicitly create each row:

function createMatrix(m, n)
  local matrix = {}
  for i = 1, m do
    matrix[i] = {}
    for j = 1, n do
      matrix[i][j] = 0
    end
  end
  return matrix
end

m0 = createMatrix(4, 4)

for i = 1, #m0 do
  for j = 1, #m0[i] do
    if i == j then
      m0[i][j] = 1
    else
      m0[i][j] = 0
    end
  end
end

for i = 1, #m0 do
  for j = 1, #m0[i] do
    io.write(string.format("%3d", m0[i][j]))
  end
  print()
end

Represented by one-dimensional table:

function createMatrix(m, n)
  local matrix = {}
  for i = 1, m do
    for j = 1, n do
      matrix[(i - 1) * n + j] = 0
    end
  end
  return matrix
end

m = 4
n = 4
m0 = createMatrix(4, 4)

for i = 1, m do
  for j = 1, n do
    if i == j then
      m0[(i - 1) * n + j] = 1
    else
      m0[(i - 1) * n + j] = 0
    end
  end
end

for i = 1, m do
  for j = 1, n do
    io.write(string.format("%3d", m0[(i - 1) * n + j]))
  end
  print()
end

The table in Lua itself is sparse, so no additional skills are needed to deal with sparse arrays. During traversal, pairs is generally used to traverse non nil elements, but because the key s in table are unordered, using pairs does not guarantee sequential access

Linked list

-- Create list
list = nil
for i = 1, 5 do
  list = {next = list, value = i}
end
-- ergodic
p = list
while p do
  print(p, p.value)
  p = p.next
end

Queue and bidirectional queue

List = {}
function List.new()
  return {first = 0, last = -1}
end

function List.pushFirst(list, value)
  local first = list.first - 1
  list.first = first
  list[first] = value
end

function List.pushLast(list, value)
  local last = list.last + 1
  list.last = last
  list[last] = value
end

function List.popFirst(list)
  local first = list.first
  if first > list.last then
    error("list is empty")
  end
  local value = list[first]
  list[first] = nil
  list.first = first + 1
  return value
end

function List.popLast(list)
  local last = list.last
  if first > list.last then
    error("list is empty")
  end
  local value = list[last]
  list[last] = nil
  list.last = last - 1
  return value
end

function List.display(list)
  for i = list.first, list.last do
    print(i, list[i])
  end
end

list = List.new()
for i = 1, 3 do
  List.pushLast(list, i)
end
List.display(list)
for i = 1, 3 do
  print(List.popFirst(list))
end
List.display(list)

If you only need to use queues, just call pushLast and popFirst

Set and unordered group (bag)

Implement set in Lua:

reserved = {
  ["while"] = true,
  ["if"] = true,
  ["for"] = true,
  ["end"] = true,
}

function stringReserved(x)
  if reserved[x] then
    return true
  else
    return false
  end
end

print(stringReserved("end")) -->true
print(stringReserved("blabla")) -->false

If you want to implement multiset, set value as a counter. If the counter is 0, delete the element

String buffer

local buffers = {}
for line in io.lines() do
  if line == "" then
    break
  end
  buffers[#buffers + 1] = line .. "\n"
end
local s = table.concat(buffers) -- use concat
print(s)

concat also has a second optional parameter, the delimiter

local buffers = {}
for line in io.lines() do
  if line == "" then
    break
  end
  buffers[#buffers + 1] = line
end
buffers[#buffers + 1] = ""
local s = table.concat(buffers, "\n")
print(s)

The underlying implementation of concat is similar to a Hanoi tower shaped stack. See P102 for details

chart

It is implemented with a table composed of nodes. There are two fields: the name of the node and the node set adjacent to the node

12 data files and persistence

data file

If it is a file similar to CSV format, it can be changed to the following form:

-- C:\Users\Administrator\Desktop\LuaTests\data.txt
-- be careful Entry{}It means to use one table Call a function for an argument Entry
Entry{
  "Alice",
  "18",
  "1997"}

Entry{
  "Bob",
  "20",
  "2000"}

There are several simple statistics:

local count = 0
function Entry()
  count = count + 1
end
dofile("C:\\Users\\Administrator\\Desktop\\LuaTests\\data.txt")
print("numbers: " .. count)

You can also use the format of "self describing data":

-- C:\Users\Administrator\Desktop\LuaTests\data.txt
Entry{
  name = "Alice",
  score = "78",
  year = "1997"}

Entry{
  name = "Bob",
  score = "99",
  year = "2000"}

Collect everyone's name:

local authors = {}
function Entry(dataTable)
  authors[#authors + 1] = dataTable.name
end
dofile("C:\\Users\\Administrator\\Desktop\\LuaTests\\data.txt")
for key, val in pairs(authors) do
  print(val)
end

Serialization

Serialization: convert data into byte stream or character stream for storage or transmission.

-- Serialize numbers or strings
function serialize(o)
  if type(o) == "number" then
    io.write(o)
  elseif type(o) == "string" then
    io.write(string.format("%q", o))
  end
end
    
io.output("C:\\Users\\Administrator\\Desktop\\LuaTests\\output.txt")
serialize(18)
serialize("\\d[]''")

Serialization result:

-- C:\Users\Administrator\Desktop\LuaTests\output.txt
18"\\d[]''"

P108: serialization of Long String Tags

Save acyclic table

-- Serialization with indentation
fourBlank = "    "

function printBlank(serializeLevel)
  for i = 1, serializeLevel do
    io.write(fourBlank)
  end
end

function serialize(o, serializeLevel)
  if type(o) == "number" then
    io.write(o)
  elseif type(o) == "string" then
    io.write(string.format("%q", o))
  elseif type(o) == "table" then
    io.write("{\n")
    for key, val in pairs(o) do
      printBlank(serializeLevel)
      io.write(key, " = ")
      serialize(val, serializeLevel + 1)
      io.write(",\n")
    end
    printBlank(serializeLevel - 1)
    io.write("}")
  else
    error("cannot serialize a " .. type(o))
  end
end

buffer = {}
function Entry(dataTable)
  buffer[#buffer + 1] = dataTable
end

dofile("C:\\Users\\Administrator\\Desktop\\LuaTests\\data.txt")
io.output("C:\\Users\\Administrator\\Desktop\\LuaTests\\output.txt")
serialize(buffer, 1)

Input:

-- "C:\\Users\\Administrator\\Desktop\\LuaTests\\data.txt"
Entry{
  name = "Alice",
  score = "78",
  year = "1997"}

Entry{
  name = "Bob",
  score = "99",
  year = "2000",
  book = {"abc", "def"}}

Output:

-- "C:\\Users\\Administrator\\Desktop\\LuaTests\\output.txt"
{
    1 = {
        year = "1997",
        name = "Alice",
        score = "78",
    },
    2 = {
        book = {
            1 = "abc",
            2 = "def",
        },
        year = "2000",
        name = "Bob",
        score = "99",
    },
}

Considering that the key of table may have an illegal Lua identifier, the

io.write(" ", k, " = ")

Change to

io.write("["); serialize(k); io.write("] = ")

The robustness of the function can be enhanced, and the output becomes:

-- "C:\\Users\\Administrator\\Desktop\\LuaTests\\output.txt"
{
    [1] = {
        ["year"] = "1997",
        ["name"] = "Alice",
        ["score"] = "78",
    },
    [2] = {
        ["book"] = {
            [1] = "abc",
            [2] = "def",
        },
        ["year"] = "2000",
        ["name"] = "Bob",
        ["score"] = "99",
    },
}

table with ring

Additional tables are required to record the tables that appear.

If table has ever appeared, use the previous name; If not, serialize and save the new name.

See P111 for details

13 metatable and metamethod

You can modify the behavior of a value through a meta table

t = {}
t1 = {}
print(getmetatable(t)) -->nil
-- setmetatable Used to modify any table Meta table of
-- Lua Can only be set in table Meta table of
-- Meta table of other types of values must pass C Code complete
setmetatable(t, t1)
assert(getmetatable(t) == t1)
print(getmetatable(t)) -->table: 0x006b1b60

-- All strings have meta tables set by the standard string library, and other types have no meta tables by default
print(getmetatable("y")) -->table: 0x006b53e8
print(getmetatable(10)) -->nil

Meta method of arithmetic class

Write a simple Set class:

Set = {}

-- Constructor
function Set.new(list)
  local set = {}
  for _, v in ipairs(list) do
    set[v] = true
  end
  return set
end

-- Union
function Set.union(a, b)
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = true
  end
  for k in pairs(b) do
    res[k] = true
  end
  return res
end

-- intersection
function Set.intersection(a, b)
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = b[k]
  end
  return res
end

-- Set To string
function Set.toString(set)
  local list = {}
  for e in pairs(set) do
    list[#list + 1] = e
  end
  return "{" .. table.concat(list, ", ") .. "}"
end

-- Print
function Set.print(s)
  print(Set.toString(s))
end

a = Set.new({1, 2, 3})
b = Set.new({2, 3, 4})
Set.print(Set.intersection(a, b)) -->{2, 3}

Add a meta table to it:

-- Set
Set = {}
local mtSet = {}

function Set.new(list)
  local set = {}
  setmetatable(set, mtSet) --> Each new Set All have the same meta table
  for _, v in ipairs(list) do
    set[v] = true
  end
  return set
end

function Set.union(a, b)
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = true
  end
  for k in pairs(b) do
    res[k] = true
  end
  return res
end

function Set.intersection(a, b)
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = b[k]
  end
  return res
end

function Set.toString(set)
  local list = {}
  for e in pairs(set) do
    list[#list + 1] = e
  end
  return "{" .. table.concat(list, ", ") .. "}"
end

function Set.print(s)
  print(Set.toString(s))
end

-- Add two meta methods to the meta table
mtSet.__add = Set.union
mtSet.__mul = Set.intersection
-- end of Set

a = Set.new({1, 2, 3})
b = Set.new({2, 3, 4})
Set.print(a * b) -->{2, 3}

The meta methods of the arithmetic class in the meta table also include:

  • __add
  • __mul
  • __sub
  • __div
  • __ unm: opposite number
  • __mod
  • __pow
  • __ concat: join operator

Lua's steps to find the meta table (taking the multiplication operator as an example): first check whether the first value has a meta table and whether there is a meta table in the meta table__ mul field, if any, is used as the meta method, which is irrelevant to the second value; If not, check the second value; If neither value is found, an error is raised.

If you want to get clearer error information, you can check the type of operands before the actual operation of the meta method:

function Set.intersection(a, b)
  if getmetatable(a) ~= mtSet or getmetatable(b) ~= mtSet then
    -- The second parameter 2 here indicates the number of layers in which the error occurs
    -- Measured in ZeroBrane Studio The number of lines that will affect the display of errors
    error("attempt to 'mul' a set with a non-set value", 2)
  end
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = b[k]
  end
  return res
end

Meta methods of relational classes

Meta methods of relation classes include:

  • __ eq: equal to
  • __ lt: less than
  • __ le: less than or equal to

Not equal to, greater than, and greater than or equal to can be obtained by the above meta method

Note that meta methods of relational classes cannot be applied to mixed types, nor can you attempt to compare two objects with different meta methods

Equal comparison will never cause an error. If the types are different or the meta methods are different, it will directly return false

-- Set
Set = {}
local mtSet = {}

function Set.new(list)
  local set = {}
  setmetatable(set, mtSet)
  for _, v in ipairs(list) do
    set[v] = true
  end
  return set
end

function Set.union(a, b)
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = true
  end
  for k in pairs(b) do
    res[k] = true
  end
  return res
end

function Set.intersection(a, b)
  if getmetatable(a) ~= mtSet or getmetatable(b) ~= mtSet then
    error("attempt to 'mul' a set with a non-set value", 2)
  end
  local res = Set.new{}
  for k in pairs(a) do
    res[k] = b[k]
  end
  return res
end

function Set.toString(set)
  local list = {}
  for e in pairs(set) do
    list[#list + 1] = e
  end
  return "{" .. table.concat(list, ", ") .. "}"
end

function Set.print(s)
  print(Set.toString(s))
end

mtSet.__add = Set.union
mtSet.__mul = Set.intersection
mtSet.__le = function(a, b)
  for k in pairs(a) do
    if not b[k] then
      return false
    end
  end
  return true
end
mtSet.__lt = function(a, b)
  return a <= b and not (b <= a)
end
mtSet.__eq = function(a, b)
  return a <= b and b <= a
end
-- end of Set

a = Set.new({1, 2, 3})
b = Set.new({1, 2, 3, 4})
print(a < b) -->true
print(a > b) -->false
print(a <= b) -->true
print(a >= b) -->false
print(a == b) -->false
print(a ~= b) -->true

Meta method defined by Library

The function print always calls tostring to format its output, and tostring checks whether the value has a__ Meta method of tostring

mtSet.__tostring = Set.toString
b = Set.new({1, 2, 3, 4})
print(b) -->{1, 2, 3, 4}

Set field__ metatable can protect the meta table from being modified

mtSet.__metatable = "not your business"
b = Set.new({1, 2, 3, 4})
myMt = {}
setmetatable(b, myMt)
-->D:\Program Files\ZeroBraneStudio\bin\lua.exe: C:\Users\Administrator\Desktop\LuaTests\test0.lua:71: cannot change a protected metatable

Meta method of table access

__index

If you access a field that does not exist in the table, the interpreter looks for a field named__ index meta method. If there is no meta method, nil is returned. Otherwise, the result returned by the meta method is returned:

Window = {}
Window.mt = {}

Window.prototype = {
  x = 0,
  y = 0,
  width = 800,
  height = 600
}

function Window.new(o)
  setmetatable(o, Window.mt)
  return o
end

Window.mt.__index = function(table, key)
  return Window.prototype[key]
end

window = Window.new{x = 10, y = 20}
print(window.width) -->800
window1 = Window.new{x = 10, y = 20, width = 256}
print(window1.width) -->256

Will__ It is a common practice to use index for inheritance. Therefore, Lua supports the following wording:

Window.mt.__index = Window.prototype

Use table as__ The index metamethod can quickly implement single inheritance, but the function will be more flexible

window = Window.new{x = 10, y = 20}
-- use rawget yes table Original access, that is, simple access without considering the meta table
print(rawget(window, "x"))
print(rawget(window, "width"))

__newindex

It is used for updating table. It is used when assigning values to indexes that do not exist

-- A wrong example
Window.mt.__newindex = function(table, key)
  table[key] = 1 -- Infinite recursion occurs here__newindex Meta method causes stack explosion
end

Correct setting__ The newindex meta method needs to cooperate with rawset(table, key, val):

Window.mt.__newindex = function(table, key)
  rawset(table, key, 1)
end
-- Whether given to non-existent index What is the setting value
-- The results are all assigned to 1
window.notExistIndex = 999
print(window.notExistIndex)

Combined use__ index and__ The newindex meta method can realize the powerful functions in Lua: read-only table, table with default value and inheritance

table with default value

Window = {}
Window.mt = {}

function Window.new(o)
  return o
end

local key = {} -- Prevent name conflicts with a new one table As the only key
local mt = {
  __index = function(t)
    return t[key]
  end
}
function setDefault(t, d)
  t[key] = d
  setmetatable(t, mt)
end

window = Window.new{x = 10, y = 10}
setDefault(window, 0)
print(window.newIndex) -->0

Track access to table

Window = {}
Window.mt = {}

function Window.new(o)
  return o
end

window = Window.new{x = 10, y = 10}

local _window = window
window = {}

local mt = {
  __index = function(t, k)
    print("access to element " .. tostring(k))
    return _window[k]
  end,
  
  __newindex = function(t, k, v)
    print("update of element " .. tostring(k) ..
      " to " .. tostring(v))
    _window[k] = v
  end
}
setmetatable(window, mt)

print(window.x)
window.x = 20
window.newIndex = 100

Output:

access to element x
10
update of element x to 20
update of element newIndex to 100

The problem with the above method is that the original table cannot be traversed

Monitor multiple table s at the same time:

Window = {}
Window.mt = {}

function Window.new(o)
  return o
end

window = Window.new{x = 10, y = 10}
window1 = Window.new{x = 15, y = 10}
window2 = Window.new{x = 15, y = 15}

local index = {}
local mt = {
  __index = function(t, k)
    print("access to element " .. tostring(k))
    return t[index][k]
  end,
  
  __newindex = function(t, k, v)
    print("update of element " .. tostring(k) ..
      " to " .. tostring(v))
    t[index][k] = v
  end
}

function track(t)
  local proxy = {}
  proxy[index] = t
  setmetatable(proxy, mt)
  return proxy
end

window = track(window)
window1 = track(window1)
window2 = track(window2)
window.x = 30
window1.aaa = 300
window2.bbb = 40

Read only table

Window = {}
Window.mt = {}

function Window.new(o)
  return o
end

window = Window.new{x = 10, y = 10}
window1 = Window.new{x = 15, y = 10}
window2 = Window.new{x = 15, y = 15}

function readOnly(t)
  local proxy = {}
  local mt = {
    -- The query access of the agent directly uses the original query table
    __index = t,
    -- An error is reported for all update operations
    __newindex = function(t, k, v)
      error("attempt to update a read-only table", 2)
    end
  }
  setmetatable(proxy, mt)
  return proxy
end

window = readOnly(window)
window.x = 90

Keywords: lua

Added by random1 on Mon, 10 Jan 2022 20:22:58 +0200