Занадто багато тверджень "якщо"?


263

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

Чи є такий тип математичної формули, який би мені приніс користь у цьому випадку, або 16, якщо твердження прийнятні?

Щоб пояснити код, це така собі гра з одночасним поворотом. У двох гравців є по чотири кнопки дій кожна, результати виходять із масиву (0-3), але змінні "один" та "два" можуть бути призначив що-небудь, якщо це допоможе. Результат: 0 = ні перемога, 1 = p1 перемога, 2 = p2 виграш, 3 = обидва виграти.

public int fightMath(int one, int two) {

    if(one == 0 && two == 0) { result = 0; }
    else if(one == 0 && two == 1) { result = 0; }
    else if(one == 0 && two == 2) { result = 1; }
    else if(one == 0 && two == 3) { result = 2; }
    else if(one == 1 && two == 0) { result = 0; }
    else if(one == 1 && two == 1) { result = 0; }
    else if(one == 1 && two == 2) { result = 2; }
    else if(one == 1 && two == 3) { result = 1; }
    else if(one == 2 && two == 0) { result = 2; }
    else if(one == 2 && two == 1) { result = 1; }
    else if(one == 2 && two == 2) { result = 3; }
    else if(one == 2 && two == 3) { result = 3; }
    else if(one == 3 && two == 0) { result = 1; }
    else if(one == 3 && two == 1) { result = 2; }
    else if(one == 3 && two == 2) { result = 3; }
    else if(one == 3 && two == 3) { result = 3; }

    return result;
}


9
Звичайно, тут є якась логіка, яка може бути узагальнена, а не насильницька? Звичайно, є якась функція, f(a, b)яка дає відповідь у загальному випадку? Ви не пояснили логіку розрахунку, тому всі відповіді - це лише помада на свині. Я б почав серйозно переосмислити вашу логіку програми, використання intпрапорів для дій дуже застаріло. enums можуть містити логіку та описові, це дозволить вам написати свій код більш сучасним способом.
Борис Павук

Після прочитання відповідей @Steve Benett, наданих у своєму альтернативному запитанні, пов'язаному вище, я можу припустити, що немає прямої відповіді формули на це, оскільки вона по суті така ж, як у базі даних. Я спробував пояснити в оригінальному питанні, що я робив просту гру (винищувач), і у користувачів є вибір з 4 кнопок: blockHigh (0), blockLow (1), attackHigh (2) і attackLow (3). Ці номери зберігаються в масиві до необхідності. Пізніше вони використовуються функцією 'fightMath ()', яка викликає вибір гравцяOne проти гравцяTwos, щоб дати результат. Фактичного виявлення зіткнення немає
TomFirth

9
Якщо у вас є відповідь, будь ласка, опублікуйте її як таку. Розширене обговорення в коментарях важко прослідкувати, особливо коли йдеться про код. Якщо ви хочете поговорити про те, чи варто це питання перенести на Code Review, існує мета- дискусія про це.
Джордж Стокер

1
Що ви маєте на увазі під «тим же самим, що і база даних»? Якщо ці значення знаходяться в базі даних, витягніть їх звідти. В іншому випадку, якщо це дійсно такий складний варіант, я б залишив його так, як у вас є, і додаю коментарі ділової логіки після кожного рядка, щоб люди зрозуміли, що відбувається. Краще (для мене) довгий і явний - хтось у майбутньому може зрозуміти, що відбувається. Якщо ви покладете його на карту або спробуєте зберегти 8 рядків коду, перевершення дійсно невелике, а зменшений розмір більший: ви робите все більше заплутаним для того, кому потрібно прочитати ваш код одного дня.
skaz

Відповіді:


600

Якщо ви не можете придумати формулу, ви можете використовувати таблицю для такої обмеженої кількості результатів:

final int[][] result = new int[][] {
  { 0, 0, 1, 2 },
  { 0, 0, 2, 1 },
  { 2, 1, 3, 3 },
  { 1, 2, 3, 3 }
};
return result[one][two];

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

4
Вам не потрібно затверджувати, Java все IndexOutOfBoundsExceptionодно кине, якщо один або кілька індексів виходять за межі.
JAB

43
@JoeHarper Якщо ви хочете щось легко прочитати, ви в першу чергу не будете використовувати магічні числа, і у вас з’явиться коментар, що пояснює відображення. Як і є, я віддаю перевагу цій версії перед оригіналом, але для чогось досяжного в довгостроковій перспективі я б застосував підхід, що включає перелічені типи або принаймні названі константи.
JAB

13
@JoeHarper "Теоретично" - це одне, "практично" - інше. Звичайно, я намагаюся використовувати описові імена (за винятком конвенції i/ j/ kдля змінних циклу), іменовані константи, упорядковуючи свій код читабельним способом тощо., Але коли імена змінних та функцій починають містити більше 20 символів кожен я вважаю це фактично призводить до менш читабельного коду. Мій звичайний підхід - спробувати зрозумілий, але стислий код з коментарями тут і там, щоб пояснити, чому код структурований таким, яким він є (проти як). Поставивши чому в назвах просто захаращується все.
JAB

13
Мені подобається таке рішення цієї конкретної проблеми, оскільки результати насправді диктуються матрицею результатів.
Проба

