Слишком много утверждений «если»?

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;
}
TomFirth
источник
1
@waqaslam: - Это может помочь оператору переключения Java обрабатывать две переменные?
Рахул Трипати
9
Конечно, здесь есть какая-то логика, которая может быть обобщена, а не грубой силой? Наверняка есть какая-то функция, f(a, b)которая дает ответ в общем случае? Вы не объяснили логику расчета, поэтому все ответы - просто помада на свинье. Я бы начал с серьезного переосмысления логики вашей программы, использование intфлагов для действий очень устарело. enumОни могут содержать логику и описательны, это позволит вам писать свой код более современным способом.
Борис Паук
После прочтения ответов @Steve Benett, представленных в его альтернативном вопросе, связанном выше, я могу предположить, что прямого ответа на этот вопрос нет, поскольку он по сути такой же, как база данных. В исходном вопросе я попытался объяснить, что я делаю простую игру (боец), и у пользователей есть выбор из 4 кнопок: blockHigh (0), blockLow (1), attackHigh (2) и attackLow (3). Эти числа хранятся в массиве, пока не понадобятся. Позже они используются функцией fightMath (), которая вызывает выбор playerOne против playerTwos для получения результата. Нет фактического обнаружения столкновений.
TomFirth
9
Если у вас есть ответ, пожалуйста, опубликуйте его как таковой. Трудно следить за подробным обсуждением в комментариях, особенно когда речь идет о коде. Если вы хотите поговорить о том, должен ли этот вопрос быть перенесен в Code Review, есть мета- обсуждение по этому поводу.
Джордж Стокер
1
Что вы подразумеваете под "так же, как база данных"? Если эти значения есть в базе данных, вытащите их оттуда. В противном случае, если это действительно так сложно, я бы оставил все как есть и добавил комментарии по бизнес-логике после каждой строки, чтобы люди понимали, что происходит. Это лучше (для меня) долго и четко - кто-то в будущем может понять, что происходит. Если вы помещаете это в карту или пытаетесь сохранить 8 строк кода, то потенциал действительно маленький, а размер больше: вы делаете его все более запутанным для того, кому нужно прочитать ваш код однажды.
Сказ

Ответы:

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];
laalto
источник
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 различных значения, также важно.

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

Константа M является кодированием карты m, где m (0) кодируется в битах M [0: 1], m (1) кодируется в битах M [2: 3], а m (n) кодируется в битах. М [п * 2: п * 2 + 1].

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

waTeim
источник
79
умный, но никто другой, читающий код, не сможет понять его.
Аран Малхолланд
106
Я думаю, что это очень плохая практика. Никто, кроме автора, не поймет этого. Вы хотите посмотреть на кусок кода и понять это быстро. Но это просто трата времени.
Balázs Németh
14
Я с @ BalázsMáriaNémeth об этом. Хотя это очень впечатляет, вы должны программировать на жестоких психопатов!
OrhanC1
90
Все downvoters думают, что это отвратительный запах кода. Все upvoters думают то же самое, но восхищаются умом позади этого. +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. ИМХО, тип enum в C # оставляет желать лучшего. Это не следует за философией успеха, которую делают остальные функции. Например, stackoverflow.com/a/847353/92414 Планирует ли команда c # создать новый тип enum (чтобы не нарушать существующий код), который разработан лучше?
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]; 
}
djm.im
источник
69

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

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

JAB
источник
Я определенно рассмотрю это, но мой текущий код позволяет мне использовать значение int oneи twoбыть повторно использованным в качестве начальных точек моей таблицы спрайтов. Хотя для этого не требуется много дополнительного кода.
TomFirth
2
@ TomFirth84 Существует EnumMapкласс, который вы можете использовать для отображения перечислений на ваши целочисленные смещения (вы также можете использовать порядковые значения членов перечисления напрямую, например, Move.ATTACK_HIGH.ordinal()было бы 0, Move.ATTACK_LOW.ordinal()будет 1и т. Д., Но это более хрупко / менее гибко, чем явно ассоциирование каждого члена со значением, так как добавление перечислимых значений между существующими приведет к сбросу счетчика, что было бы не так с EnumMap.)
JAB
7
Это наиболее читаемое решение, поскольку оно переводит код во что-то значимое для человека, читающего код.
Дэвид Стэнли
Ваш код, по крайней мере тот, который использует перечисления, неверен. Согласно операторам if в OP успешный блок приводит к попаданию в атакующего. Но +1 для значимого кода.
Taemyr
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, но вы можете избавиться от всех операторов if и просто записать их примерно так:

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

Я не знаю, если битовое смещение на 2 быстрее, чем умножение. Но это может стоить попробовать.

DJ Bazzie Wazzie
источник
2
Сдвиг битов 2 почти определенно быстрее, чем умножение на 4. В лучшем случае умножение на 4 признало бы, что 4 равно 2 ^ 2, и само делало бы сдвиг битов (возможно, переведенный компилятором). Честно говоря, для меня сдвиг более читабелен.
Cruncher
Мне нравится ваш подход! По сути, это сводит матрицу 4x4 в массив из 16 элементов.
Кэмерон Тинкер
6
В настоящее время, если я не ошибаюсь, компилятор, несомненно, распознает, что вы умножаете на степень два, и оптимизирует его соответствующим образом. Так что для вас, программиста, сдвиг битов и умножение должны иметь одинаковую производительность.
Таннер Светт
24

