Чому HashMap слід використовувати (у функціях), щоб визначити, яке значення повернути (для ключа), коли конструкція if if може виконати роботу в кращий час?


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);
}

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

Єдиною причиною використання другого коду може бути те, що він забезпечує кращу читаність. Але якщо функція отримує виклик багато разів, невже друга функція не сповільнить час роботи утиліти, яка викликає її?

Що ти думаєш про це?


4
Для трьох значень карта здається завищеною ( switchздається більш доцільною, ніж if-else). Але в якийсь момент це стає проблематичним. Основна перевага використання Map в тому, що ви можете завантажити її з файлу або таблиці тощо. Якщо ви жорстко кодуєте вхід на карту, я не бачу великого значення через комутатор.
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 варіантів, але, мабуть, початковий код був набагато довшим.
Флоріан Ф

12

У вашому другому прикладі, Mapповинен бути приватним статичним членом, щоб уникнути надмірної ініціалізації.

За великої кількості значень карта працюватиме краще. Використовуючи хештеб, можна шукати відповідь постійно. Конструкція з декількома параметрами повинна порівняти вхід до кожної з можливостей, поки не знайде правильну відповідь.

Іншими словами, пошук на карті - O (1), тоді як ifs - O (n), де n - кількість можливих входів.

Створення карти - O (n), але робиться лише один раз, якщо воно є статичним постійним станом. При частому пошуку, карта буде перевершувати ifтвердження в довгостроковій перспективі, ціною трохи більше часу при запуску програми (або завантаження класу, залежно від мови).

Однак, карта не завжди є правильним інструментом для цієї роботи. Приємно, коли значень багато, або значення мають бути налаштовані за допомогою текстового файлу, вводу користувача чи бази даних (у цьому випадку карта виконує функції кешу).


Так, для великої кількості значень карта працюватиме краще. Але кількість значень фіксована, і це три.
RemcoGerlich

створення карти O (N), лише пошук в ній є O (1).
Пітер Б

Хороші моменти, я уточнив свою відповідь.

Карта також вимагає автоматичного розпакування, що дуже слабо впливає на продуктивність.
user949300

@ user949300 на Java, так, і код у питанні, схоже, є Java. Однак він не позначений жодною мовою, і підхід працює на декількох мовах (включаючи C # і C ++, жодна з яких не вимагає боксу).

3

У програмному забезпеченні дві швидкості: час, необхідний для написання / читання / налагодження коду; і час, необхідний для виконання коду.

Якщо ви можете переконати мене (і ваших рецензентів на код), що функція хешмапу дійсно повільніше, ніж if / then / else (після рефакторингу для створення статичної хешмапу) І ви можете переконати мене / рецензентів у тому, що її закликали достатньо разів, щоб зробити фактичну різниця, тоді вперед і замініть хешмап на if / else.

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


3
Ну, це заперечення розпадається, коли натомість порівнюють із перемикачем ...
Deduplicator

Також розпадається, якщо ви пишете if-заяви в одному рядку.
gnasher729

2
Помістивши створення хешмапу десь в іншому місці, ви просто ускладнюєте зрозуміти, що відбувається насправді. Вам потрібно побачити ці клавіші та значення, щоб знати, яким є фактичний ефект функції.
RemcoGerlich

2

Я дуже вважаю за краще відповідь у стилі HashMap.

Для цього є показник

Існує показник якості коду під назвою Цикломатична складність . Цей показник в основному підраховує кількість різних шляхів через код ( як обчислити цикломатичну складність ).

Для кожного можливого шляху виконання метод стає важчим і складнішим для того, щоб зрозуміти І повністю перевірити на правильність.

Це зводиться до того, що "керування ключовими словами" на зразок: 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, розкидані по всьому місцю. Добре кажучи
Джон Честерфілд

1

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

Таблиця даних завжди є кращим представленням деяких даних, ніж код, проходить оптимізація модулів. Наскільки складно виразити таблицю, може залежати мова - я не знаю Java, але сподіваюся, що вона може реалізувати таблицю пошуку просто, ніж приклад в ОП.

Це таблиця пошуку в python. Якщо це сприймається як запрошення конфлікту, врахуйте, що питання не позначено темою java, рефакторинг - це мовна агностика і що більшість людей не знає Java.

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

Ідея реструктуризації коду для скорочення часу виконання має перевагу, але я б скоріше скористався компілятором, який піднімає загальну настройку, ніж це роблю сам.


-1 не відповідь на запитання.
Пітер Б

В якому сенсі? Я б попросив автора замінити ланцюг if if на карту, виходячи з того, що дані та код мають бути окремими. Це чітка причина, чому другий код кращий за перший, хоча всі виклики map.put невдалі.
Джон Честерфілд

2
@JonChesterfield Ця відповідь в основному "використовувати кращу мову", що рідко є корисним.
проходити

@walpen справедливий пункт. Іван зробив приблизно те саме спостереження через Java, тому мені явно не потрібно було переходити на python. Я побачу, чи зможу я його трохи прибрати
Джон Честерфілд

У деяких старих кодах Java мені було потрібно кілька карт для чогось дуже схожого, тому написав невелику утиліту для перетворення N-мірних масивів 2-D масивів у карту. Трохи гакітний, але добре працював. Ця відповідь вказує на потужність Python / JS у підтримці нотації JSONy так просто та легко.
user949300
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.