从递归方法中删除输入

Posted

技术标签:

【中文标题】从递归方法中删除输入【英文标题】:Removing an input from a recursive method 【发布时间】:2017-08-14 12:46:53 【问题描述】:

早上好!我收到了一个问题陈述来编写一个方法,该方法返回传递的字符串输入的所有可能组合,例如

如果通过了 ABC 则返回 [A, AB, BC, ABC, AC, B, C] 如果通过了 ABCD 则返回 [A, AB, BC, CD, ABC, AC, ACD, B, BCD, BD, ABD, AD, C, D, ABCD]

表示 AB 和 BA 总是取相同的,ABC、BAC 和 ACB 也相同。

我最终编写了下面的代码,但它似乎可以正常工作(不确定)。

public static Set<String> getAnyPermutations(String s,String strInput) 
    Set<String> resultSet = new HashSet<>();
    char[] inp = strInput.toCharArray();
    for(int i=0; i<inp.length; i++) 
        String temp =s+String.valueOf(inp[i]);
        resultSet.add(temp);
        if(i+1<=inp.length)
            resultSet.addAll(getAnyPermutations(temp, String.valueOf(Arrays.copyOfRange(inp, i+1, inp.length))));
    
    return resultSet; 

我的问题是,我想从方法中删除第一个参数(String s),因为它只用于内部计算,或者如果这不可能,那么确保用户总是传递一个“”值或者我可以重置对于此方法的第一次(非递归)调用,它为“”。我很困惑如何在递归函数中做到这一点。 如果您怀疑它在这种情况之外可能会失败,也请添加评论。

条件,只能在这个函数内部完成,不能创建其他方法。

【问题讨论】:

你可能想使用方法重载。 @MikeCAT:那将创建另一个“功能”。 “不能创建其他函数” - 为什么不呢?提取方法通常是一个非常好的重构。 【参考方案1】:

只能在这个函数内部完成,不能创建其他函数。

那你就做不到了。该函数没有(合理的)* 方法来知道它是调用自己还是被另一个函数调用。

有很多解决方案涉及创建另一个函数。 可能满足您的要求的一种方法,取决于它们的实际表达方式,是让函数定义一个 lambda 来完成工作,并让 lambda 自己调用。例如,getAnyPermutations 实际上不会是递归的,它会包含一个递归函数。

但这可能超出了上面引用的确切含义,因为 lambda 是另一个函数,只是不能从外部访问。


* un合理的方法是检查堆栈跟踪,您可以从Thread.currentThread().getStackTrace 获得。

【讨论】:

【参考方案2】:

您始终可以将递归方法转换为等效的迭代方法 - 例如参见 Way to go from recursion to iteration.

在迭代版本中,不暴露状态参数很容易(您现在只需在迭代方法的开头初始化它)。

这一般来说不是很实用(但我相信这个问题的目的更理论化,否则只是暴露另一种方法总是一个很好的解决方案)。

此外,在这种特殊情况下,您可能会考虑这种简单的迭代方法(尽管它不是通过直接翻译给定代码获得的):

public static Set<String> getAnyPermutations(String strInput) 
    Set<String> resultSet = new HashSet<>();
    char[] inp = strInput.toCharArray();

    for (int bitMask = 0; bitMask < (1 << inp.length); bitMask++) 
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < inp.length; i++) 
            if ((bitMask & (1 << i)) != 0) 
                str.append(inp[i]);
            
        

        if (str.length() > 0) 
            resultSet.add(str.toString());
        
    

    return resultSet;

【讨论】:

这太棒了,效果惊人..但我不明白有人怎么能想到这个解决方案..你是如何解决问题和这些位移的,你是这些方面的专家还是有什么办法朝那个方向思考?非常感谢! 这是一种已知的技术,用于迭代生成一组大小为 N 的所有子集。例如,请参阅此链接:***.com/questions/728972/…【参考方案3】:

您可以将当前方法更改为私有方法,并将其与具有一个参数的公共方法接口,例如:

private static Set<String> getAnyPermutations(String s,String strInput) 
    Set<String> resultSet = new HashSet<>();
    char[] inp = strInput.toCharArray();
    for(int i=0; i<inp.length; i++)
        String temp =s+String.valueOf(inp[i]);
        resultSet.add(temp);
        if(i+1<=inp.length)
            resultSet.addAll(getAnyPermutations(temp, String.valueOf(Arrays.copyOfRange(inp, i+1, inp.length))));
    
    return resultSet; 

现在,您可以向用户公开一个参数方法,该方法又会调用上述方法,例如:

public static Set<String> getAnyPermutations(String strInput) 
    return getAnyPermutations("", strInput);

更新

如果您根本无法创建任何其他方法,那么唯一的选择就是使用var-args。但是,这需要更改实现,实际上并不限制用户传递多个值。

【讨论】:

【参考方案4】:

您可以重写此特定算法,使其不需要将状态传递到递归调用的调用。

(以 Java 为中心的伪代码):

Set<String> getAnyPermutations(String str) 
    if(str.length() == 0) 
        return Collections.emptySet();
    

    String head = str.substring(0,1);
    String tail = str.substring(1);

    Set<String> permutationsOfTail = getAnyPermutations(tail);

    Set<String> result = new HashSet();

    // Head on its own
    // For input 'ABC', adds 'A'
    result.add(head);

    // All permutations that do not contain head
    // For input 'ABC', adds 'B', 'C', 'BC'
    result.addAll(permutationsOfTail);

    // All permutations that contain head along with some other elements
    // For input 'ABC', adds 'AB, 'AC', 'ABC'
    for(String tailPerm : permutationsOfTail) 
       result.add(head + tailPerm);
    

    return result;

这符合您不创建任何额外方法的目标——但请注意,如果将for 循环提取到允许result.addAll(prefixEachMember(head,permutationsOfTail)) 的新方法Set&lt;String&gt; prefixEachMember(String prefix, Set&lt;String&gt; strings) 中,代码会更简洁。


然而并不总是可以做到这一点,有时你确实想要携带状态。一种方式是您要求避免的方式,但我将把它包含在我的回答中,因为它是实现目标的一种干净且常见的方式。

 public Foo myMethod(Bar input) 
      return myMethod(new HashSet<Baz>(), input);
 

 private Foo myMethod(Set<Baz> state, Bar input) 
      if(...) 
          return ...;
       else 
          ...
          return myMethod(..., ...); 
      
 

这里,第一种方法是您的公共 API,其中不需要收集器/状态参数。第二种方法是私有工作方法,最初调用时使用一个空状态对象。

另一个选项是引用一个对象字段。但是,我建议不要这样做,因为当递归代码引用全局对象时,它会变得混乱。

【讨论】:

如果return Collections.emptySet(); 不是return Collections.singleton(""),您可以省略result.add(head);,因为它成为循环的一部分。就其本身而言,我认为包含空字符串的 1 元素集是空字符串输入情况的正确解决方案。 @RalfKleberhoff 好吧,我想 OP 可以决定他们的要求。我会说空字符串的正确输出是一个空集。 正确。我没有仔细阅读OP声明。 OP给我们的参考结果中没有空字符串。这排除了我对您的答案的修改..

以上是关于从递归方法中删除输入的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 --- [关于递归的理解,使用递归的方式删除链表的元素]

递归-约瑟夫环

Python 3 - 在链表中使用递归

从目录中递归删除.LCK文件。

无法从 Git 递归删除文件

无法从 Git 递归删除文件