Изменение порядка аргументов с использованием рекурсии (за, против, альтернативы) - PullRequest
3 голосов
/ 04 апреля 2010

Я обнаружил, что часто делаю рекурсивный вызов, чтобы просто изменить порядок аргументов.

Например, вот мое решение для endOther от codingbat.com :

Учитывая две строки, вернуть true, если одна из строк появляется в самом конце другой строки, игнорируя различия в верхнем / нижнем регистре (другими словами, вычисление не должно быть "чувствительным к регистру"). Примечание: str.toLowerCase() возвращает строчную версию строки.

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

Мне очень удобно с рекурсиями, но я, конечно, могу понять, почему некоторые, возможно, будут возражать против этого.

Есть две очевидные альтернативы этой технике рекурсии:

Своп a и b традиционно

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 техник? (Есть еще?)
    • Есть ли название для того, чего они достигают? (например, «нормализация параметров», возможно?)
  • Есть ли официальные рекомендации относительно того, какую технику использовать (когда)?
  • Какие еще плюсы / минусы я мог пропустить?

Другой пример

Чтобы сосредоточить обсуждение больше на технике, а не на конкретной проблеме кодирования, вот еще один пример, в котором я чувствую, что рекурсия гораздо более элегантна, чем набор if-else, swap или повторяющегося кода.

// 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, которое только можно написать. Не стесняйтесь, чтобы показать мне иначе.

Ответы [ 4 ]

1 голос
/ 04 апреля 2010

Еще один +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:)

1 голос
/ 04 апреля 2010

Давайте сначала установим, что дублирование кода обычно является плохой идеей.

Таким образом, какое бы решение мы ни выбрали, логика метода должна быть записана только один раз, и нам нужны средства обмена аргументами, которые не мешают логике.

Я вижу три общих решения для этого:

  1. Ваша первая рекурсия (с использованием if или условного оператора).
  2. swap - что в Java является проблемой, но может быть уместным в других языках.
  3. Два отдельных метода (как в решении @ Ha), где один действует как реализация логики, а другой - как интерфейс , в этом случае для сортировки параметров.

Я не знаю, какое из этих решений объективно является лучшим. Однако я заметил, что существуют определенные алгоритмы, для которых (1) обычно принимается как идиоматическое решение, например, Алгоритм Евклида для вычисления GCD двух чисел.

Обычно я не согласен с решением swap (2), так как оно добавляет дополнительный вызов, который ничего не делает в связи с алгоритмом. Теперь технически это не проблема - я сомневаюсь, что это было бы менее эффективно, чем (1) или (3) с использованием любого достойного компилятора. Но это добавляет умственное ускорение.

Решение (3) кажется мне чрезмерно сложным, хотя я не могу думать ни о какой критике, кроме того, что это больше текста для чтения. Как правило, мне не нравится дополнительная косвенность, введенная любым методом с суффиксом «Impl».

В заключение, я бы, вероятно, предпочел (1) для большинства случаев, хотя у меня действительно фактически использовался (3) в аналогичных обстоятельствах.

1 голос
/ 04 апреля 2010

В этом конкретном примере я бы не использовал то, что вы предложили. Вместо этого я написал бы:

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 можно установить точку останова на одной из условных ветвей, тогда как с помощью тернарного оператора обе ветви являются частью одного и того же оператора, поэтому большинство отладчиков не разделяются на них .

0 голосов
/ 04 апреля 2010

Немного лучше использовать другой метод вместо рекурсии

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());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...