Skip to content

Latest commit

 

History

History
1165 lines (940 loc) · 20.5 KB

pil_solutions.md

File metadata and controls

1165 lines (940 loc) · 20.5 KB

author: niplav, created: 2022-01-08, modified: 2022-01-08, language: english, status: in progress, importance: 2, confidence: likely

Solutions to the book “Programming in Lua” by Roberto Ierusalimschy.

Solutions to “Programming in Lua”

Chapter 1

Exercise 1.1

function fact(n)
	elseif n==0 then
		return 1
	else
		return n * fact(n-1)
	end
end

print("enter a number:")
a=io.read("*n")
print(fact(a))

Exercise 1.2

For a script that loads a library the dofile option is probably preferable, since then there is no need to deal with the different ways different platforms handle flags, when using lua interactively, the '-l' option is nicer, since one needs to type less.

Exercise 1.3

Actually, from the top of my head, I really can't. But apparently, Haskell does it, which is weird, since Lua and Haskell have pretty different lineages afaik.

Exercise 1.4

Valid identifiers from the list: ___, _end, End, NULL

Invalid identifiers: end, until?, nil

Exercise 1.5

#!/usr/bin/lua
print(arg[0])

Then, update the permissions (on a unix-based system):

$ chmod 0755 ex_5.lua

Chapter 2

Exercise 2.1

The result of the expression type(nil)==nil is false, since the type of type(nil) is string, and the string "nil" is not equal to the value nil.

Exercise 2.2

Invalid numerals: .e12, 0.0e, 0xABFG, FFFF, 0x

Valid numerals (with value): .0e12 -> 0.0, 0x12 -> 18, 0xA -> 10, 0xFFFFFFFF -> 4294967295, 0x1P10 -> 1024, 0.1e1 -> 1.0, 0x0.1p1 -> 0.125

Exercise 2.3

12.7 can't be represented by

(n*12.7)/2^m

but 5.5 can be represented by

11/2^1

Exercise 2.4

a="<![CDATA[\n  Hello world\n]]>"
b=[=[
<![CDATA[
  Hello world
]]>]=]

Exercise 2.5

One good way to format the sequence of bytes would be to display it in a string with escaped bytes in hexadecimal format, split into several lines with "\z". By doing this problems with line length can be avoided and performance is probably not very bad, considering that computers handle byte sequences considerably well. Readability can, but should not be a problem, since when dealing with arbitrary byte sequences the value in hexadecimal is used more frequently than the ASCII value.

Exercise 2.6

a={}
a.a=a

a.a.a.a evaluates to the table a the first a is a pointer to the table a, the other as are all keys the expression can be rewritten as a["a"]["a"]["a"]

a.a.a.a=3

Now the value for the key "a" is 3 in the table a, so a.a is 3, and a.a.a is not indexable, since 3 has no field called "a". So Lua prints an error message.

Chapter 3

Exercise 3.1

-10 2
-9 0
-8 1
-7 2
-6 0
-5 1
-4 2
-3 0
-2 2
-1 1
0 0
1 1
2 2
3 0
4 1
5 2
6 0
7 1
8 2
9 0
10 1

Exercise 3.2

