使用递归重新排序参数(优点、缺点、替代方案)

发布于 2024-08-27 10:31:58 字数 1874 浏览 6 评论 0原文

我发现我经常进行递归调用只是为了重新排序参数。

例如,这是我对codingbat.com 的 endOther 的解决方案

给定两个字符串,如果其中一个字符串出现在另一个字符串的最末尾,则返回true,忽略大小写差异(换句话说,计算不应该“区分大小写”) ”)。注意:str.toLowerCase() 返回字符串的小写版本。

public boolean endOther(String a, String b) {
  return a.length() < b.length() ? endOther(b, a)
    : a.toLowerCase().endsWith(b.toLowerCase());
}

我对递归非常满意,但我当然可以理解为什么有些人可能会反对它。

对于这种递归技术,有两种明显的替代方法:

传统上交换 ab

public boolean endOther(String a, String b) {
  if (a.length() < b.length()) {
    String t = a;
    a = b;
    b = t;
  }
  return a.toLowerCase().endsWith(b.toLowerCase());
}
  • 对于像 Java 这样不通过引用传递的语言来说并不方便
  • 大量代码只是为了执行简单的操作
  • 额外的 if 语句破坏了“流程”

重复代码

public boolean endOther(String a, String b) {
  return (a.length() < b.length())
    ? b.toLowerCase().endsWith(a.toLowerCase())
    : a.toLowerCase().endsWith(b.toLowerCase());
}
  • 显式对称性可能是一件好事(或不是?)
  • 坏主意,除非重复的代码非常简单
    • ...尽管在这种情况下您可以去掉三元,而只使用 || 两个表达式

所以我的问题是:

  • 这 3 种技术有名称吗? (还有更多吗?)
    • 他们所取得的成就有一个名称吗? (例如“参数标准化”,也许?)
  • 是否有关于使用哪种技术(何时)的官方建议?
  • 我可能错过了哪些其他优点/缺点?

另一个例子

为了将讨论更多地集中在技术而不是特定的codingbat问题上,这里有另一个例子,我觉得递归比一堆if-else、交换或重复代码要优雅得多。

// sorts 3 values and return as array
static int[] sort3(int a, int b, int c) {
    return
      (a > b) ? sort3(b, a, c) :
      (b > c) ? sort3(a, c, b) :
      new int[] { a, b, c };
}

递归和三元运算符并不像某些人那样困扰我。老实说,我相信上面的代码是人们可以编写的最好的纯 Java 解决方案。请随意向我展示其他情况。

I find that I often make a recursive call just to reorder arguments.

For example, here's my solution for endOther from codingbat.com:

Given two strings, return true if either of the strings appears at the very end of the other string, ignoring upper/lower case differences (in other words, the computation should not be "case sensitive"). Note: str.toLowerCase() returns the lowercase version of a string.

public boolean endOther(String a, String b) {
  return a.length() < b.length() ? endOther(b, a)
    : a.toLowerCase().endsWith(b.toLowerCase());
}

I'm very comfortable with recursions, but I can certainly understand why some perhaps would object to it.

There are two obvious alternatives to this recursion technique:

Swap a and b traditionally

public boolean endOther(String a, String b) {
  if (a.length() < b.length()) {
    String t = a;
    a = b;
    b = t;
  }
  return a.toLowerCase().endsWith(b.toLowerCase());
}
  • Not convenient in a language like Java that doesn't pass by reference
  • Lots of code just to do a simple operation
  • An extra if statement breaks the "flow"

Repeat code

public boolean endOther(String a, String b) {
  return (a.length() < b.length())
    ? b.toLowerCase().endsWith(a.toLowerCase())
    : a.toLowerCase().endsWith(b.toLowerCase());
}
  • Explicit symmetry may be a nice thing (or not?)
  • Bad idea unless the repeated code is very simple
    • ...though in this case you can get rid of the ternary and just || the two expressions

So my questions are:

  • Is there a name for these 3 techniques? (Are there more?)
    • Is there a name for what they achieve? (e.g. "parameter normalization", perhaps?)
  • Are there official recommendations on which technique to use (when)?
  • What are other pros/cons that I may have missed?

Another example

To focus the discussion more on the technique rather than the particular codingbat problem, here's another example where I feel that the recursion is much more elegant than a bunch of if-else's, swaps, or repetitive code.

// sorts 3 values and return as array
static int[] sort3(int a, int b, int c) {
    return
      (a > b) ? sort3(b, a, c) :
      (b > c) ? sort3(a, c, b) :
      new int[] { a, b, c };
}

Recursion and ternary operators don't bother me as much as it bothers some people; I honestly believe the above code is the best pure Java solution one can possibly write. Feel free to show me otherwise.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

玩心态 2024-09-03 10:31:58

让我们首先确定代码重复通常是一个坏主意。

因此,无论我们采取什么解决方案,该方法的逻辑都应该只编写一次,并且我们需要一种在不干扰逻辑的情况下交换参数的方法。

