如何停止递归?

时间:2016-07-01 17:44:05

标签: recursion functional-programming declarative factor-lang

Advent of Code Day 1要求以一种或多种形式循环使用((((())(())(((()))((等长串括号。我们的想法是(上升到"地板&#34 ;,)落在一层,目标是打印

  1. 字符串中第一个索引为负数且
  2. 的索引
  3. 找到字符串末尾的最后一层。
  4. 使用for循环的命令式解决方案很简单(以Python为例):

    def main():
        flr = 0
        basement = False
        for idx, elt in enumerate(text):
            flr += {
                "(": 1,
                ")": -1
            }.get(elt)
    
            if flr < 0 and not basement:
                print("first basement pos:", idx + 1)
                basement = True
    
        print("final floor:", flr)
    

    递归函数解决方案有点复杂,但仍然不太难。

    def worker(flr, txt, idx, basement):
        flr += {"(": 1, ")": -1}[ txt[0] ]
    
        if not (len(txt) - 1): return flr
    
        if flr < 0 and not basement:
            print("first basement floor index: ", idx + 1)
            basement = True
    
        return worker(flr, txt[1:], idx + 1, basement)
    
    
    def starter(txt):
        flr, basement, idx = 0, False, 0
        return worker(flr, txt, idx, basement)
    
    
    if __name__ == '__main__':
        __import__("sys").setrecursionlimit(int(1e5))
        print("final floor:", starter(text))
    

    这两个都给出正确的

    输出
    first basement floor index:  1795
    final floor: 74
    

    在我的挑战输入时运行。

    除了第二个是愚蠢的,因为Python没有尾调用优化,但没关系

    如何在Factor中实现这些中的任何一个?自从我开始使用因子以来,我就一直感到困惑。

    我们不能只使用for循环,因为没有等价物允许我们在迭代之间保持可变状态。

    我们可以使用递归解决方案:

    : day-1-starter ( string -- final-floor )
      [ 0 ] dip 0 f day-1-worker 3drop "final floor: %s" printf ;
    
    : day-1-worker 
      ( floor string index basement? -- floor string index basement? )
      day-1-worker  ! what goes here?
      ; recursive
    

    太好了,这是一个骷髅,但day-1-worker的内容是什么?因素并没有任何方法可以及早回归&#34;来自递归调用,因为没有办法反向运行程序而没有返回的概念 - 这没有任何意义。

    我感觉可能递归并不是因子中这个问题的答案。如果是,我该如何停止递归呢?

3 个答案:

答案 0 :(得分:3)

首先,递归总是答案:) 由于这是一个挑战(而且我不知道因素),只需提示: 在您的python解决方案中,您已使用副作用来打印第一个地下室级别。完全没必要!您也可以使用 basemet 参数来保存楼层编号,如下所示:

    def worker(flr, txt, idx, basement):
        flr += {"(": 1, ")": -1}[ txt[0] ]

        if not (len(txt) - 1): return [flr, basement] # <- return both

        if flr < 0 and not basement:
            #print("first basement floor index: ", idx + 1) # side effects go away!
            basement = idx+1 # <- a number in not False, so that's all
        return worker(flr, txt[1:], idx + 1, basement)

现在你得到了

    final,first_basement = worker(0, txt, 0, False)
或者,或者你也可以编写2个函数,第一个是寻找地下一层的索引,另一个是计算最后一层。即使你关心性能,有+ 2000额外的小步也不是什么大问题。

祝你好运!

修改:截至关于因子递归的问题,请查看the Ackermann Function in Factorthe Fibonacci sequence in Factor,您应该知道如何打破环路&#34 ;.实际上唯一的问题是思考(从命令式模型中解放自己:));在函数式语言中,没有&#34; return&#34;,只是最终的值,你提到的基于堆栈的语言是同一事物的其他计算模型(而不是考虑折叠一个人想到的树#34;推送和弹出堆栈&#34; - 这是实现前者的常用方法。

编辑:( SPOILER!) 我安装了Factor并开始玩它(相当不错),对于第一个问题(计算最终得分)可能的解决方案是

    : day-1-worker (  string floor -- floor )
      dup length 0 = 
       [ drop ]
       [ dup first 40 =
         [ swap 1 + ]
         [ swap 1 - ]
         if
         swap rest
         day-1-worker ]
       if ;

    : day-1-starter ( string -- floor )
      0 swap day-1-worker ;

所以现在你可以为计算地下室的索引写一个类似的,或者(这会更酷!)来修改它,以便它也管理索引和基础...(可能使用 cond 比嵌套 if s更明智。

答案 1 :(得分:3)

您可以使用cum-sum组合器:

: to-ups/downs ( str -- seq )
    [ CHAR: ( = 1 -1 ? ] { } map-as ;

: run-elevator ( str -- first-basement final-floor )
    to-ups/downs cum-sum [ -1 swap index 1 + ] [ last ] bi ;

IN: scratchpad "((())))(())(())(((()))((" run-elevator

--- Data stack:
7
2

答案 2 :(得分:2)

修改

我最初误读了你计算basement值的方式。我已经更新了以下答案

这是一个JavaScript解决方案。对不起,我不知道这如何转换为因子。 reduce是一个迭代过程

const worker = txt=>
  txt.split('').reduce(({floor, basement}, x, i)=> {
    if (x === '(')
      return {floor: floor + 1, basement}
    else if (basement === null && floor === 0)
      return {floor: floor - 1, basement: i}
    else
      return {floor: floor - 1, basement}
  }, {floor: 0, basement: null})
	
let {floor, basement} = worker('((((())(())(((()))((')
console.log(floor)    //=> 6
console.log(basement) //=> null; never reaches basement

上面的答案取决于某些.split.reduce,这些可能不会出现在您的语言中。这是使用Y-combinator的另一种解决方案,只有substring内置(大多数语言都包含在内)。这个答案还取决于你的语言是否具有一流的功能。

const U = f=> f (f)
const Y = U (h=> f=> f (x=> h (h) (f) (x)))

const strhead = s=> s.substring(0,1)
const strtail = s=> s.substring(1)

const worker = Y (f=> ({floor, basement})=> i=> txt=> {
  // txt is empty string; return answer
  if (txt === '')
    return {floor, basement}

  // first char in txt is '(', increment the floor
  else if (strhead (txt) === '(')
    return f ({floor: floor + 1, basement}) (i+1) (strtail (txt))

  // if basement isn't set and we're on floor 0, we found the basement
  else if (basement === null && floor === 0)
    return f ({floor: floor - 1, basement: i}) (i+1) (strtail (txt))

  // we're already in the basement, go down another floor
  else
    return f ({floor: floor - 1, basement}) (i+1) (strtail (txt))
}) ({floor: 0, basement: null}) (0)

{
  let {floor, basement} = worker('((((())(())(((()))((')
  console.log(floor)    //=> 6
  console.log(basement) //=> null; never reaches basement
}

{
  let {floor, basement} = worker(')(((((')
  console.log(floor)    //=> 4
  console.log(basement) //=> 0
}

{
  let {floor, basement} = worker('((())))')
  console.log(floor)    //=> -1
  console.log(basement) //=> 6
}

相关问题