Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enum case handling - better to use a switch or a dictionary?

When handling the values of an enum on a case by case basis, is it better to use a switch statement or a dictionary?

I would think that the dictionary would be faster. In terms of space, it takes up some in memory, but the case statement would also take up some memory just in the memory needed for the program itself. So bottom line I'm thinking it's always better to just use the dictionary.

Here are the two implementations side by side for comparison:

Given these enums:

enum FruitType
{
    Other,
    Apple,
    Banana,
    Mango,
    Orange
}
enum SpanishFruitType
{
    Otra,
    Manzana, // Apple
    Naranja, // Orange
    Platano, // Banana
    Pitaya // Dragon fruit, only grown in Mexico and South American countries, lets say
    // let's say they don't have mangos, because I don't remember the word for it.
}

Here is the way to do it with the switch statement:

private static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
{
    switch(typeOfFruit)
    {
        case FruitType.Apple:
            return SpanishFruitType.Manzana;
        case FruitType.Banana:
            return SpanishFruitType.Platano;
        case FruitType.Orange:
            return SpanishFruitType.Naranja;
        case FruitType.Mango:
        case FruitType.Other:
            return SpanishFruitType.Otra;
        default:
            throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
    }
}

And here is how it is done with a dictionary:

private static Dictionary<FruitType, SpanishFruitType> EnglishToSpanishFruit = new Dictionary<FruitType, SpanishFruitType>()
{
    {FruitType.Apple, SpanishFruitType.Manzana}
    ,{FruitType.Banana, SpanishFruitType.Platano}
    ,{FruitType.Mango, SpanishFruitType.Otra}
    ,{FruitType.Orange, SpanishFruitType.Naranja}
    ,{FruitType.Other, SpanishFruitType.Otra}
};
private static SpanishFruitType GetSpanishEquivalentWithDictionary(FruitType typeOfFruit)
{
    return EnglishToSpanishFruit[typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}

Not only does the dictionary have the speed boost, it also has less unnecessary strings in the code. Is this always better to use the dictionary then? Is there even a third better way?

Thanks in advance.

like image 864
user420667 Avatar asked Jul 12 '13 00:07

user420667


1 Answers

In fact, a dictionary is slower. Really. Just write simple benchmark (I've added example with converting dictionary to array):

void Main()
{
    for (int itFac = 0; itFac < 7; itFac++ ) {
        var iterations = 100;
        iterations *= (int)Math.Pow(10, itFac);

        Console.WriteLine("Iterations: {0}", iterations);

        {
            Random r = new Random();
            int maxFruits = 5;
            var timer = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++) {
                var res =  Fruits.GetSpanishEquivalentWithArray((Fruits.FruitType)r.Next(maxFruits));
            }
            Console.WriteLine("Array time: {0}", timer.Elapsed);
        }       

        {
            Random r = new Random();
            int maxFruits = 5;
            var timer = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++) {
                var res = Fruits.GetSpanishEquivalent((Fruits.FruitType)r.Next(maxFruits));
            }
            Console.WriteLine("Switch time    : {0}", timer.Elapsed);
        }

        {
            Random r = new Random();
            int maxFruits = 5;
            var timer = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++) {
                var res =  Fruits.GetSpanishEquivalentWithDictionary((Fruits.FruitType)r.Next(maxFruits));
            }
            Console.WriteLine("Dictionary time: {0}", timer.Elapsed);
        }

        Console.WriteLine();
    }
}

class Fruits {
    public enum FruitType
    {
        Other,
        Apple,
        Banana,
        Mango,
        Orange
    }
    public enum SpanishFruitType
    {
        Otra,
        Manzana, // Apple
        Naranja, // Orange
        Platano, // Banana
        // let's say they don't have mangos, because I don't remember the word for it.
    }

    public static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
    {
        switch(typeOfFruit)
        {
            case FruitType.Apple:
                return SpanishFruitType.Manzana;
            case FruitType.Banana:
                return SpanishFruitType.Platano;
            case FruitType.Orange:
                return SpanishFruitType.Naranja;
            case FruitType.Mango:
            case FruitType.Other:
                return SpanishFruitType.Otra;
            default:
                throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
        }
    }

    public static SpanishFruitType GetSpanishEquivalent(string typeOfFruit)
    {
        switch(typeOfFruit)
        {
            case "apple":
                return SpanishFruitType.Manzana;
            case "banana":
                return SpanishFruitType.Platano;
            case "orange":
                return SpanishFruitType.Naranja;
            case "mango":
            case "other":
                return SpanishFruitType.Otra;
            default:
                throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
        }
    }