我看到了三种通用的解决方案:

  1. 您的第一次递归(使用 if 或条件运算符)。
  2. swap – 这在 Java 中是一个问题,但在其他语言中可能是合适的。
  3. 两种独立的方法(如 @Ha 的解决方案),其中一个充当逻辑的实现,另一个充当接口,在本例中用于整理参数。

我不知道这些解决方案中哪一个客观上是最好的。然而,我注意到某些算法通常将 (1) 视为惯用解决方案,例如用于计算两个数字的 GCD 的 Euklid 算法。

我通常不喜欢交换解决方案 (2),因为它添加了一个额外的调用,而该调用实际上并没有执行与算法相关的任何操作。现在,技术上这不是问题 - 我怀疑使用任何像样的编译器它的效率都会低于(1)或(3)。但它增加了精神上的减速。

解决方案(3)给我的印象是过度设计,尽管我想不出任何批评,除了它需要阅读更多的文字。一般来说,我不喜欢任何以“Impl”为后缀的方法引入的额外间接。

总之,在大多数情况下我可能更喜欢(1),尽管我实际上在类似情况下使用了(3)。

Let’s first establish that code duplication is usually a bad idea.

So whatever solution we take, the logic of the method should only be written once, and we need a means of swapping the arguments around that does not interfere with the logic.

I see three general solutions to that:

  1. Your first recursion (either using if or the conditional operator).
  2. swap – which, in Java, is a problem, but might be appropriate in other languages.
  3. Two separate methods (as in @Ha’s solution) where one acts as the implementation of the logic and the other as the interface, in this case to sort out the parameters.

I don’t know which of these solutions is objectively the best. However, I have noticed that there are certain algorithms for which (1) is generally accepted as the idiomatic solution, e.g. Euklid’s algorithm for calculating the GCD of two numbers.

I am generally averse to the swap solution (2) since it adds an extra call which doesn’t really do anything in connection with the algorithm. Now, technically this isn’t a problem – I doubt that it would be less efficient than (1) or (3) using any decent compiler. But it adds a mental speed-bump.

Solution (3) strikes me as over-engineered although I cannot think of any criticism except that it’s more text to read. Generally, I don’t like the extra indirection introduced by any method suffixed with “Impl”.

In conclusion, I would probably prefer (1) for most cases although I have in fact used (3) in similar circumstances.

送你一个梦 2024-09-03 10:31:58

另一个+1表示“无论如何,我的建议是在每个语句中尽可能少地做事。在单个语句中做的事情越多,对于需要维护代码的其他人来说就越令人困惑。”

抱歉,你的代码:

// sorts 3 values and return as array
static int[] sort3(int a, int b, int c) {
    return
      (a > b) ? sort3(b, a, c) :
      (b > c) ? sort3(a, c, b) :
      new int[] { a, b, c };
}

这可能对你来说是最好的“纯java代码”,但对我来说这是最糟糕的......不可读的代码,如果我们没有方法或注释,我们第一眼就无法知道什么它正在做...

难以阅读的代码应该只在需要高性能时使用(但无论如何,许多性能问题都是由于糟糕的架构...)。如果你必须编写这样的代码,那么你能做的就是制作一个好的javadoc和单元测试......如果我们只需要使用它而不是返工的话,我们开发人员通常不关心这些方法的实现...但是由于第一眼并没有告诉我们它的作用,我们必须相信它会像我们期望的那样工作,并且我们可以节省时间...

当它是一个简短的方法时,递归方法是可以的,但我认为如果算法很复杂并且有另一种方法可以在几乎相同的计算时间内完成它,则应避免使用递归方法......特别是如果其他人可能会使用这种方法。

对于你的例子来说,它是一个简短的方法,但无论如何,如果你不关心性能,你可以使用类似的东西:

// sorts int values
public static int[] sort(Integer... intValues) {
    ArrayList list = new ArrayList(
    for ( Integer i : intValues ) {
      list.add(i);
    }
    Collections.sort(list);
    return list.toArray();
}

一种简单的方法来实现你的方法,所有java> = 1.5开发人员都可以轻松阅读,适用于 1 到 n 个整数...
不是最快的,但无论如何,如果只是为了速度,请使用 c++ 或 asm :)

Another +1 for "In any case, my recommendation would be to do as little in each statement as possible. The more things that you do in a single statement, the more confusing it will be for others who need to maintain your code."

Sorry but your code:

// sorts 3 values and return as array
static int[] sort3(int a, int b, int c) {
    return
      (a > b) ? sort3(b, a, c) :
      (b > c) ? sort3(a, c, b) :
      new int[] { a, b, c };
}

It's perhaps for you the best "pure java code", but for me it's the worst... unreadable code, if we don't have the method or the comment we just can't know at first sight what it's doing...

Hard to read code should only be used when high performances are needed (but anyway many performances problems are due to bad architecture...). If you HAVE TO write such code, the less you can do is to make a good javadoc and unit tests... we developper often don't care about implementation of such methods if we just have to use it, and not to rework it... but since the first sight doesn't tell us what is does, we can have to trust it works like we expect it does and we can loose time...