Здесь используется немного битмага (вы уже делаете это, удерживая два бита информации (низкий / высокий и атака / блок) в одном целом числе):

Я не запускал его, только напечатал здесь, пожалуйста, перепроверьте. Идея, безусловно, работает. РЕДАКТИРОВАТЬ: теперь проверяется для каждого входа, работает нормально.

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

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

Вы можете вкладывать if, поэтому потенциально для вашего последнего if-теста будет небольшое увеличение производительности, поскольку он не прошел бы столько же операторов if. Но в вашем контексте базового курса 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
Не знаю, действительно ли это хорошая практика, но для этого случая я бы, вероятно, использовал бы вложенные операторы switch. Это займет больше места, но это будет действительно ясно.
красители
Это также сработает, но я думаю, что это вопрос предпочтений. Я на самом деле предпочитаю операторы if, поскольку он фактически говорит о том, что делает код. Конечно, не опускать ваше мнение, все, что работает для вас :). Upvote для альтернативного предложения, хотя!
Джо Харпер
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 ожидается согласно спецификации). Может быть, вы можете поработать над этим, чтобы сделать это правильно, но я не уверен, что полученный код будет по-прежнему таким элегантным, поскольку это означает, что строки будут расти несколько длиннее.
Элиас
Не понял, что за «удар» был награжден за блок. Спасибо за указание на это. Скорректировано с очень простым исправлением.
Крис
10

У меня нет опыта работы с Java, поэтому могут быть некоторые опечатки. Пожалуйста, рассмотрите код как псевдокод.

Я бы пошел с простым переключателем. Для этого вам понадобится оценка одного номера. Однако для этого случая, поскольку 0 <= one < 4 <= 9и 0 <= two < 4 <= 9, мы можем преобразовать оба целых числа в простое 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];
}
Франциско Презенсия
источник
У Явы нет ламд до 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
Я знаю, что это тривиально, но разве добавление цепочек else не будет выполняться быстрее? в 3 из 4 случаев? У меня всегда есть привычка писать код, который будет выполняться максимально быстро, даже если это всего лишь несколько циклов.
Брэндон Бирден
2
@BrandonBearden здесь они не будут иметь никакого значения (при условии, что вход всегда находится в диапазоне 0..3). Компилятор, вероятно, все равно будет микрооптимизировать код. Если у нас есть длинные серии else ifутверждений, мы можем ускорить код с помощью switchили просмотра таблиц.
Ник Дандулакис
Как это так? Если one==0он выполнит код, то он должен будет проверить, если one==1тогда, если one==2и, наконец, если one==3- Если бы там было еще, если, там, он не будет делать последние три проверки, потому что он выйдет из оператора после первого соответствия. И да, вы могли бы дополнительно оптимизировать использование оператора switch вместо if (one...операторов, а затем дополнительно использовать другой переключатель в случае one's. Однако это не мой вопрос.
Брэндон Бирден
6

Попробуйте это с корпусом переключателя. ..

Посмотрите здесь или здесь для получения дополнительной информации об этом

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

Вы можете добавить к нему несколько условий (не одновременно) и даже иметь опцию по умолчанию, если другие случаи не были выполнены.

PS: только если одно условие должно быть выполнено ..

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

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

Дэвид Р. Триббл
источник
Это вариант другого моего ответа здесь .
Дэвид Р. Триббл
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

Также стоит упомянуть, что, поскольку у вас есть две переменные, вы должны объединить две переменные, чтобы использовать их в switch

Проверить этот оператор Java для обработки двух переменных?

Рахул Трипати
источник
3

Когда я рисую таблицу между одним / двумя и результатом, я вижу один шаблон,

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

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

Надеюсь это поможет.

AnonNihcas
источник
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;
}

Не содержит никаких «магических» чисел;) Надеюсь, это поможет.

PW
источник
2
Подобные формулы сделают невозможным изменение (обновление) результата комбинации на более позднем этапе. Единственным способом было бы переделать всю формулу.
SNag
2
@SNag: я согласен с этим. Наиболее гибким решением является использование 2D-массива. Но автору этого поста нужна формула, и это лучшая форма, которую вы можете получить, используя только простые математические операции.
PW
2

static int val(int i, int u){ int q = (i & 1) ^ (u & 1); return ((i >> 1) << (1 ^ q))|((u >> 1) << q); }

user1837841
источник
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);
    }
}
Khaled.K
источник
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; }
  }
}
TomFirth
источник
13
Это на самом деле менее читабельно, чем оригинал и не уменьшает количество операторов if ...
Чад
@Chad Идея заключалась в том, чтобы увеличить скорость процесса, и, хотя он выглядит ужасно, его легко обновить, если я добавлю больше действий в будущем. Сказав это, я сейчас использую предыдущий ответ, который я до конца не понимал.
TomFirth
3
@ TomFirth84 Есть ли причина, по которой вы не соблюдаете надлежащие правила кодирования для своих операторов if?
ylun.ca
@ylun: Я уменьшил количество строк перед тем, как вставить его в SO, не для удобства чтения, а для чистого спам-пространства. На этой странице есть варианты практики, и, к сожалению, это просто способ, которым я научился и с которым мне удобно.
TomFirth
2
@ TomFirth84 Я не думаю, что это легко обновляется, число строк растет как произведение количества допустимых значений.
Андрей Лазарь
0
  1. Используйте константы или перечисления, чтобы сделать код более читабельным
  2. Попробуйте разбить код на несколько функций
  3. Попробуйте использовать симметрию задачи

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

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.
Чад