201

Оскільки набір даних настільки малий, ви можете стиснути все в 1 довге ціле число і перетворити його у формулу

public int fightMath(int one,int two)
{
   return (int)(0xF9F66090L >> (2*(one*4 + two)))%4;
}

Бітовий варіант:

Це використовує той факт, що все кратно 2

public int fightMath(int one,int two)
{
   return (0xF9F66090 >> ((one << 3) | (two << 1))) & 0x3;
}

Походження магічної константи

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

Суть функції, що вирішує задачу ОП, - це карта від 2 чисел (одне, два), домен {0,1,2,3} до діапазону {0,1,2,3}. Кожна з відповідей підходила до того, як реалізувати цю карту.

Крім того, ви можете побачити в ряді відповідей перестановку задачі як карту 1 2-значного базового 4 числа N (один, два), де один - цифра 1, два - цифра 2, а N = 4 * одна + два; N = {0,1,2, ..., 15} - шістнадцять різних значень, це важливо. Результатом функції є одне однозначне базове 4 число {0,1,2,3} - 4 різні значення, також важливі.

Тепер 1-розрядне базове 4 число може бути виражене як двозначне базове 2 число; {0,1,2,3} = {00,01,10,11}, і тому кожен вихід може бути закодований лише 2 бітами. Зверху можливі лише 16 різних виходів, тому 16 * 2 = 32 біта - це все, що необхідно для кодування всієї карти; це все може вміститися в одне ціле число.

Константа M - це кодування карти m, де m (0) кодується бітами M [0: 1], m (1) кодується бітами M [2: 3], а m (n) кодується бітами M [n * 2: n * 2 + 1].

Залишилося лише індексувати та повернути праву частину константи, в цьому випадку ви можете змістити M вправо в 2 * N рази і взяти 2 найменш значущих біта, тобто (M >> 2 * N) & 0x3. Вирази (один << 3) і (два << 1) просто множать речі, зазначаючи, що 2 * x = x << 1 і 8 * x = x << 3.


79
розумний, але ніхто більше не читає код не матиме шансу зрозуміти його.
Аран Малхолланд

106
Я думаю, що це вкрай погана практика. Ніхто інший, крім автора, цього не зрозуміє. Ви хочете переглянути фрагмент коду і швидко зрозуміти його. Але це лише марна трата часу.
Balázs Németh

14
Я з цим балазом @ BalázsMáriaNémeth. Хоча це дуже вражає, вам слід кодувати насильницьких психопатів!
OrhanC1

90
Всі потоки вважають, що це жахливий запах коду. Усі зверхники думають однаково, але захоплюються розумністю, що стоїть за ним. +1 (Ніколи не використовуйте цей код.)
usr

4
Який прекрасний приклад написання лише коду !
lealand

98

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

Ось як я написав би цей код - я знаю лише C #, а не Java, але ви отримаєте малюнок:

const bool t = true;
const bool f = false;
static readonly bool[,] attackResult = {
    { f, f, t, f }, 
    { f, f, f, t },
    { f, t, t, t },
    { t, f, t, t }
};
[Flags] enum HitResult 
{ 
    Neither = 0,
    PlayerOne = 1,
    PlayerTwo = 2,
    Both = PlayerOne | PlayerTwo
}
static HitResult ResolveAttack(int one, int two)
{
    return 
        (attackResult[one, two] ? HitResult.PlayerOne : HitResult.Neither) | 
        (attackResult[two, one] ? HitResult.PlayerTwo : HitResult.Neither);
}    

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

Однак це може бути ще краще; що булівський масив дещо непрозорий. Мені подобається підхід до пошуку таблиці, але я був би схильний записати його таким чином, щоб було зрозуміло, якою була призначена семантика гри. Тобто, замість того, щоб "атака нуля і захист одного результату не влучила", замість того, щоб знайти спосіб зробити код більш чітким, що означає "атаку з низьким ударом і низький захист блоку не призводить до удару". Зробіть, щоб код відображав ділову логіку гри.


66
Дурниці. Більшість м'яко досвідчених програм зможуть оцінити поради, які даються тут, і застосувати стиль кодування до власного мови. Питання полягало в тому, як уникнути рядку ifs. Це показує, як.
GreenAsJade

6
@ user3414693: Я добре знаю, що це питання Java. Якщо ви уважно прочитаєте відповідь, це стане зрозумілим. Якщо ви вважаєте, що моя відповідь нерозумна, то я закликаю вас написати свою відповідь, яка вам більше подобається.
Ерік Ліпперт

1
@EricLippert Мені також подобається рішення JAB. IMHO, тип перерахунку в C # залишає бажати кращого. Він не дотримується філософії ями успіху, що решта функцій. Наприклад, stackoverflow.com/a/847353/92414 Чи планує команда c # створити новий тип перерахунку (щоб уникнути порушення існуючого коду), який розроблений краще?
SolutionYogi

@SolutionYogi: Мені не дуже подобаються перерахунки на C #, хоча вони є такими, якими вони є з добрих історичних причин. (Переважно щодо сумісності з існуючими перерахунками COM.) Мені невідомі плани планувати додавання нового обладнання для перерахунків на C # 6.
Ерік Ліпперт

