Recursion Groovy中递归闭包改为蹦床闭包

Recursion Groovy中递归闭包改为蹦床闭包,recursion,groovy,Recursion,Groovy,我需要从只包含字符0、1和V(通配符)的字符串构造所有可能的二进制表示形式。字符串可以是任意长度(超过1000个字符),但通配符的数量少于20个 例如,对于输入V1V,输出将是[010、011、110、111] 我当前的实现可以正常工作,但是堆栈中溢出了少量的通配符。代码如下所示 def permutts permutts = { if (!it.contains('V')) return [it] def target = it def res = [] ['0',

我需要从只包含字符
0
1
V
(通配符)的字符串构造所有可能的二进制表示形式。字符串可以是任意长度(超过1000个字符),但通配符的数量少于20个

例如,对于输入
V1V
,输出将是
[010、011、110、111]

我当前的实现可以正常工作,但是堆栈中溢出了少量的通配符。代码如下所示

def permutts
permutts =
{
  if (!it.contains('V'))
    return [it]

  def target = it
  def res = []

  ['0', '1'].each
  {
    def s = target.replaceFirst(~/V/, it)
    if (s.contains('V'))
    {
      res += permutts(s)
    }
    else
    {
      res << s
    }
  }
  res
}
println permutts('V1V')
输出为:

entering with VV, res=[]
entering with 0V, res=[]
entering with 00, res=[]
[00]
至少它在做些什么,但我不明白为什么它不能继续。有人能解释一下我做错了什么,或者提出一种不同的方法来解决这个问题吗?

Groovy的
trampoline()
提供了,所以它应该用于在最后执行的指令(tail)中调用自身的闭包/方法

因此,更好的解决方案是经典的“头/尾”处理(添加println以跟踪调用):

def permutts
permutts={s,res->
如果(s.length()==0){
println“s=$s,res=$res”
物件
}否则{
println“s=$s,res=$res”
如果(s[0]=='V'){//s[0]~list.head()
res=res.collect({it=it+'0'})+res.collect({it=it+'1'})
}否则{
res=res.collect({it=it+s[0]})
}
蹦床(s.substring(1),res)//s.substring(1)~list.tail()
}
}1.蹦床
示例:

permutts('VV', [''])     
  s = VV, res = []
  s = V, res = [0, 1]
  s = , res = [00, 10, 01, 11]
  Result: [00, 10, 01, 11]

permutts('0V0', ['']) 
  s = 0V0, res = []
  s = V0, res = [0]
  s = 0, res = [00, 01]
  s = , res = [000, 010]
  Result: [000, 010]
关于您的代码,请参见
TrampolineClosure
javadoc:

蹦床闭包需要在一个平台上执行的闭包 功能性蹦床。一经呼叫,蹦床将呼叫 原始关闭等待其结果。如果电话的结果是 另一个蹦床暴露的例子,可能是由此产生的 要调用TrampolineClosure.trampoline()方法 蹦床将再次被调用。这种重复调用 返回的TrampolineClosure实例将继续,直到值为其他值 比蹦床还多。该值将成为最终值 蹦床的结果

也就是说,在尾部调用优化中进行的替换。在您的代码中,
TrampolineClosure
s的整个链在其中一个没有返回蹦床closure时立即返回

在groovy 2.3中,您可以使用
@TailRecursive
AST转换进行尾部调用优化:

import groovy.transform.TailRecursive
@尾递归
列表排列(字符串s,列表res=['']){
如果(s.length()==0){
物件
}否则{
res=(s[0]='V')?res.collect({it=it+'0'})+res.collect({it=it+'1'}):res.collect({it=it+s[0]})
permutts(s.子字符串(1),res)
}
}
编辑

为了完成我的回答,可以在一行中完成上述操作,在Groovy中是(使用集合的头部作为初始值,并在尾部进行迭代):


这真是太棒了…它干净易懂。这是我见过的最好的蹦床例子,我现在明白了,至少比以前好多了。它也教会了我更多关于Groovy的知识。我要等一天才能接受你的回答,这样我也可以给你一笔赏金。谢谢你花时间做出这么好的回应。不客气。我很高兴这有帮助。谢谢你的鼓励。刚才添加了一个示例,说明如何使用groovy的inject实现。
permutts('VV', [''])     
  s = VV, res = []
  s = V, res = [0, 1]
  s = , res = [00, 10, 01, 11]
  Result: [00, 10, 01, 11]

permutts('0V0', ['']) 
  s = 0V0, res = []
  s = V0, res = [0]
  s = 0, res = [00, 01]
  s = , res = [000, 010]
  Result: [000, 010]
assert ['000', '010'] == ['0', 'V', '0'].inject([''], { res, value -> (value == 'V') ?  res.collect({ it = it + '0' }) + res.collect({ it = it + '1' }) : res.collect({ it = it + value }) })
assert ['00', '10', '01', '11'] == ['V', 'V'].inject([''], { res, value -> (value == 'V') ?  res.collect({ it = it + '0' }) + res.collect({ it = it + '1' }) : res.collect({ it = it + value }) })