Recursive methods are ok when it's a short method, but i think a recursive method should be avoided if the algorithm is complex and if there's another way to do it for almost the same computation time... Particulary if other peoples will prolly work in this method.

For your exemple it's ok since it's a short method, but anyway if you'r just not concerned by performances you could have used something like that:

// sorts int values
public static int[] sort(Integer... intValues) {
    ArrayList list = new ArrayList(
    for ( Integer i : intValues ) {
      list.add(i);
    }
    Collections.sort(list);
    return list.toArray();
}

A simple way to implement your method, easily readable by all java >= 1.5 developper, that works for 1 to n integers...
Not the fastest but anyway if it's just about speed use c++ or asm :)

久光 2024-09-03 10:31:58

对于这个特定的例子,我不会使用你建议的任何东西。我会写:

public boolean endOther(String a, String b){
    String alower=a.toLowerCase();
    String blower=b.toLowerCase();
    if ( a.length() < b.length() ){
        return blower.endsWith(alower);
    } else {
        return alower.endsWith(blower);
    }
} 

虽然三元运算符确实有它的位置,但 if 语句通常更容易理解,特别是当操作数相当多时复杂的。此外,如果您在 if 语句的不同分支中重复代码,则它们只会在所采用的分支中进行计算(在许多编程语言中,三元运算符的两个操作数都会被计算,无论哪个)已选择分支)。虽然,正如您所指出的,这在 Java 中不是一个问题,但许多程序员使用过多种语言,并且可能不记得这种详细程度,因此最好仅对简单的操作数使用三元运算符。

人们经常听说“递归”与“迭代”/“非递归”实现。我没有听说过您所提供的各种选项的任何特定名称。

无论如何,我的建议是在每个声明中尽可能少做任何事情。您在一条语句中执行的操作越多,对于需要维护您的代码的其他人来说就会越混乱。

就您对重复的抱怨而言......如果有几行被重复,那么是时候创建一个“帮助器”函数来完成该部分。函数组合的目的是减少重复。交换没有任何意义,因为交换比简单地重复需要更多的努力......而且,如果函数后面的代码使用参数,那么参数现在的含义与以前不同。

编辑
我关于三元运算符的论点不是有效的......绝大多数编程语言都使用三元运算符的惰性求值(在撰写本文时我正在考虑 Verilog,它是一种硬件描述语言( HDL),其中两个分支并行评估)。也就是说,有充分的理由避免在三元运算符中使用复杂的表达式;例如,使用 if...else 语句,可以在其中一个条件分支上设置断点,而使用三元运算符,两个分支都是同一语句的一部分,因此大多数调试器不会在它们上进行拆分。

For this particular example, I wouldn't use anything you suggested.. I would instead write:

public boolean endOther(String a, String b){
    String alower=a.toLowerCase();
    String blower=b.toLowerCase();
    if ( a.length() < b.length() ){
        return blower.endsWith(alower);
    } else {
        return alower.endsWith(blower);
    }
} 

While the ternary operator does have its place, the if statement is often more intelligible, especially when the operands are fairly complex. In addition, if you repeat code in different branches of an if statement, they will only be evaluated in the branch that is taken (in many programming languages, both operands of the ternary operator are evaluated no matter which branch is selected). While, as you have pointed out, this is not a concern in Java, many programmers have used a variety of languages and might not remember this level of detail, and so it is best to use the ternary operator only with simple operands.

One frequently hears of "recursive" vs. "iterative"/"non-recursive" implementations. I have not heard of any particular names for the various options that you have given.

In any case, my recommendation would be to do as little in each statement as possible. The more things that you do in a single statement, the more confusing it will be for others who need to maintain your code.

In terms of your complaint about repetitiion... if there are several lines that are being repated, then it is time to create a "helper" function that does that part. Function composition is there to reduce repitition. Swapping just doesn't make any sense to do, since there is more effort to swap than to simply repeat... also, if code later in the function uses the parameters, the parameters now mean different things than they used to.

EDIT
My argument vis-a-vis the ternary operator was not a valid one... the vast majority of programming languages use lazy evalution with the ternary operator (I was thinking of Verilog at the time of writing, which is a hardware description language (HDL) in which both branches are evaluated in parallel). That said, there are valid reasons to avoid using complicated expressions in ternary operators; for example, with an if...else statement, it is possible to set a breakpoint on one of the conditional branches whereas, with the ternary operator, both branches are part of the same statement, so most debuggers won't split on them.

请叫√我孤独 2024-09-03 10:31:58

使用其他方法代替递归稍微好一些

public boolean endOther(String a, String b) {
    return a.length() < b.length() ? endOtherImpl(b,a):endOtherImpl(a,b);
}

protected boolean endOtherImpl(String longStr,String shortStr)
{
    return longStr.toLowerCase().endsWith(shortStr.toLowerCase());
}

It is slightly better to use another method instead of recursion

public boolean endOther(String a, String b) {
    return a.length() < b.length() ? endOtherImpl(b,a):endOtherImpl(a,b);
}

protected boolean endOtherImpl(String longStr,String shortStr)
{
    return longStr.toLowerCase().endsWith(shortStr.toLowerCase());
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文