3
@ Списку немає, коментарі не запускаються. ОП зробив саме те, що слід зробити; конвертувати коментарі в чистий код. Див, наприклад, Код Стіва МакКоннелла Повна stevemcconnell.com/cccntnt.htm
djechlin

87

Ви можете створити матрицю, яка містить результати

int[][] results = {{0, 0, 1, 2}, {0, 0, 2, 1},{2, 1, 3, 3},{2, 1, 3, 3}};

Коли ви хочете отримати цінність, ви будете використовувати

public int fightMath(int one, int two) {
  return this.results[one][two]; 
}

69

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

public int fightMath(int one, int two) {
    if (one > 3 || one < 0 || two > 3 || two < 0) {
        throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
    }

    if (one <= 1) {
        if (two <= 1) return 0;
        if (two - one == 2) return 1;
        return 2; // two can only be 3 here, no need for an explicit conditional
    }

    // one >= 2
    if (two >= 2) return 3;
    if (two == 1) return 1;
    return 2; // two can only be 0 here
}

Це в кінцевому підсумку є менш читабельним, ніж це може бути інакше через неправильність відображення частин введення> результатів. Натомість я віддаю перевагу стилю матриці через його простоту та те, як ви можете налаштувати матрицю, щоб візуально мати сенс (хоча на це частково впливають мої спогади про карти Карна):

int[][] results = {{0, 0, 1, 2},
                   {0, 0, 2, 1},
                   {2, 1, 3, 3},
                   {2, 1, 3, 3}};

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

enum MoveType {
    ATTACK,
    BLOCK;
}

enum MoveHeight {
    HIGH,
    LOW;
}

enum Move {
    // Enum members can have properties/attributes/data members of their own
    ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
    ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
    BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
    BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);

    public final MoveType type;
    public final MoveHeight height;

    private Move(MoveType type, MoveHeight height) {
        this.type = type;
        this.height = height;
    }

    /** Makes the attack checks later on simpler. */
    public boolean isAttack() {
        return this.type == MoveType.ATTACK;
    }
}

enum LandedHit {
    NEITHER,
    PLAYER_ONE,
    PLAYER_TWO,
    BOTH;
}

LandedHit fightMath(Move one, Move two) {
    // One is an attack, the other is a block
    if (one.type != two.type) {
        // attack at some height gets blocked by block at same height
        if (one.height == two.height) return LandedHit.NEITHER;

        // Either player 1 attacked or player 2 attacked; whoever did
        // lands a hit
        if (one.isAttack()) return LandedHit.PLAYER_ONE;
        return LandedHit.PLAYER_TWO;
    }

    // both attack
    if (one.isAttack()) return LandedHit.BOTH;

    // both block
    return LandedHit.NEITHER;
}

Вам навіть не потрібно змінювати саму функцію, якщо ви хочете додати блоки / атаки більшої висоти, просто перерахунки; додавання додаткових типів рухів, ймовірно, потребуватиме зміни функції. Крім того, EnumSets може бути більш розширюваним, ніж використання додаткових перерахунків як властивостей основного перерахунку, наприклад, EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);а потім, attacks.contains(move)а не move.type == MoveType.ATTACK, хоча використання EnumSets, ймовірно, буде трохи повільніше, ніж прямі рівні перевірки.


У випадку, коли вдалий блок призводить до лічильника, його можна замінити if (one.height == two.height) return LandedHit.NEITHER;на

if (one.height == two.height) {
    // Successful block results in a counter against the attacker
    if (one.isAttack()) return LandedHit.PLAYER_TWO;
    return LandedHit.PLAYER_ONE;
}

Крім того, заміна деяких ifвисловлювань з використанням термінального оператора ( boolean_expression ? result_if_true : result_if_false) може зробити код більш компактним (наприклад, код у попередньому блоці стане return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;), але це може призвести до більш важкого для читання oneliners, щоб я не хотів ' t рекомендувати його для більш складних розгалужень.


Я обов'язково вивчу це, але мій поточний код дозволяє мені використовувати значення int oneта twoбути повторно використаним у якості початкових точок на моєму spritesheet. Хоча для цього не потрібно багато додаткового коду.
TomFirth

2
@ TomFirth84 Існує EnumMapклас, який можна використовувати для зіставлення переліків на цілі зсуви (ви також можете використовувати порядкові значення членів enum, наприклад, Move.ATTACK_HIGH.ordinal()було б 0, Move.ATTACK_LOW.ordinal()було б 1і т. Д., Але це більш крихко / менш гнучко, ніж явно асоціювання кожного члена зі значенням, оскільки додавання значень перерахунків між існуючими може скинути кількість рахунків, що не було б у випадку EnumMap.)
JAB

7
Це найбільш читабельне рішення, оскільки воно переводить код на щось, що має значення для людини, яка читає код.
Девід Стенлі

Ваш код, принаймні той, що використовує переписки, неправильний. Згідно з тим, що якщо заяви в ОП, успішний блок призводить до удару нападника. Але +1 для значущого коду.
Теймір

