如何在Java的递归算法中保持"事情完成"?

时间:2020-03-05 18:45:03  来源:igfitidea点击:

我有一个递归算法,该算法逐个字符地遍历字符串,然后解析它以创建树状结构。我希望能够跟踪解析器当前所在的字符索引(对于错误消息以及其他信息),但是不希望实现类似元组的东西来处理多个返回的类型。

我尝试使用Integer类型,该类型在方法外声明并传递到递归方法中,但是由于它是最终的,因此当我返回时,递归调用增量被"忘记"了。 (因为整数值的增加使按值传递的对象参考点位于新对象上)

有没有办法使类似的工作不会污染我的代码?

解决方案

回答

这有点像黑客,但有时我会使用可变的AtomicInteger来做这样的事情。我还看到了传入大小为1的int []的情况。

回答

我正在使用的当前解决方案是:

int[] counter = {0};

然后将其传递给递归算法:

public List<Thing> doIt (String aString, int[] counter) { ... }

当我想增加它时:

counter[0]++;

不是很优雅,但是可以用...

回答

整数是不可变的,这意味着当我们将其作为参数传递时,它会创建一个副本,而不是对同一项目的引用。 (解释)。

为了获得我们想要的行为,我们可以编写自己的类,就像Integer only mutable。然后,只要将其传递给递归函数,它就会在递归中递增,并且当递归结束后再次访问它时,它仍将保持其新值。

编辑:请注意,使用int []数组是此方法的一种变体...在Java中,数组也通过引用传递,而不是像基元或者不可变类那样复制。

回答

由于我们已经发现了伪可变整数" hack",因此该选项如何:

制作一个单独的解析器类对我们有意义吗?如果执行此操作,则可以将当前状态存储在成员变量中。我们可能需要考虑如何处理任何线程安全性问题,这对于该特定应用程序可能会显得过大,但它可能对我们有用。

回答

老实说,我将对函数进行重新编码,使其成为使用循环的线性算法。这样,如果我们要遍历一个非常大的字符串,就没有机会用完堆空间。同样,我们无需具有额外的参数即可跟踪计数。

这也可能会导致算法更快,因为它不需要对每个字符都进行函数调用。

当然,除非有特殊原因,否则需要递归。

回答

我可以想到的一种可能性是将计数存储在类的成员变量中。当然,这假定公共的doIt方法仅由单个线程调用。

另一种选择是重构公共方法以调用私有帮助器方法。 private方法将列表作为参数并返回计数。例如:

public List<Thing> doIt(String aString) {
    List<Thing> list = new ArrayList<Thing>();
    int count = doItHelper(aString, list, 0);
    // ...
    return list;
}

private int doItHelper(String aString, List<Thing> list, int count) {
    // ...
    // do something that updates count
    count = doItHelper(aString, list, count);
    // ...
    return count;
}

假设我们可以在公共doIt方法中进行错误处理,因为count变量实际上并未传递回调用者。如果需要这样做,当然可以抛出异常:

public List<Thing> doIt(String aString) throws SomeCustomException {
    List<Thing> list = new ArrayList<Thing>();
    int count = doItHelper(aString, list, 0);
    // ...
    if (someErrorOccurred) {
        throw new SomeCustomException("Error occurred at chracter index " + count, count);
    }
    return list;
}

在不进一步了解算法实际工作原理的情况下,很难知道这是否会有所帮助。

回答

我们可以只使用一个静态int类变量,该变量在每次调用doIt方法时都会递增。