Подтвердить что ты не робот

Можно ли упростить if-инструкцию, которая проверяет комбинацию?

В настоящее время я работаю над добавлением звуковых эффектов в игру, и хотя мой текущий код работает нормально, я ищу способ упростить его. В принципе, каждый объект в игре имеет строковое значение, указывающее его материал (например, "дерево", "металл" и т.д.), И когда сталкиваются два объекта, звуковой эффект воспроизводится на основе комбинации. Код по существу выглядит следующим образом:

if( (matA == "metal" && matB == "wood") || (matA == "wood" && matB == "metal") )
{
    //play sound for metal-wood collision
}

Но мне интересно, есть ли способ упростить if-инструкцию примерно так:

if( one of the materials is wood && one of the materials is metal )
{
    //play sound for metal-wood collision
}
4b9b3361

Ответ 1

Вы должны использовать enum для материалов вместо строки, и вы можете использовать Dictionary для хранения соответствующих звуковых комбинаций. Вы можете пропускать несколько операторов if и автоматически выбирать соответствующий объект для каждого материала с помощью Dictionary. Например:

[Flags]
enum Material  
    {  
        Wood=1,
        Iron=2,
        Glass=4
        //...  
    }  
Dictionary<Material,SoundObject> sounds = new Dictionary<Material,SoundObject>();  
sounds.add(Material.Wood,woodSound);  
sounds.add(Material.Iron,ironSound);  
sounds.add(Material.Wood | Material.Iron,woodAndIronSound);

// And play corresponding sound directly without any if statement.  
sounds[object.Material].Play();  
sounds[matA | matB].Play();  

Преимущества производительности:

Вы также улучшите производительность, используя этот подход. потому что определенно целочисленное сравнение значений Enum или хеш-кодов было бы проще и быстрее, чем сравнение строк. И о словарях VS multiple if-else операторов, ряд операторов if/else if выполняется линейно; поэтому его производительность очень зависит от количества операторов if и равномерного сравнения объекта; а Dictionary - на основе Hashtable. Он использует оптимизированную по индексу коллекцию для хранения значений, которая имеет фактически постоянное время доступа. Это означает, что часто неважно, сколько ключей находится в словаре, вы будете получать доступ к значениям в постоянное время, а в большинстве сценариев - очень быстро, чем несколько операторов if.

Сравнение производительности:

В этом примере мы сравним производительность двух подходов:

//If you want to try, just copy the code and see the result.  
static Dictionary<char, short> myHashTable = Enumerable.Range((short)'A', (short)'z').ToDictionary((ch) => (char)ch, (sh) => (short)sh);  

static void Main(string[] args)  
{  
    System.Diagnostics.Stopwatch SW = new   System.Diagnostics.Stopwatch();  
    short temp = 0;  
    SW.Start();  
    for(int i=0;i<10000000;i++)  
    temp = getValue('z');  
    SW.Stop();  
    Console.WriteLine(SW.ElapsedMilliseconds );  
    SW.Reset();              
    SW.Start();  
    for(int i =0;i<10000000;i++)  
    temp = myHashTable['a'];  
    SW.Stop();  
    Console.WriteLine(SW.ElapsedMilliseconds);  
}  
static short getValue(char input)  
{  
    if (input == 'a')  
        return (short)'a';  
    else if (input == 'b')  
        return (short)'b';  
    else if (input == 'c')  
        return (short)'c';  
    else if (input == 'd')  
        return (short)'d';  
    else if (input == 'e')  
        return (short)'e';  
    else if (input == 'f')  
        return (short)'f';  
    else if (input == 'g')  
        return (short)'g';  
    else if (input == 'h')  
        return (short)'h';  
    else if (input == 'i')  
        return (short)'i';  
    else if (input == 'j')  
        return (short)'j';  
    else if (input == 'k')  
        return (short)'k';  
    else if (input == 'l')  
        return (short)'l';  
    else if (input == 'm')  
        return (short)'m';  
    else if (input == 'n')  
        return (short)'n';  
    else if (input == 'o')  
        return (short)'o';  
    else if (input == 'p')  
        return (short)'p';  
    else if (input == 'q')  
        return (short)'q';  
    else if (input == 'r')  
        return (short)'r';  
    else if (input == 's')  
        return (short)'s';  
    else if (input == 't')  
        return (short)'t';  
    else if (input == 'u')  
        return (short)'u';  
    else if (input == 'v')  
        return (short)'v';  
    else if (input == 'w')  
        return (short)'w';  
    else if (input == 'x')  
        return (short)'x';  
    else if (input == 'y')  
        return (short)'y';  
    else if (input == 'z')  
        return (short)'z';  
    return 0;  

} 