2
Ви можете навіть додати attack(against)метод до Moveперерахунку, повертаючи HIT, коли рух є успішною атакою, BACKFIRE, коли рух є блокованою атакою, і НІЩО, коли це не атака. Таким чином, ви можете реалізувати його в цілому ( public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; }), а також змінити його на конкретні кроки, коли це необхідно (слабкі рухи, які може блокувати будь-який блок, атаки, які ніколи не дають зворотної події тощо)
переписано

50

Чому б не використовувати масив?

Почну з самого початку. Я бачу закономірність, значення переходить від 0 до 3, і ви хочете отримати всі можливі значення. Це ваш стіл:

0 & 0 = 0
0 & 1 = 0
0 & 2 = 1
0 & 3 = 2
1 & 0 = 0
1 & 1 = 0
1 & 2 = 2
1 & 3 = 1
2 & 0 = 2
2 & 1 = 1
2 & 2 = 3
2 & 3 = 3
3 & 0 = 2
3 & 1 = 1
3 & 2 = 3
3 & 3 = 3

коли ми дивимось на цю ж бінарну таблицю, ми бачимо такі результати:

00 & 00 = 00
00 & 01 = 00
00 & 10 = 01
00 & 11 = 10
01 & 00 = 00
01 & 01 = 00
01 & 10 = 10
01 & 11 = 01
10 & 00 = 10
10 & 01 = 01
10 & 10 = 11
10 & 11 = 11
11 & 00 = 10
11 & 01 = 01
11 & 10 = 11
11 & 11 = 11

Тепер, можливо, ви вже бачите деякий візерунок, але коли я поєдную значення одне і два, я бачу, що ви використовуєте всі значення 0000, 0001, 0010, ..... 1110 і 1111. Тепер давайте поєднаємо значення одне і два, щоб зробити єдине 4 бітове ціле число.

0000 = 00
0001 = 00
0010 = 01
0011 = 10
0100 = 00
0101 = 00
0110 = 10
0111 = 01
1000 = 10
1001 = 01
1010 = 11
1011 = 11
1100 = 10
1101 = 01
1110 = 11
1111 = 11

Коли ми переводимо це назад у десяткові значення, ми бачимо дуже можливий масив значень, де один та два комбіновані можуть використовуватися як індекс:

0 = 0
1 = 0
2 = 1
3 = 2
4 = 0
5 = 0
6 = 2
7 = 1
8 = 2
9 = 1
10 = 3
11 = 3
12 = 2
13 = 1
14 = 3
15 = 3

Масив - це тоді {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3}, де його індекс просто один і два разом.

Я не Java-програміст, але ви можете позбутися всіх, якщо заяви, і просто записати це як щось подібне:

int[] myIntArray = {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3};
result = myIntArray[one * 4 + two]; 

Я не знаю, чи швидше переміщення на 2 швидше, ніж множення. Але варто спробувати.


2
Зсув бітів на 2 майже напевно швидше, ніж множення на 4. У кращому випадку, множення на 4, визнало б, що 4 є 2 ^ 2, і зробить сам зсув (перекладений компілятором потенційно). Відверто кажучи, для мене зрушення читабельніше.
Cruncher

Мені подобається ваш підхід! Він по суті розгладжує матрицю 4x4 у масив із 16 елементів.
Камерон Тінкер

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

24

Для цього використовується трохи бітмагічного (ви вже робите це, тримаючи два біти інформації (низький / високий & атака / блок) в одному цілому цілому):

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

public int fightMath(int one, int two) {
    if(one<2 && two<2){ //both players blocking
        return 0; // nobody hits
    }else if(one>1 && two>1){ //both players attacking
        return 3; // both hit
    }else{ // some of them attack, other one blocks
        int different_height = (one ^ two) & 1; // is 0 if they are both going for the same height - i.e. blocker wins, and 1 if height is different, thus attacker wins
        int attacker = one>1?1:0; // is 1 if one is the attacker, two is the blocker, and 0 if one is the blocker, two is the attacker
        return (attacker ^ different_height) + 1;
    }
}

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


2
Я погоджуюся з цим рішенням, схоже на те, що я мав на увазі в коментарі до головного питання. Я вважаю за краще розділити його на окремі змінні, що полегшить додавання середньої атаки, наприклад, у майбутньому.
Йо

2
Я просто виправив деякі помилки в коді вище після тестування, тепер він працює добре. Йдучи далі по біт-маніпулятору, я також придумав рішення в одному рядку, яке все ще не так містично, як бітмаска в інших відповідях, але все ще досить складне, щоб накрутити свій розум:return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
elias

1
Це найкраща відповідь, оскільки будь-який новий програміст, читаючи його, насправді зрозуміє магію, що відбувається за всіма цими магічними числами.
bezmax

20

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

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

else if(one == 3 && two == 3) { result = 3; }

Тож замість ...

if(one == 0 && two == 0) { result = 0; }
else if(one == 0 && two == 1) { result = 0; }
else if(one == 0 && two == 2) { result = 1; }
else if(one == 0 && two == 3) { result = 2; }

Ви б зробили ...

if(one == 0) 
{ 
    if(two == 0) { result = 0; }
    else if(two == 1) { result = 0; }
    else if(two == 2) { result = 1; }
    else if(two == 3) { result = 2; }
}

І просто переформатуйте його, як хочете.

