53 votes

Est-il possible de simplifier une instruction if qui recherche une combinaison?

Je suis actuellement en train de travailler sur l'ajout d'effets sonores pour un jeu, et bien que mon code fonctionne très bien, je suis à la recherche d'un moyen de le simplifier. Fondamentalement, chaque objet dans le jeu a une valeur de chaîne indiquant son matériel (ie. "bois", "métal", etc.), et lorsque deux objets entrent en collision, un effet sonore est joué basée sur la combinaison. Le code est essentiellement ressemble à ceci:

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

Mais je me demandais si il existe un moyen de simplifier le if à quelque chose comme ceci:

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

56voto

mehrdad safa Points 987

Vous devez utiliser un enum pour les matériaux à la place de la chaîne et vous pouvez utiliser un Dictionary pour tenir correspondant combinaisons de sons. Vous pouvez sauter plusieurs if états et sélectionnez l'objet correspondant pour chaque matériau automatiquement à l'aide de l' Dictionary. Par exemple:

[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();  

Avantages de Performance:

Vous pouvez également améliorer les performances par l'utilisation de cette approche. car certainement comparaison des entiers de valeurs Enum ou des codes de hachage serait plus facile et plus rapide que la comparaison de chaînes de caractères. Et à propos de dictionnaire VS plusieurs if-else des déclarations, des séries de if/else if états exécute de façon linéaire; de sorte que son rendement très dépend du nombre de si les déclarations et comparateur d'égalité de l'objet; tout en Dictionary est basé sur une table de hachage. Il utilise un index-collection optimisée pour stocker des valeurs, ce qui a fait de la constante de temps d'accès. Cela signifie souvent, il n'est pas question de la façon dont beaucoup de touches sont dans le dictionnaire, vous aurez accès à valeurs dans un temps constant et dans la plupart des scénarios, il est très rapide de plusieurs instructions if.

Comparaison des performances:

Nous allons comparer les performances des deux approche dans cet exemple:

//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;  

} 

résultat:

if des énoncés à 26 éléments| dictionnaire avec 122 articles.
593 254
256 579
252 572
570 246
587 248
574 291
576 246
685 265
599 282
723 338

ce qui indique dictionnaire est plus de 2 fois plus rapide que l' if/else if des déclarations.

13voto

weston Points 11882

L'approche normale, lorsque vous trouvez la répétition du code consiste à extraire une méthode:

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

IsWoodAndMetal est défini comme:

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

Ce sera aussi rapide que le code d'origine contrairement à tous les linq/liste et de concaténation de chaînes de solutions allouer de la mémoire qui est de mauvaises nouvelles pour une fréquente boucle de jeu, car il provoque de plus en plus fréquentes et/ou plus de la collecte des poubelles.

On peut aller plus loin si l' || encore vous dérange, extrait:

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

Il se lit maintenant:

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

Et j'avais encore de la fantaisie la performance de que sur les autres solutions (à part le dictionnaire de la solution lorsque vous avez un peu d'entrées).

9voto

phil13131 Points 1371

Il pourrait ne pas être des solutions les plus modernes, mais à l'aide de nombres premiers comme des références à votre matériel pourrait augmenter vos performances. Je sais et je comprends que le terme "optimisation de l'avant, il est nécessaire" est ce que la plupart des programmeurs ne recommandons, toutefois, dans ce contexte, je pense qu'il ne fait de loin pas l'augmentation de la complexité du code, mais augmente les performances de ce (trivial) de la tâche.

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
}

Cette approche est indépendante de la commande des matériaux (multiplication commutative) et n'a pas besoin d'une longue comparaison de chaînes ou toutes les structures plus complexes que des entiers.

En supposant que vous voulez garder tous les numéros de référence entiers de 4 octets, et la racine carrée de la plus grand entier de 4 octets est autour de 65535, qui vous quitterait avec autour de 6550 possible nombres premiers ci-dessous 65535, tels que les produits ne serait provoquer un débordement d'entier. Qui devrait d'ici loin d'être suffisant pour n'importe quel jeu commun.

7voto

Aleksandar Points 2199

Vous devriez changer le mat{A,B} de type enum. Qui serait défini comme suit:

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

Ensuite, le code ressemblera à ceci:

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

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

Le seul problème ici est que matA, peuvent maintenant être de type Bois et le Métal en même temps, mais ce problème est aussi présent dans la chaîne de solution.

--- EDIT ---

Il est également possible de créer de l'enum alias pour le bois et le métal

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

Ensuite, le code ressemblera à ceci:

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

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

6voto

BenM Points 210

Je me sens obligé de publier ce que je considère comme la solution la plus «évidente», que personne ne semble avoir encore publiée. Si la réponse acceptée fonctionne pour vous, allez avec celle-là. J'ajoute simplement ceci pour être complet.

Premièrement, définissez une méthode d'assistance statique qui effectue la comparaison dans les deux sens:

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

Utilisez ensuite cela dans vos déclarations 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.
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X