    public static Dictionary<FruitType, SpanishFruitType> EnglishToSpanishFruit = new Dictionary<FruitType, SpanishFruitType>()
    {
        {FruitType.Apple, SpanishFruitType.Manzana}
        ,{FruitType.Banana, SpanishFruitType.Platano}
        ,{FruitType.Mango, SpanishFruitType.Otra}
        ,{FruitType.Orange, SpanishFruitType.Naranja}
        ,{FruitType.Other, SpanishFruitType.Otra}
    };

    public static SpanishFruitType GetSpanishEquivalentWithDictionary(FruitType typeOfFruit)
    {
        return EnglishToSpanishFruit[typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
    }

    public static SpanishFruitType[] EnglishToSpanishFruitArray;

    static Fruits() {
        EnglishToSpanishFruitArray = new SpanishFruitType[EnglishToSpanishFruit.Select(p => (int)p.Key).Max() + 1];
        foreach (var pair in EnglishToSpanishFruit)
            EnglishToSpanishFruitArray[(int)pair.Key] = pair.Value;
    }

    public static SpanishFruitType GetSpanishEquivalentWithArray(FruitType typeOfFruit)
    {
        return EnglishToSpanishFruitArray[(int)typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
    }
}

Results:

Iterations: 100
Array time     : 00:00:00.0108628
Switch time    : 00:00:00.0002204
Dictionary time: 00:00:00.0008475

Iterations: 1000
Array time     : 00:00:00.0000410
Switch time    : 00:00:00.0000472
Dictionary time: 00:00:00.0004556

Iterations: 10000
Array time     : 00:00:00.0006095
Switch time    : 00:00:00.0011230
Dictionary time: 00:00:00.0074769

Iterations: 100000
Array time     : 00:00:00.0043019
Switch time    : 00:00:00.0047117
Dictionary time: 00:00:00.0611122

Iterations: 1000000
Array time     : 00:00:00.0468998
Switch time    : 00:00:00.0520848
Dictionary time: 00:00:00.5861588

Iterations: 10000000
Array time     : 00:00:00.4268453
Switch time    : 00:00:00.5002004
Dictionary time: 00:00:07.5352484

Iterations: 100000000
Array time     : 00:00:04.1720282
Switch time    : 00:00:04.9347176
Dictionary time: 00:00:56.0107932

What happens. Let's look on the generated IL code:

Fruits.GetSpanishEquivalent:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  stloc.1     
IL_0003:  ldloc.1     
IL_0004:  switch      (IL_002B, IL_001F, IL_0023, IL_002B, IL_0027)
IL_001D:  br.s        IL_002F
IL_001F:  ldc.i4.1    
IL_0020:  stloc.0     
IL_0021:  br.s        IL_004A
IL_0023:  ldc.i4.3    
IL_0024:  stloc.0     
IL_0025:  br.s        IL_004A
IL_0027:  ldc.i4.2    
IL_0028:  stloc.0     
IL_0029:  br.s        IL_004A
IL_002B:  ldc.i4.0    
IL_002C:  stloc.0     
IL_002D:  br.s        IL_004A
IL_002F:  ldstr       "what kind of fruit is "
IL_0034:  ldarg.0     
IL_0035:  box         UserQuery+Fruits.FruitType
IL_003A:  ldstr       "?!"
IL_003F:  call        System.String.Concat
IL_0044:  newobj      System.Exception..ctor
IL_0049:  throw       
IL_004A:  ldloc.0     
IL_004B:  ret         

What happens? Switch happens. For sequenced number of values switch can be optimized and replaced by jump to pointer from array. Why real array works faster than switch - dunno, it just works faster.

Well, if you do not work with enums, but with strings there is no real difference between switch and dictionary on small number of variants. With more and more variants dictionary becomes faster.

What to choose? Choose what is easier to read for you and your team. When you see that your solution creates performance issues you should replace Dictionary (if you use it) to switch or array like me. When your function of translation is rarely called there is no need to optimize it.

Talking about your case - to get translation, all solutions are bad. Translations must be stored in resources. There must be only one FruitType, no other enums.

like image 169
Viktor Lova Avatar answered Oct 12 '22 10:10

Viktor Lova