Це не робить код краще виглядати, але, можливо, я його трохи прискорює.


3
Не знаю, чи це дійсно хороша практика, але для цього випадку, мабуть, я б використовував вкладені заяви переключення. Це займе більше місця, але це було б дійсно зрозуміло.
барвники

Це також спрацювало б, але я думаю, це питання переваги. Я дійсно віддаю перевагу, якщо заяви, як це практично говорить, що робить код. Не висуваючи своєї думки, звичайно, все, що працює для вас :). Проте подайте заявку на альтернативну пропозицію!
Джо Харпер

12

Подивимося, що ми знаємо

1: ваші відповіді симетричні для P1 (гравець один) та P2 (гравець два). Це має сенс для бойової гри, але також є те, чим ви можете скористатися, щоб покращити свою логіку.

2: 3 удари 0 ударів 2 удари 1 удар 3. Єдині випадки, які не охоплені цими випадками, - це комбінації 0 проти 1 і 2 проти 3. Іншим чином, унікальна таблиця перемог виглядає так: 0 ударів 2, 1 удар 3, 2 удари 1, 3 удари 0.

3: Якщо 0/1 йде вгору один проти одного, то відбувається нічия нічия, але якщо 2/3 піднімаються проти кожного, то обидва удари

Спочатку давайте побудуємо односторонню функцію, яка говорить нам, чи ми перемогли:

// returns whether we beat our opponent
public boolean doesBeat(int attacker, int defender) {
  int[] beats = {2, 3, 1, 0};
  return defender == beats[attacker];
}

Потім ми можемо використовувати цю функцію для складання кінцевого результату:

// returns the overall fight result
// bit 0 = one hits
// bit 1 = two hits
public int fightMath(int one, int two)
{
  // Check to see whether either has an outright winning combo
  if (doesBeat(one, two))
    return 1;

  if (doesBeat(two, one))
    return 2;

  // If both have 0/1 then its hitless draw but if both have 2/3 then they both hit.
  // We can check this by seeing whether the second bit is set and we need only check
  // one's value as combinations where they don't both have 0/1 or 2/3 have already
  // been dealt with 
  return (one & 2) ? 3 : 0;
}

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

(Минуло деякий час, оскільки я зробив будь-яку Java, тому вибачте, якщо синтаксис вимкнений, сподіваємось, він все ще зрозумілий, якщо я зрозумів його трохи неправильно)

До речі, 0-3 явно щось означають ; вони не є довільними значеннями, тому це допоможе назвати їх.


11

Сподіваюся, я правильно зрозумів логіку. Як щодо чогось такого:

public int fightMath (int one, int two)
{
    int oneHit = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : 0;
    int twoHit = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : 0;

    return oneHit+twoHit;
}

Перевірка високого удару або низького удару не заблокована, і однаково для гравця два.

Редагувати: Алгоритм не був повністю зрозумілий, "потрапляння" призначається при блокуванні, яке я не усвідомлював (Thx elias):

public int fightMath (int one, int two)
{
    int oneAttack = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : (one >= 2) ? 2 : 0;
    int twoAttack = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : (two >= 2) ? 1 : 0;

    return oneAttack | twoAttack;
}

Спосіб пошуку відмінного результату з малюнком!
Чад

1
Мені подобається підхід, але я боюся, що в цьому рішенні повністю бракує можливості удару, блокуючи атаку (наприклад, якщо один = 0 і два = 2, він повертає 0, але 1 очікується відповідно до специфікації). Можливо, ви можете попрацювати над цим, щоб виправити це, але я не впевнений, що отриманий код буде все ще таким елегантним, оскільки це означає, що рядки будуть рости дещо довше.
elias

Не усвідомлював, що за удар потрапив "удар". Дякуємо, що вказали на це. Коригується дуже простим виправленням.
Кріс

10

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

Я б пішов з простим перемикачем. Для цього вам знадобиться оцінка одного номера. Однак у цьому випадку, оскільки 0 <= one < 4 <= 9і 0 <= two < 4 <= 9, ми можемо перетворити обидва int у простий int шляхом множення oneна 10 та додавання two. Потім використовуйте перемикач в отриманому числі таким чином:

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 10
    int evaluate = one * 10 + two;

    switch(evaluate) {
        // I'd consider a comment in each line here and in the original code
        // for clarity
        case 0: result = 0; break;
        case 1: result = 0; break;
        case 1: result = 0; break;
        case 2: result = 1; break;
        case 3: result = 2; break;
        case 10: result = 0; break;
        case 11: result = 0; break;
        case 12: result = 2; break;
        case 13: result = 1; break;
        case 20: result = 2; break;
        case 21: result = 1; break;
        case 22: result = 3; break;
        case 23: result = 3; break;
        case 30: result = 1; break;
        case 31: result = 2; break;
        case 32: result = 3; break;
        case 33: result = 3; break;
    }

    return result;
}

Є ще один короткий метод, який я просто хочу зазначити як теоретичний код. Однак я б не використовував його, оскільки він має додаткову складність, з якою ти зазвичай не хочеш мати справу. Додаткова складність походить від основи 4 , оскільки підрахунок дорівнює 0, 1, 2, 3, 10, 11, 12, 13, 20, ...

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 4
    int evaluate = one * 4 + two;

    allresults = new int[] { 0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 1, 2, 3, 3 };

    return allresults[evaluate];
}

