Почему следует использовать HashMap (в функциях), чтобы определить, какое значение возвращать (для ключа), когда конструкция if else может выполнить работу в более подходящее время?

9

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

Предположим, у меня есть функция, которая возвращает 12, если вход A, 21, если B, и 45, если C.

Так что я могу написать сигнатуру функции как:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

Но при проверке кода меня попросили изменить функцию на следующую:

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

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

Единственной причиной использования второго кода может быть то, что он обеспечивает лучшую читаемость. Но если функция вызывается много раз, разве вторая функция не замедлит время работы вызывающей ее утилиты?

Что Вы думаете об этом?

Nikunj Banka
источник
4
Для трех значений карта выглядит излишней ( switchкажется более подходящей, чем if-else). Но в какой-то момент это становится проблематичным. Основным преимуществом использования карты является то, что вы можете загрузить ее из файла, таблицы и т. Д. Если вы жестко программируете входные данные для карты, я не вижу большой ценности по сравнению с переключателем.
JimmyJames

Ответы:

16

Смысл в том, чтобы переместить создание хэш-карты за пределы функции и сделать это один раз (или просто меньше, чем в противном случае).

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

Однако с тех пор java7 может иметь (окончательные) строки в переключателях:

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}
чокнутый урод
источник
1
Я не вижу, как это отвечает на вопрос ОП, поэтому -1 здесь. Но +1 за предложение переключиться.
user949300
Он показывает, как правильно реализовать стиль кодирования, чтобы действительно иметь смысл и, возможно, улучшить производительность. Это все еще не имеет смысла для 3 вариантов, но оригинальный код, вероятно, был намного длиннее.
Florian F
12

Во втором примере Mapдолжен быть закрытый статический член, чтобы избежать избыточных издержек инициализации.

Для большого количества значений карта будет работать лучше. Используя хеш-таблицу, можно искать ответ за постоянное время. Конструкция множественного if должна сравнивать входные данные с каждой из возможностей, пока не найдет правильный ответ.

Другими словами, поиск карты - это O (1), а ifs - это O (n), где n - это количество возможных входных данных.

Создание карты O (n), но выполняется только один раз, если это статическое постоянное состояние. Для часто выполняемого поиска карта будет превосходить ifоператоры в долгосрочной перспективе за счет чуть большего времени, когда программа запускается (или класс загружается, в зависимости от языка).

При этом карта не всегда является подходящим инструментом для этой работы. Хорошо, когда значений много, или значения должны быть конфигурируемыми с помощью текстового файла, пользовательского ввода или базы данных (в этом случае карта действует как кэш).


