我正在通过阅读Ierusalimschy的在Lua中编程(第4版)来自学Lua,并进行练习。练习6.5是
编写一个函数,该函数接受一个数组并打印该数组中元素的所有组合。
在此简洁的陈述之后,这本书给出了一个提示,该提示使我们清楚地表明,期望做的是编写一个打印所有 C ( n 的函数, m )从 n 个元素数组中组合的 m 个元素。
我实现了如下所示的combinations
函数:
function combinations (array, m)
local append = function (array, item)
local copy = {table.unpack(array)}
copy[#copy + 1] = item
return copy
end
local _combinations
_combinations = function (array, m, prefix)
local n = #array
if n < m then
return
elseif m == 0 then
print(table.unpack(prefix))
return
else
local deleted = {table.unpack(array, 2, #array)}
_combinations(deleted, m - 1, append(prefix, array[1]))
_combinations(deleted, m, prefix)
end
end
_combinations(array, m, {})
end
它可以正常工作,但不是尾递归。
有人可以给我看一个尾递归函数,其功能与上面的combinations
一样吗?
(出于价值,我使用的是Lua 5.3。)
注意::我认识到该练习不需要该函数具有尾递归功能。出于好奇,这是我添加的一项要求。
编辑:我稍微简化了函数,但删除了几个嵌套函数,它们并没有增加太多。
答案 0 :(得分:1)
还有第三种选择,没有蛇吃它的尾巴。尽管使用尾调用进行递归不会导致堆栈溢出,但我还是避免出于个人喜好而这样做。我使用while循环和一个堆栈,该堆栈保存每次迭代的信息。在循环中,您从堆栈中弹出下一个任务,执行工作,然后将下一个任务推入堆栈。我觉得它看起来更干净,并且可以更直观地看到嵌套。
这是我将您的代码转换为编写代码的方式:
function combinations(sequence, item)
local function append(array, item)
local copy = {table.unpack(array)}
copy[#copy + 1] = item
return copy
end
local stack = {}
local node = { sequence, item, {} }
while true do
local seq = node[ 1 ]
local itm = node[ 2 ]
local pre = node[ 3 ]
local n = #seq
if itm == 0 then
print(table.unpack(pre))
elseif n < itm then
-- do nothing
else
local reserve = {table.unpack(seq, 2, #seq)}
table.insert(stack, { reserve, itm, pre })
table.insert(stack, { reserve, itm-1, append(pre, seq[ 1 ]) })
end
if #stack > 0 then
node = stack[ #stack ] -- LIFO
stack[ #stack ] = nil
else
break
end
end
end
您几乎可以将这种while循环堆栈/节点技术用于任何递归方法。这是一个示例,用于打印深层嵌套的表:https://stackoverflow.com/a/42062321/5113346
以您的输入示例为例,我的版本提供了相同的输出:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
。
请原谅我,如果它不能与其他传递的参数一起使用,因为我没有尝试解决练习的答案,而只是在原始帖子中重写了代码。
答案 1 :(得分:0)
好的,我想我找到了一种方法:
function combinations (array, m)
local dropfirst = function (array)
return {table.unpack(array, 2, #array)}
end
local append = function (array, item)
local copy = {table.unpack(array)}
copy[#copy + 1] = item
return copy
end
local _combinations
_combinations = function (sequence, m, prefix, queue)
local n = #sequence
local newqueue
if n >= m then
if m == 0 then
print(table.unpack(prefix))
else
local deleted = dropfirst(sequence)
if n > m then
newqueue = append(queue, {deleted, m, prefix})
else
newqueue = queue
end
return _combinations(deleted, m - 1,
append(prefix, sequence[1]),
newqueue)
end
end
if #queue > 0 then
newqueue = dropfirst(queue)
local newargs = append(queue[1], newqueue)
return _combinations(table.unpack(newargs))
end
end
_combinations(sequence, m, {}, {})
end
我认为此版本是尾递归的。不幸的是,它不能像我的原始非尾递归版本那样以很好的顺序打印结果(更不用说代码的复杂性了),但是却不能拥有全部!
编辑:不,一个可以拥有一切!下面的版本是尾递归的,和以与原始非尾递归版本相同的顺序打印结果:
function combinations (sequence, m, prefix, stack)
prefix, stack = prefix or {}, stack or {}
local n = #sequence
if n < m then return end
local newargs, newstack
if m == 0 then
print(table.unpack(prefix))
if #stack == 0 then return end
newstack = droplast(stack)
newargs = append(stack[#stack], newstack)
else
local deleted = dropfirst(sequence)
if n > m then
newstack = append(stack, {deleted, m, prefix})
else
newstack = stack
end
local newprefix = append(prefix, sequence[1])
newargs = {deleted, m - 1, newprefix, newstack}
end
return combinations(table.unpack(newargs)) -- tail call
end
它使用以下辅助功能:
function append (sequence, item)
local copy = {table.unpack(sequence)}
copy[#copy + 1] = item
return copy
end
function dropfirst (sequence)
return {table.unpack(sequence, 2, #sequence)}
end
function droplast (sequence)
return {table.unpack(sequence, 1, #sequence - 1)}
end
示例:
> combinations({1, 2, 3, 4, 5}, 3)
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
具有讽刺意味的是,该版本通过实现其自己的堆栈实现了尾递归,因此我不确定它最终是否会比非尾递归版本更好。然后,我猜该函数的stack
实际上是驻留在堆中(对吗?),因为Lua的表是按引用传递的(对吗?),所以毕竟这也许是一种改进。 (如果我错了,请纠正我!)