2^3^4=2^(3^4)=1.4178e+24
2^-3^4=(2^(-(3^4))=4.1359e-25

Exercise 3.3

function polyeval(a, x)
	local res=0
	for i=1, #a do
		res=res+a[i]*x^(i-1)
	end
	return res
end

Exercise 3.4

function polyeval(a, x)
	local res=0
	for i=1, #a do
		res=res*x
		res=res+a[i]
	end
	return res
end

Exercise 3.5

function isbool(v)
	return v==true or v==false
end

Exercise 3.6

The parentheses are not necessary, since not binds stronger than and, and and binds stronger than or. However, the parentheses are probably still a good idea, since they make the expression easier to read and understand.

Exercise 3.7

monday	sunday	sunday

Exercise 3.8

One way to initialize the table would be to use the square bracket notation. Associating the escape sequence "\a" with the literal bell character would be done like this:

{["\\a"]="\a"}

This would be done for every escape sequence.

Chapter 4

Exercise 4.1

In Lua there is no switch/case statement, so elseif is a necessary (albeit not sufficient) replacement.

Exercise 4.2

Preferred method is the first one, since it doesn't deal with limits and makes an infinite loop clear from the beginning.

while true do end

for i=0, math.huge do end

for i=2, 1, 0 do end

repeat until false

Exercise 4.3

repeat-until is a construct similar to do-while in C, a construct which is also used rarely there. Apparently, in C only 5% of loops in C code are do-while loops. However, repeat-until can be useful to avoid initializing a variable the same way it is incremented in the loop, saving one line. It makes elegant solutions easier, but not much beyond that, so it could be left out easily.

Exercise 4.4

local z = 0
local c

repeat
	c = io.read()
	if c == '0' then z=z+1 end
	print(z)
until c == nil

if z % 2 == 0 then
	print('ok')
else
	print('not ok')
end

Exercise 4.5

Jumping out of a function would jump into the scope of a local variable, which is not possible (per definition) in Lua.

This would also cause problems in a function calling itself and jumping back into itself (or two different functions calling each other recursively), since there would be no good way to determine whether the goto would simply jump inside the function or outside into a previous call of itself.

Exercise 4.6

The program would first print:

10
9
8
7
6
5
4
3
2
1

and then return an anonymous function that jumps to the end of getlabel() and return 0.

Chapter 5

Exercise 5.1

function concat(...)
	local args={...}
	local r=""
	for i, v in ipairs(args) do
		r=r..v
	end
	return r
end

Exercise 5.2

function printarr(arr)
	print(table.unpack(arr))
end

Exercise 5.3

function allbutone(...)
	local r={}
	local args=table.pack(...)
	for i=2, args.n do
		r[i-1]=args[i]
	end
	return table.unpack(r)
end

Exercise 5.4

function combi(t, n)
	local arg={}
	local res={}
	local save={}

	if n<=0 then
		return {{}}
	end

	for k, v in ipairs(t) do save[k]=v end

	while #save>0 do
		local last=save[#save]
		save[#save]=nil

		for k, v in ipairs(save) do table.insert(arg, v) end

		local tmp=combi(arg, n-1)
		arg={}

		for i=1, #tmp do table.insert(tmp[i], last) end
		for k, v in ipairs(tmp) do table.insert(res, v) end
	end
	return res
end

function comb(t)
	local res={}
	for i=1, #t do
		tmp=combi(t, i)
		for k, v in ipairs(tmp) do table.insert(res, tmp[k]) end
	end
	return res
end

Chapter 6

Exercise 6.1

function integral(f)
	return function(x, y)
		local i, res, eps
		res=0; eps=0.001
		for i=x, y, eps do
			res=res+f(i)*eps
		end
		return res
	end
end

Exercise 6.2

function newpoly(t)
	return function(x)
		local res=0
		for i=1, #t do
			res=res+t[i]*x^(#t-i)
		end
		return res
	end
end

Exercise 6.3

n=100000

function f()
	print(n)
	n=n-1
	if n<0 then
		return nil
	else
		return "i=1;"
	end
end

load(f)()

Exercise 6.4

function room1()
	local move=io.read()
	if move=="south" then room3()
	elseif move=="east" then room2()
	else
		print("invalid move")
		room1()
	end
end

function room2()
	local move=io.read()
	if move=="south" then room4()
	elseif move=="west" then room1()
	else
		print("invalid move")
		room2()
	end
end

function room3()
	local move=io.read()
	if move=="north" then room1()
	elseif move=="east" then room4()
	else
		print("invalid move")
		room3()
	end
end

function room4()
	print("Congrats, asshat. You won.")
end

room1()

Chapter 7

Exercise 7.1

function fromto(n, m)
	return fromit, {m}, n-1
end

function fromit(t, v)
	if v<t[1] then
		return v+1
	else
		return nil
	end
end

Exercise 7.2

function fromto(n, m, s)
	return fromit, {m, s}, n-s
end

function fromit(t, v)
	if v+t[2]<=t[1] then
		return v+t[2]
	else
		return nil
	end
end

Exercise 7.3

function uniquewords()
	local line=io.read()
	local wordtable={}
	local pos=1
	wordtable[""]=1
	return function()
		local str=""
		local s, e
		repeat
			s, e=string.find(line, "%w+", pos)
			if s then
				pos=e+1
				str=str.sub(line, s, e)
			else
				line=io.read()
				pos=1
			end
		until not line or wordtable[str]~=1
		wordtable[str]=1
		return (line and str) or nil
	end
end

Exercise 7.4

function substrings(s)
	local len, pos
	len=0
	pos=1
	return function()
		if pos+len>#s then
			pos=pos+1
			len=0
		end
		if pos>#s then
			return nil
		else
			local str=string.sub(s, pos, pos+len)
			len=len+1
			return str
		end
	end
end

Chapter 8

Exercise 8.1

s is definitely a string, c can be a string or a function

function loadwithprefix(s, c)
	return load(function ()
		local t
		if s then
			t=s
			s=nil
		elseif type(c)=="string" then
			t=c
			c=nil
		elseif type(c)=="function" then
			t=c()
		end
		return t
	end)
end

Exercise 8.2

function multiload(...)
	local t=table.pack(...)
	local i=1
	return load(function ()
		local res
		if i>#t then
			return nil
		end
		if type(t[i])=="string" then
			i=i+1
			return t[i-1]
		end
		while not res and i<=#t do
			res=t[i]()
			i=i+1
		end
		return res
	end)
end

Exercise 8.3

The two functions have nearly the same speed.

function stringrep_n(n)
	local res=[[	s=...
	local r=""
]]
	if n>0 then
		while n>1 do
			if n%2~=0 then res=res .. "\tr=r .. s\n" end
			res=res .. "\ts=s .. s\n"
			n=math.floor(n/2)
		end
		res=res .. "\tr=r .. s\n"
	end
	res=res .. "\treturn r\n"
	return load(res)
end

Exercise 8.4

Shamelessly stolen (years later, I don't know where from, though :-D):

function f()
	count = 0
	function g()
		count = count + 1
		if count == 2 then debug.sethook() end
		error()
	end
	debug.sethook(g, "r")
end

with

pcall(pcall, f)

returns false, nil.

Chapter 9

Exercise 9.1

function combgen(t, n, r, i)
	local idx=i or 1
	local res=r or {}

	if n<=0 then
		coroutine.yield(res)
	else
		for j=idx, #t-n do
			res[n]=t[j]
			combgen(t, n-1, res, j+1)
		end
	end
end

function combinations(t, n)
	return coroutine.wrap(function() combgen(t, n) end)
end

Exercise 9.2

socket=require("socket")

function download(host, file)
	local c=assert(socket.connect(host, 80))
	local count=0
	c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
	while true do
		local s, status=receive(c)
		count=count+#s
		if status=="closed" then break end
	end
	c:close()
	print(file, count)
end

function receive(connection)
	connection:settimeout(0)
	local s, status, partial=connection:receive(2^10)
	if status=="timeout" then
		coroutine.yield(connection)
	end
	return s or partial, status
end

function get(host, file)
	local co=coroutine.create(function()
	download(host, file)
	end)
	table.insert(threads, co)
end

function dispatch()
	local i=1
	local timedout={}
	while true do
		if threads[i]==nil then
			if threads[1]==nil then break end
			i=1
			timedout={}
		end
		local status, res=coroutine.resume(threads[i])
		if not res then
			table.remove(threads, i)
		else
			i=i+1
			timedout[#timedout+1]=res
			if #timedout==#thredas then
				socket.select(timedout)
			end
		end
	end
end

threads={}

Exercise 9.3

function transfer(cr)
	coroutine.yield(cr)
end

function dispatch(b)
	nr=coroutine.create(b)
	while nr~=nil do
		nr=coroutine.resume(nr)
	end
end

Chapter 10

Exercise 10.1

local N=8

local function isplaceok(a, n, c)
	for i=1, n-1 do
		if (a[i]==c) or
		   (a[i]-i==c-n) or
		   (a[i]+i==c+n) then
			return false
		end
	end
	return true
end

local function printsolution(a)
	for i=1, N do
		for j=1, N do
			io.write(a[i]==j and "X" or "-", " ")
		end
		io.write("\n")
	end
	io.write("\n")
	os.exit()
end

local function addqueen(a, n)
	if n>N then
		printsolution(a)
	else
		for c=1, N do
			if isplaceok(a, n, c) then
				a[n]=c
				addqueen(a, n+1)
			end
		end
	end
end

addqueen({}, 1)

Exercise 10.2

local N=8

function permgen(a, n)
        n=n or #a
        if n<=1 then
                coroutine.yield(a)
        else
                for i=1, n do
                        a[n], a[i]=a[i], a[n]
                        permgen(a, n-1)
                        a[n], a[i]=a[i], a[n]
                end
        end
end

function permutations(a)
        return coroutine.wrap(function() permgen(a) end)
end

local function isplaceok(a, n, c)
        for i=1, n-1 do
                if (a[i]==c) or
                   (a[i]-i==c-n) or
                   (a[i]+i==c+n) then
                        return false
                end
        end
        return true
end

local function printsolution(a)
        for i=1, N do
                for j=1, N do
                        io.write(a[i]==j and "X" or "-", " ")
                end
                io.write("\n")
        end
        io.write("\n")
end

for q in permutations({1, 2, 3, 4, 5, 6, 7, 8}) do
	local i=1
	while i<N+1 and isplaceok(q, i, q[i]) do i=i+1 end
	if i==N+1 then printsolution(q) end
end

Exercise 10.3

local function allwords()
	local auxwords=function()
		for line in io.lines() do
			for word in string.gmatch(line, "%w+") do
				coroutine.yield(word)
			end
		end
	end
	return coroutine.wrap(auxwords)
end

local counter={}
for w in allwords() do
	if #w>=4 then
		counter[w]=(counter[w] or 0) + 1
	end
end

local words={}
for w in pairs(counter) do
	words[#words+1]=w
end

table.sort(words, function(w1, w2)
	return counter[w1]>counter[w2] or
		counter[w1]==counter[w2] and w1<w2
end)

for i=1, (tonumber(arg[1]) or 10) do
	print(words[i], counter[words[i]])
end

Exercise 10.4

local N=3

function allwords()
	local line=io.read()
	local pos=1
	return function()
		while line do
			local s, e=string.find(line, "%w+", pos)
			if s then
				pos=e+1
				return string.sub(line, s, e)
			else
				line=io.read()
				pos=1
			end
		end
		return nil
	end
end

function prefix(p)
	local s=p[1]
	for i=2, #p do s=s .. " " .. p[i] end
	return s
end

function back(p)
	for i=1, N-1 do p[i]=p[i+1] end
end

local statetab={}

function insert(index, value)
	local list=statetab[index]
	if list==nil then
		statetab[index]={value}
	else
		list[#list+1]=value
	end
end

local MAXGEN=10000
local NOWORD="\n"

pf={}
for i=1, N do pf[i]=NOWORD end

for w in allwords() do
	insert(prefix(pf), w)
	back(pf)
	pf[N]=w
end

insert(prefix(pf), NOWORD)

for i=1, N do pf[i]=NOWORD end

for i=1, MAXGEN do
	local ls=statetab[prefix(pf)]
	local r=math.random(#ls)
	local nextword=ls[r]
	if nextword==NOWORD then return end
	io.write(nextword, " ")
	back(pf)
	pf[N]=nextword
end

Chapter 11

Exercise 11.1

It's stupid to set both values to 0. This just restores the first state.

queue={}

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

function queue.pushfirst(q, value)
	q[q.first]=value
	q.first=q.first+1
end

function queue.pushlast(q, value)
	q[q.last]=value
	q.last=q.last-1
end

function queue.popfirst(q)
	local first=q.first-1
	if q.last>first then error("queue is empty") end
	local value=q[first]
	q[first]=nil
	q.first=first
	if q.last>=q.first-1 then q.first=0; q.last=-1 end
	return value
end

function queue.poplast(q)
	local last=q.last+1
	if last>q.first then error("queue is empty") end
	local value=q[last]
	q[last]=nil
	q.last=last
	if q.last>=q.first-1 then q.first=0; q.last=-1 end
	return value
end

Exercise 11.2

Using a closure for the allwords argument

local function allwords(f)
	local auxwords=function()
		for line in io.lines(f) do
			for word in string.gmatch(line, "%w+") do
				coroutine.yield(word)
			end
		end
	end
	return coroutine.wrap(auxwords)
end

local ignore={}
for w in allwords(arg[2] or "ignore") do
	ignore[w]=true
end

local counter={}
for w in allwords() do
	if not ignore[w] then
		counter[w]=(counter[w] or 0) + 1
	end
end

local words={}
for w in pairs(counter) do
	words[#words+1]=w
end

table.sort(words, function(w1, w2)
	return counter[w1]>counter[w2] or
		counter[w1]==counter[w2] and w1<w2
end)

for i=1, (tonumber(arg[1]) or 10) do
	print(words[i], counter[words[i]])
end

Exercise 11.3

function name2node(graph, name)
	local node=graph[name]
	if not node then
		node={name=name, adj={}}
		graph[name]=node
	end
	return node
end

function readgraph(f)
	local graph={}
	for line in io.lines(f) do
		local namefrom, nameto, label=string.match(line, "(%S+)%s+(%S+)%s+(%d+)")
		local from=name2node(graph, namefrom)
		local to=name2node(graph, nameto)
		from.adj[to]=label
	end
	return graph
end

function findpath(curr, to, path, visited)
	path=path or {}
	visited=visited or {}
	if visited[curr] then
		return nil
	end
	visited[curr]=true
	path[#path+1]=curr
	if curr==to then
		return path
	end
	for node in pairs(curr.adj) do
		local p=findpath(node, to, path, visited)
		if p then return p end
	end
	path[#path]=nil
end

function printpath(path)
	for i=1, #path do
		print(path[i].name)
	end
end

Exercise 11.4

dofile("ex_3.lua")

function dijkstra(g, s, e)
	local unvisited={}
	local dist={}
	local prev={}
	local start=g[s]
	local lst=g[e]
	local current=start

	for i, j in pairs(g) do unvisited[j]=true end
	for i, j in pairs(g) do dist[j]=math.huge end

	dist[start]=0
	while current~=lst do
		for to, l in pairs(current.adj) do
			if unvisited[to]==true then
				local ndist=dist[current]+l
				if dist[to]>ndist then
					dist[to]=ndist
					prev[to]=current
				end
			end
		end

		unvisited[current]=false
		current=lst
		for i, j in pairs(unvisited) do
			if j==true and dist[i]<dist[current] then
				current=i
			end
		end
	end

	return prev
end
  • g: the graph
  • s: the name of the starting point
  • e: the name of the endpoint

Chapter 12

Exercise 12.1

function serialize(o, l)
	l=l or 1
	if type(o)=="number" then
		io.write(string.format("%a", o))
	elseif type(o)=="string" then
		io.write(string.format("%q", o))
	elseif type(o)=="table" then
		io.write("{\n")
		for k, v in pairs(o) do
			io.write(string.rep("\t", l))
			serialize(k, l+1)
			io.write("=")
			serialize(v, l+1)
			io.write(",\n")
		end
		io.write(string.rep("\t", l-1) .. "}")
		if l==1 then io.write("\n") end
	else
		error("cannot serialize a " .. type(o))
	end
end

Exercise 12.2

function serialize(o, l)
	l=l or 1
	if type(o)=="number" then
		io.write(string.format("%a", o))
	elseif type(o)=="string" then
		io.write(string.format("%q", o))
	elseif type(o)=="table" then
		io.write("{\n")
		for k, v in pairs(o) do
			io.write(string.rep("\t", l) .. "[")
			serialize(k, l+1)
			io.write("] = ")
			serialize(v, l+1)
			io.write(",\n")
		end
		io.write(string.rep("\t", l-1) .. "}")
		if l==1 then io.write("\n") end
	else
		error("cannot serialize a " .. type(o))
	end
end

Exercise 12.3

function serialize(o, l)
	l=l or 1
	if type(o)=="number" then
		io.write(string.format("%a", o))
	elseif type(o)=="string" then
		io.write(string.format("%q", o))
	elseif type(o)=="table" then
		io.write("{\n")
		for k, v in pairs(o) do
			io.write(string.rep("\t", l))
			if type(k)~="string" or not isidentifier(k) then io.write("[") end
			if type(k)=="string" and isidentifier(k) then io.write(k)
			else serialize(k, l+1) end
			if type(k)~="string" or not isidentifier(k) then io.write("]") end
			io.write(" = ")
			serialize(v, l+1)
			io.write(",\n")
		end
		io.write(string.rep("\t", l-1) .. "}")
		if l==1 then io.write("\n") end
	else
		error("cannot serialize a " .. type(o))
	end
end

function isidentifier(s)
	return string.match(s, "^[a-zA-Z_][a-zA-Z0-9_]*$")
end

Exercise 12.4

function serialize(o, l)
	l=l or 1
	if type(o)=="number" then
		io.write(string.format("%a", o))
	elseif type(o)=="string" then
		io.write(string.format("%q", o))
	elseif type(o)=="table" then
		local i=1
		io.write("{\n")
		if o[i]~=nil then
			io.write(string.rep("\t", l))
			while o[i]~=nil do serialize(o[i]); io.write(", "); i=i+1 end
			io.write("\n")
		end
		for k, v in pairs(o) do
			if type(k)~="number" or k<1 or k>i then
				io.write(string.rep("\t", l))
				if type(k)~="string" or not isidentifier(k) then io.write("[") end
				if type(k)=="string" and isidentifier(k) then io.write(k)
				else serialize(k, l+1) end
				if type(k)~="string" or not isidentifier(k) then io.write("]") end
				io.write(" = ")
				serialize(v, l+1)
				io.write(",\n")
			end
		end
		io.write(string.rep("\t", l-1) .. "}")
		if l==1 then io.write("\n") end
	else
		error("cannot serialize a " .. type(o))
	end
end

function isidentifier(s)
	return string.match(s, "^[a-zA-Z_][a-zA-Z0-9_]*$")
end