источник
Да, для большого количества значений карта будет работать лучше. Но количество значений фиксировано, и это три.
RemcoGerlich
создание карты - O (N), только поиск в ней - O (1).
Питер Б
Хорошие моменты, я уточнил свой ответ.
Карта также требует автоматической распаковки, что незначительно влияет на производительность.
user949300
@ user949300 на Java, да, и код в вопросе выглядит как Java. Однако он не помечен каким-либо языком, и этот подход работает на нескольких языках (включая C # и C ++, ни один из которых не требует бокса).
3

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

Если вы можете убедить меня (и ваших рецензентов кода), что функция hashmap действительно медленнее, чем if / then / else (после рефакторинга для создания статического hashmap) И вы можете убедить меня / рецензентов, что ее вызывали достаточно раз, чтобы сделать фактическую Разница, а затем замените хэш-карту с if / else.

В противном случае код хэш-карты будет в высшей степени читаемым; и (вероятно) без ошибок; Вы можете определить это быстро, просто посмотрев на него. Вы не можете сказать то же самое о if / else, не изучив его; разница еще более преувеличена, когда есть сотни вариантов.

Роберт Хансон
источник
3
Ну, это возражение разваливается, когда вместо этого сравнивают с переключателем ...
Дедупликатор
Также разваливается, если вы пишете операторы if в одну строку.
gnasher729
2
Поместив создание хэш-карты в другое место, вы просто усложняете процесс выяснения того, что происходит на самом деле. Вы должны увидеть эти клавиши и значения, чтобы узнать, каков реальный эффект функции.
RemcoGerlich
2

Я очень предпочитаю ответ в стиле HashMap.

Для этого есть метрика

Существует метрика качества кода, которая называется Cyclomatic Complexity . Эта метрика в основном подсчитывает количество различных путей в коде ( как вычислить цикломатическую сложность ).

Для каждого возможного пути выполнения метод становится все сложнее и сложнее понять и полностью проверить на правильность.

Это сводится к тому, что «управляющие ключевые слова», такие как: ifs, elses, whiles и т. Д. ... используют булевы тесты, которые могут быть ошибочными. Повторное использование «контрольных ключевых слов» приводит к хрупкому коду.

Дополнительные преимущества

Кроме того, «картографический подход» побуждает разработчиков думать о парах ввода-вывода как о наборе данных, который можно извлекать, использовать повторно, манипулировать во время выполнения, тестировать и проверять. Например, ниже я переписал «foo», чтобы мы не были постоянно заблокированы в «A-> 12, B-> 21, C-> 45»:

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freak упоминает этот тип рефакторинга в своем ответе, он утверждает скорость и повторное использование, я выступаю за гибкость времени выполнения (хотя использование неизменяемой коллекции может иметь огромные преимущества в зависимости от ситуации)

Иван
источник
1
Добавление этой гибкости во время выполнения - это либо отличная перспективная идея, либо ненужное чрезмерное проектирование, которое значительно усложняет понимание происходящего. :-).
user949300
@JimmyJames У меня работает ссылка: Это: leepoint.net/principles_and_practices/complexity/…
Иван
1
@ user949300 Дело в том, что данные ключ / значение, поддерживающие метод "foo", представляют собой отдельную концепцию, которая может заслуживать некоторой ясности. Объем кода, который вы хотите написать для изоляции Карты, будет сильно зависеть от того, сколько элементов содержит Карта и как часто эти элементы нужно менять.
Иван
1
@ user949300 Одна из причин, по которой я предлагаю вытянуть цепочку if-else, заключается в том, что я видел цепочки if-else в группах. В некотором роде подобные тараканы, если есть один метод, основанный на цепочке if-else, вероятно, есть другой метод в другом месте кодовой базы с аналогичной / идентичной цепочкой if-else. Извлечение карты приносит дивиденды, если мы предположим, что могут быть другие методы, которые используют подобную логическую структуру поиска / переключения.
Иван
Я также видел дубликаты, почти идентичные блоки if / else, разбросанные повсюду. Хорошо поставленный
Джон Честерфилд
1

Данные лучше чем код. Не в последнюю очередь потому, что слишком заманчиво добавить еще одну ветвь в код, но добавить строку в таблицу сложно, чтобы ошибиться. Этот вопрос является небольшим примером этого. Вы пишете таблицу поиска. Либо напишите реализацию, дополненную условной логикой и документацией, либо напишите таблицу и посмотрите в ней.

Таблица данных всегда является лучшим представлением некоторых данных, чем код, по модулю оптимизации. То, насколько трудно выразить таблицу, может зависеть от языка - я не знаю Java, но надеюсь, что она может реализовать справочную таблицу проще, чем пример в OP.

Это таблица поиска в Python. Если это рассматривается как вызывающий конфликт, учтите, что вопрос не помечен как java, рефакторинг не зависит от языка, и что большинство людей не знают java.

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

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

Джон Честерфилд
источник
-1 не ответ на вопрос.
Питер Б
В каком смысле? Я бы попросил автора заменить цепочку if else картой, исходя из того, что данные и код должны быть отдельными. Это явная причина, почему второй код лучше первого, хотя все вызовы map.put неудачны.
Джон Честерфилд
2
@JonChesterfield Этот ответ в основном «используйте лучший язык», что редко бывает полезно.
Walpen
@ Walpen Fair Point. Иван сделал примерно то же самое с помощью Java, поэтому мне явно не нужно было переходить на python. Я посмотрю, смогу ли я это немного почистить
Джон Честерфилд,
В более раннем Java-коде мне понадобилось несколько карт для чего-то очень похожего, поэтому я написал небольшую утилиту для преобразования N-мерных массивов 2-D массивов в карту. Немного хакерский, но работал хорошо. Этот ответ указывает на силу Python / JS в поддержке нотации JSONy так просто и легко.
user949300