Дійсно, лише додаткова примітка, якщо я щось не пропускаю з Java. У PHP я би зробив:

function fightMath($one, $two) {
    // Convert one and two to a single variable in base 4
    $evaluate = $one * 10 + $two;

    $allresults = array(
         0 => 0,  1 => 0,  2 => 1,  3 => 2,
        10 => 0, 11 => 0, 12 => 2, 13 => 1,
        20 => 2, 21 => 1, 22 => 3, 23 => 3,
        30 => 1, 31 => 2, 32 => 3, 33 => 3 );

    return $allresults[$evaluate];
}

У Java немає ламди до 8-ї версії
Кирило Гамазков

1
Це. Для такої невеликої кількості входів я б використовував перемикач із складеним значенням (хоча він може бути більш читабельним із множником, більшим за 10, наприклад 100 чи 1000).
Медінок

7

Оскільки ви віддаєте перевагу вкладеним ifумовам, ось ще один спосіб.
Зауважте, що він не використовує resultчлен і він не змінює жодного стану.

public int fightMath(int one, int two) {
    if (one == 0) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 1; }
      if (two == 3) { return 2; }
    }   
    if (one == 1) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 2; }
      if (two == 3) { return 1; }
    }
    if (one == 2) {
      if (two == 0) { return 2; }
      if (two == 1) { return 1; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    if (one == 3) {
      if (two == 0) { return 1; }
      if (two == 1) { return 2; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    return DEFAULT_RESULT;
}

чому ти не маєш інших?
FDinoff

3
@FDinoff Я міг би використовувати elseланцюги, але це не мало значення.
Нік Дандулакіс

1
Я знаю, що це тривіально, але чи не додавання інших ланцюжків виконується швидше? у 3 з 4 випадків? Я завжди маю звичку писати код якнайшвидше, навіть якщо це лише кілька циклів.
Брендон Беарден

2
@BrandonBearden тут вони не хочуть нічого змінювати (якщо вважати, що вхід завжди знаходиться в межах 0..3). Компілятор, швидше за все, мікрооптимізує код. Якщо у нас є довгі серії else ifзаяв, ми можемо прискорити код за допомогою switchтаблиць або пошуку.
Нік Дандолакіс

Як це так? Якщо one==0він запустить код, тоді він повинен буде перевірити, якщо one==1тоді, one==2і, нарешті, якщо one==3- Якщо б там було інше, він би не робив останні три перевірки, оскільки він би вийшов із заяви після першого матчу. І так, ви можете додатково оптимізувати, використовуючи оператор переключення замість if (one...операторів, а потім додатково використовуючи інший комутатор всередині корпусу one's. Однак це не моє питання.
Брендон Беарден

6

Спробуйте це з корпусом вимикача...

Подивіться тут або тут, щоб отримати докладнішу інформацію про це

switch (expression)
{ 
  case constant:
        statements;
        break;
  [ case constant-2:
        statements;
        break;  ] ...
  [ default:
        statements;
        break;  ] ...
}

Ви можете додати до нього кілька умов (не одночасно) і навіть мати параметр за замовчуванням, коли жодних інших випадків не було задоволено.

PS: Тільки за умови виконання однієї умови.

Якщо дві умови виникають одночасно .. Я не думаю, що перемикач можна використовувати. Але ви можете зменшити свій код тут.

Заява Java перемикання декількох випадків


6

Перше, що мені прийшло в голову - це по суті та сама відповідь, яку дав Франциско Презенсія, але дещо оптимізована:

public int fightMath(int one, int two)
{
    switch (one*10 + two)
    {
    case  0:
    case  1:
    case 10:
    case 11:
        return 0;
    case  2:
    case 13:
    case 21:
    case 30:
        return 1;
    case  3:
    case 12:
    case 20:
    case 31:
        return 2;
    case 22:
    case 23:
    case 32:
    case 33:
        return 3;
    }
}

Ви можете додатково оптимізувати його, зробивши останній випадок (для 3) за замовчуванням:

    //case 22:
    //case 23:
    //case 32:
    //case 33:
    default:
        return 3;

Перевага цього методу полягає в тому, що легше зрозуміти, для яких значень oneі twoвідповідати цим повернутим значенням, ніж деякі інші запропоновані методи.


Це варіація іншої моєї відповіді тут .
David R Tribble

6
((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2

4
Скільки часу вам знадобилося досягти цього?
mbatchkarov

2
@mbatchkarov Близько 10 хвилин читання інших відповідей, а потім 10 хвилин відбивання олівцем та папером.
Давуд ібн Карім

7
Мені було б дуже сумно, якби мені довелося це підтримувати.
Мерейові

гмммм ... Є помилка: вас не вистачає; --unicorns
Альберто

Я погоджуюся з @Meryovi, реквізит для того, щоб бути лаконічним, але жахливим, як код APL
Ед Грібель

4

Ви можете використовувати корпус комутатора замість муфеляif

Також слід зазначити, що оскільки у вас є дві змінні, то вам доведеться об'єднати дві змінні, щоб використовувати їх в комутаторі

Перевірте цей оператор перемикача Java на обробку двох змінних?


3

Коли я малюю таблицю між одним / двома та результатом, я бачу один візерунок,

if(one<2 && two <2) result=0; return;

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

Сподіваюсь, це допомагає.


3

Хорошим моментом було б визначити правила як текст, ви можете легше вивести правильну формулу. Це витягнуто з приємного представлення масиву laalto:

{ 0, 0, 1, 2 },
{ 0, 0, 2, 1 },
{ 2, 1, 3, 3 },
{ 1, 2, 3, 3 }

І тут ми ідемо з деякими загальними коментарями, але ви повинні описати їх правилами:

if(one<2) // left half
{
    if(two<2) // upper left half
    {
        result = 0; //neither hits
    }
    else // lower left half
    {
        result = 1+(one+two)%2; //p2 hits if sum is even
    }
}
else // right half
{
    if(two<2) // upper right half
    {
        result = 1+(one+two+1)%2; //p1 hits if sum is even
    }
    else // lower right half
    {
        return 3; //both hit
    }
}

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

if((one<2)&&(two<2)) result = 0; //top left
else if((one>1)&&(two>1)) result = 3; //bottom right
else result = 1+(one+two+((one>1)?1:0))%2; //no idea what that means

Деякі пояснення щодо складних хітів p1 / p2 були б чудовими, виглядає цікаво!


3

Найкоротше і досі читабельне рішення:

static public int fightMath(int one, int two)
{
    if (one < 2 && two < 2) return 0;
    if (one > 1 && two > 1) return 3;
    int n = (one + two) % 2;
    return one < two ? 1 + n : 2 - n;
}

або навіть коротше:

static public int fightMath(int one, int two)
{
    if (one / 2 == two / 2) return (one / 2) * 3;
    return 1 + (one + two + one / 2) % 2;
}

Не містить жодних "магічних" номерів;) Сподіваюся, це допомагає.


2
Такі формули унеможливлять згодом змінити (оновити) результат комбінації. Єдиний спосіб - переробити всю формулу.
SNag

2
@SNag: Я згоден з цим. Найбільш гнучким рішенням є використання 2D масиву. Але автор цієї публікації хотів формули, і це найкраще, що ви можете отримати лише за допомогою простого ifs та математики.
PW


1

Мені особисто подобається каскад потрійних операторів:

int result = condition1
    ? result1
    : condition2
    ? result2
    : condition3
    ? result3
    : resultElse;

Але у вашому випадку ви можете використовувати:

final int[] result = new int[/*16*/] {
    0, 0, 1, 2,
    0, 0, 2, 1,
    2, 1, 3, 3,
    1, 2, 3, 3
};

public int fightMath(int one, int two) {
    return result[one*4 + two];
}

Або ви можете помітити візерунок у бітах:

one   two   result

section 1: higher bits are equals =>
both result bits are equals to that higher bits

00    00    00
00    01    00
01    00    00
01    01    00
10    10    11
10    11    11
11    10    11
11    11    11

section 2: higher bits are different =>
lower result bit is inverse of lower bit of 'two'
higher result bit is lower bit of 'two'

00    10    01
00    11    10
01    10    10
01    11    01
10    00    10
10    01    01
11    00    01
11    01    10

Отже, ви можете використовувати магію:

int fightMath(int one, int two) {
    int b1 = one & 2, b2 = two & 2;
    if (b1 == b2)
        return b1 | (b1 >> 1);

    b1 = two & 1;

    return (b1 << 1) | (~b1);
}

1

Ось досить коротка версія, схожа на відповідь JAB . Тут використовується карта для зберігання, яка перемагає тріумф над іншими.

public enum Result {
  P1Win, P2Win, BothWin, NeitherWin;
}

public enum Move {
  BLOCK_HIGH, BLOCK_LOW, ATTACK_HIGH, ATTACK_LOW;

  static final Map<Move, List<Move>> beats = new EnumMap<Move, List<Move>>(
      Move.class);

  static {
    beats.put(BLOCK_HIGH, new ArrayList<Move>());
    beats.put(BLOCK_LOW, new ArrayList<Move>());
    beats.put(ATTACK_HIGH, Arrays.asList(ATTACK_LOW, BLOCK_LOW));
    beats.put(ATTACK_LOW, Arrays.asList(ATTACK_HIGH, BLOCK_HIGH));
  }

  public static Result compare(Move p1Move, Move p2Move) {
    boolean p1Wins = beats.get(p1Move).contains(p2Move);
    boolean p2Wins = beats.get(p2Move).contains(p1Move);

    if (p1Wins) {
      return (p2Wins) ? Result.BothWin : Result.P1Win;
    }
    if (p2Wins) {
      return (p1Wins) ? Result.BothWin : Result.P2Win;
    }

    return Result.NeitherWin;
  }
} 

Приклад:

System.out.println(Move.compare(Move.ATTACK_HIGH, Move.BLOCK_LOW));

Друкує:

P1Win

Я б рекомендував static final Map<Move, List<Move>> beats = new java.util.EnumMap<>();замість цього, він повинен бути трохи ефективнішим.
JAB

@JAB Так, гарна ідея. Я завжди забуваю, що тип існує. І ... як незручно його сконструювати!
Дункан Джонс

1

Я б використовував Карту, або HashMap, або TreeMap

Особливо, якщо параметри не у формі 0 <= X < N

Як набір випадкових натуральних чисел ..

Код

public class MyMap
{
    private TreeMap<String,Integer> map;

    public MyMap ()
    {
        map = new TreeMap<String,Integer> ();
    }

    public void put (int key1, int key2, Integer value)
    {
        String key = (key1+":"+key2);

        map.put(key, new Integer(value));
    }

    public Integer get (int key1, int key2)
    {
        String key = (key1+":"+key2);

        return map.get(key);
    }
}

1

Завдяки @Joe Harper, як я закінчив, використовуючи варіацію його відповіді. Щоб зменшити його ще більше, оскільки 2 результати на 4 були однаковими, я зменшив його ще далі.

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

public int fightMath(int one, int two) {
  if (one === 0) {
    if (two === 2) { return 1; }
    else if(two === 3) { return 2; }
    else { return 0; }
  } else if (one === 1) {
    if (two === 2) { return 2; }
    else if (two === 3) { return 1; }
    else { return 0; }
  } else if (one === 2) {
    if (two === 0) { return 2; }
    else if (two === 1) { return 1; }
    else { return 3; }
  } else if (one === 3) {
    if (two === 0) { return 1; }
    else if (two === 1) { return 2; }
    else { return 3; }
  }
}

13
Це насправді менш читабельно, ніж оригінал, і не зменшує кількість if висловлювань ...
Чад,

@Chad Ідея цього полягала в тому, щоб збільшити швидкість процесу, і хоча це виглядає приголомшливо, він легко оновлюється, якщо в майбутньому я додаватиму більше дій. Говорячи про це, я зараз використовую попередню відповідь, яку я не повністю зрозумів раніше.
TomFirth

3
@ TomFirth84 Чи є причина, що ви не дотримуєтесь правильних правил кодування для своїх операторів if?
ylun.ca

@ylun: Я скоротив рядки перед тим, як вставити його на SO, не для читабельності, а для простого спаму. На цій сторінці є різні практики, і, на жаль, це я просто так, як я навчився і мені комфортно.
TomFirth

2
@ TomFirth84 Я не думаю, що це легко оновлюється, кількість рядків зростає щось на зразок добутку кількості дозволених значень.
Андрій Лазар

0
  1. Використовуйте константи або перерахунки, щоб зробити код читабельнішим
  2. Спробуйте розділити код на більше функцій
  3. Спробуйте використати симетрію проблеми

Ось пропозиція, як це могло б виглядати, але використання ints тут все-таки некрасиво:

static final int BLOCK_HIGH = 0;
static final int BLOCK_LOW = 1;
static final int ATTACK_HIGH = 2;
static final int ATTACK_LOW = 3;

public static int fightMath(int one, int two) {
    boolean player1Wins = handleAttack(one, two);
    boolean player2Wins = handleAttack(two, one);
    return encodeResult(player1Wins, player2Wins); 
}



private static boolean handleAttack(int one, int two) {
     return one == ATTACK_HIGH && two != BLOCK_HIGH
        || one == ATTACK_LOW && two != BLOCK_LOW
        || one == BLOCK_HIGH && two == ATTACK_HIGH
        || one == BLOCK_LOW && two == ATTACK_LOW;

}

private static int encodeResult(boolean player1Wins, boolean player2Wins) {
    return (player1Wins ? 1 : 0) + (player2Wins ? 2 : 0);
}

Було б приємніше використовувати структурований тип для введення та виводу. Вхід фактично має два поля: позицію та тип (блок чи атака). Вихід також має два поля: player1Wins та player2Wins. Кодування цього цілого цілого числа ускладнює читання коду.

class PlayerMove {
    PlayerMovePosition pos;
    PlayerMoveType type;
}

enum PlayerMovePosition {
    HIGH,LOW
}

enum PlayerMoveType {
    BLOCK,ATTACK
}

class AttackResult {
    boolean player1Wins;
    boolean player2Wins;

    public AttackResult(boolean player1Wins, boolean player2Wins) {
        this.player1Wins = player1Wins;
        this.player2Wins = player2Wins;
    }
}

AttackResult fightMath(PlayerMove a, PlayerMove b) {
    return new AttackResult(isWinningMove(a, b), isWinningMove(b, a));
}

boolean isWinningMove(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.ATTACK && !successfulBlock(b, a)
            || successfulBlock(a, b);
}

boolean successfulBlock(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.BLOCK 
            && b.type == PlayerMoveType.ATTACK 
            && a.pos == b.pos;
}

На жаль, Java не дуже добре виражає такі типи даних.


-2

Замість цього зробіть щось подібне

   public int fightMath(int one, int two) {
    return Calculate(one,two)

    }


    private int Calculate(int one,int two){

    if (one==0){
        if(two==0){
     //return value}
    }else if (one==1){
   // return value as per condtiion
    }

    }

4
Ви тільки що створили приватну функцію, яку охоплює загальнодоступна. Чому б просто не реалізувати це на публічній функції?
Мартін Удінг

5
І ви не зменшили кількість тверджень if.
Чад
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.