4

What is the best universal approach (speed and handling) for managing constant data that is dependant on one or several conditional variables (int, float, string, ...)?

Basic examples:

Car CreateCar(int brand)
{
    //...
    float colourValue = GetRandomFloat(0F, 1F);
    car.Coulor = GetCarCoulor(brand, colourValue);
    //...
}

//-------------
//Example for data functions:
//-------------

string GetCarCoulor(int brand, float colourValue)
{
    if (brand == 0)
    {
        if (colourValue < 0.3)
            return "red";
        else if (colourValue < 0.7)
            return "green";
        else
            return "blue";
    }
    else if (brand == 1)
    {
        if (colourValue < 0.2)
            return "red";
        else
            return "blue";
    }
    //...
    return "default";
}

float GetCarSpeed(int brand, int carModel, int gear, bool returnMin)
{
    if (brand == 0)
    {
        if (carModel == 0)
        {
            if (gear == 1)
            {
                if (returnMin)
                    return 1F;
                else
                    return 15F;
            }
            else if (gear == 2)
            {
                if (returnMin)
                    return 15F;
                else
                    return 40F;
            }
        }
        //...
    }
    else if (brand == 1)
    {
        //...
    }
    //...
    return 0F;
}

Functions with if-else constructs are obviously the most basic form that work for most data but doesn't necessarily look/handle and perform very well with bigger data sets. Otherwise there are Dictionary<T1,T2> and switch constructs that come to mind, but these don't work that well with multiple or some types of input variables.

So are there any other kinds of constructs/approaches to look up constant data or some kind of best practice rules for that?

I also read of using XML files for data storage. Is this generally advisable for these kind of data, or is it slower or adds too much complexity? Advantages/disadvantages?

EDIT: Extended example to show what kind of data functions I had in mind.

LiWa
  • 51
  • 1
  • 5
  • 1
    When I see your question the word HashTable comes again and again to my head maybe with a lambda to calculate the key based on the different parameters. – Ignacio Soler Garcia Mar 04 '16 at 13:16
  • What sort of rules will decide the outputs (or is your example what you are looking for)? Will you require to modify the "rules" i.e. is maintenance of the code an issue. Or group the rules together? Do you need to test the rules? – Alex KeySmith Mar 04 '16 at 13:17
  • 1
    Basically all depends on concrete situation. In one case IF-ELSE is great, in other SWITCH may be better, sometimes creating Dictionary is preferable. Each approach is viable and there is no single winner for each and every scenario. – Sasha Mar 04 '16 at 13:19
  • What you describe doesn't involve *constant* data. You could say you are performing a *lookup* on *reference* data. The comparison operators though make this a *rule* more than anything else. There are a lot of ways to implements rules, some involving lookup structures like a Dictionary. If `input1` is *always* checked for equality and there are *always* 1 threshold for `input2`, you could use a `Dictionary>` to hold tuples with the threshold and possible results for each possible int – Panagiotis Kanavos Mar 04 '16 at 13:20
  • When I see your example, it reminds me of a coupon construction or something like that. Maybe you can take that as example how they approach constructions like this. – NiZa Mar 04 '16 at 13:24
  • 1
    As for data storage, pick anything you like that makes loading the structures easier. For simple rules you could use a simple table with a row per rule. Or you could use a json file. XML doesn't really add anything on top of Json in this case – Panagiotis Kanavos Mar 04 '16 at 13:24

3 Answers3

5

As an alternative approach, you could have a dictionary of "rules" as long as your input types stay the same.

I actually wouldn't recommend this (see the note below), but it's a possibility.

void Main()
{
    List<RuleResultPair> rules = new List<RuleResultPair>();

    rules.Add(new RuleResultPair 
    {
        Rule = (input) => input.A == 0 && input.B < 0.3, 
        Result = "output1.1"
    });

    rules.Add(new RuleResultPair
    {
        Rule = (input) => input.A == 0 && input.B < 0.7,
        Result = "output1.2"
    });

    var test1Point1 = new Input
    {
        A = 0,
        B = 0.2F
    };

    var test1Point2 = new Input
    {
        A = 0,
        B = 0.7F
    };


    rules.First(w => w.Rule(test1Point1)).Result.Dump(); //outputs output1.1
    rules.First(w => w.Rule(test1Point2)).Result.Dump(); //outputs output1.2

}

// Define other methods and classes here
public struct Input
{
    public int A;
    public float B;
}

public class RuleResultPair
{
    public Predicate<Input> Rule;
    public string Result;
}

http://share.linqpad.net/pqouhc.linq

I'm sure this code can be improved greatly, but I want to keep it relatively simple for the purpose of example.

As a general note:

It looks like this could lead to a maintenance problem. Often blocks of switch statements or if statements are a bit of a code smell.

It looks as though to extend your application that you'll have to modify a giant switch, if/else block.

Perhaps a strategy pattern could help break your algorithm apart. But it depends.

Edit:


From LiWa's additional example of needing to vary the inputs, here is a useful blog post describing refactoring a switch statement into separate algorithms: Applying Strategy Pattern Instead of Using Switch Statements

Alex KeySmith
  • 16,657
  • 11
  • 74
  • 152
  • 1
    A Dictionary is a very bad choice if you intent to use `First`, essentially iterating over all entries until you find a match. A List or array would be a lot faster – Panagiotis Kanavos Mar 04 '16 at 15:18
  • Though I'd want to store the "rule" and the "result" as a pair, so wouldn't a Dictionary be an ok choice? First() is there to cover when given inputs could match multiple rules (though this is again where a maintenance problem is an issue, and instead something like strategy pattern would come in). – Alex KeySmith Mar 04 '16 at 15:48
  • But yeah I see what you mean, perhaps a Tuple would be better or a list of structs. – Alex KeySmith Mar 04 '16 at 15:50
  • 1
    Ah ok, infact yeah there's no point having a func as a key: http://stackoverflow.com/questions/13409425/dictionary-with-func-as-key I'll tweak the code when I get a sec. – Alex KeySmith Mar 04 '16 at 15:52
  • @PanagiotisKanavos updated, thanks for the feedback :-) I won't go to far with the example, as I want to keep it reasonably simple to understand the concept. Also perhaps so a different technique can be used altogether. – Alex KeySmith Mar 04 '16 at 16:11
  • Interesting approach, thanks. But I was rather looking for a more dynamic construct that allows for different input and output types without having to rewrite that much code. Like a multidimensional lookup table with variable input, output and condition types like in my edited example. – LiWa Mar 05 '16 at 11:56
  • 1
    No worries @LiWa I've added an additional example for applying the strategy pattern which might help. Searching for "strategy pattern context" is a way of passing data to your differing algorithms. – Alex KeySmith Mar 07 '16 at 09:20
1

Dictionaries can still be good, if the several input variables are encapsulated in a single class, which has appropriate equality semantics, including hash code (you must always define hash code semantics compatible with your equality semantics).

PeteAC
  • 789
  • 8
  • 19
1

I've found some pattern usefull in past, not cover all cases, but you can found something here...

resource from constant

When you have one or more output as function of a single value (as a switch) you can use resources.

return ResourceNamespace.ResxFileName.ResourceManager.GetString("ResourceKey")

String combination: when the result can be determined with a "concatenation" of codes you can drop in resources

public string Decode(string a, string b, string c)
{
    return ResourceNamespace.ResxFileName.ResourceManager.GetString(a + b + c);
}