Результат:

if с 26 элементами | словарь с 122 элементами.
593 254
579 256
572 252
570 246
587 248
574 291
576 246
685 265
599 282
723 338

который указывает, что словарь более чем в 2 раза быстрее, чем if/else if.

Ответ 2

Обычный подход, когда вы оказываетесь повторяющийся код, заключается в извлечении метода:

if (IsWoodAndMetal(matA, matB) || IsWoodAndMetal(matB, matA))
{
    // play sound for metal-wood collision
}

Где IsWoodAndMetal определяется как:

public static bool IsWoodAndMetal(string matA, string matB)
{
    return matA == "wood" && matB == "metal";
}

Это будет так же быстро, как исходный код, в отличие от всех решений linq/list и конкатенации строк, которые выделяют память плохая новость для частых игровых циклов, поскольку она вызывает более частые и/или более длинные сбор мусора.

Мы можем пойти дальше, если || все еще беспокоит вас, извлекая:

public static bool EitherParameterOrder<T>(Func<T, T, bool> func, T a, T b)
{
    return func(a, b) || func(b, a);
}

Теперь он читает:

if (EitherParameterOrder(IsWoodAndMetal, matA, matB))
{
    // play sound for metal-wood collision
}

И я по-прежнему представляю производительность этого решения по сравнению с другими решениями (помимо словарного решения, когда у вас есть несколько записей).

Ответ 3

Это может быть не самое современное решение, но использование простых чисел в качестве ссылок на ваши материалы может повысить вашу производительность. Я знаю и понимаю, что "оптимизация до того, как это необходимо" - это то, что многие программисты не рекомендуют, однако в этом контексте я думаю, что это далеко не увеличивает сложность кода, но увеличивает производительность этой (довольно тривиальной) задачи.

public static class Materials
{
   public static uint Wood = 2,
   public static uint Metal = 3,
   public static uint Dirt = 5,
   // etc...
}

if(matA*matB == Materials.Wood*Materials.Metal)
{
   //play sound for metal-wood collision
}

//or with enums but annoying casts are necessary...

enum Materials:uint
{
   Wood = 2,
   Metal = 3,
   Dirt = 5,
   // etc...
}

if((uint)matA*(uint)matB == (uint)Materials.Wood*(uint)Materials.Metal)
{
   //play sound for metal-wood collision
}

Этот подход не зависит от порядка материалов (коммутативное умножение) и не требует какого-либо длительного сравнения строк или более сложных структур, чем целых чисел.

Предполагая, что вы хотите, чтобы все ссылочные номера состояли из 4-байтовых целых чисел, а квадратный корень из наибольшего 4-байтового целого составлял около 65535, это оставило бы вам около 6550 возможных простых чисел ниже 65535, так что ни один продукт не будет вызывают целочисленное переполнение. Этого должно быть достаточно для любой общей игры.

Ответ 4

Вы должны изменить тип mat {A, B} для перечисления. Это будет определено следующим образом:

[Flags]
enum Materials {
    Wood = 1,
    Metal = 2,
    Concrete = 4,
    // etc ...
}

Тогда код будет выглядеть так:

Meterials matA = Materials.Wood;
Meterials matB = Materials.Metal;

if ((matA | matB) == (Materials.Metal | Materials.Wood))
{
    // Do something
}

Единственная проблема здесь в том, что matA теперь может быть типа Wood и Metal одновременно, но эта проблема также присутствовала в строчном решении.

--- EDIT ---

Также возможно создать псевдоним enum для дерева и металла

[Flags]
enum Materials
{
    Wood = 1,
    Metal = 2,
    WoodMetal = Wood | Metal,
    Concrete = 4,
    // etc
}

Тогда код будет выглядеть так:

Materials matA = Materials.Wood;
Materials matB = Materials.Metal;

if ((matA | matB) == Materials.WoodMetal)
{
    // Do something
}

Ответ 5

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

Сначала определите статический вспомогательный метод, который выполняет сравнение в обоих направлениях:

public static bool MaterialsMatch(string candidate1, string candidate2, 
                                   string expected1, string expected2)
{
    return (candidate1 == expected1 && candidate2 == expected2) || 
           (candidate1 == expected2 && candidate2 == expected1);
}

Затем используйте это в своих операторах if:

if (MaterialsMatch(matA, matB, "wood", "metal"))
{
    // play sound for wood-metal collision
}
else if (MaterialsMatch(matA, matB, "wood", "rubber"))
{
    // play sound for wood-rubber collision
}
else if (MaterialsMatch(matA, matB, "metal", "rubber"))
{
    // play sound for metal-rubber collision
}
// etc.