Search code examples
c#winformsrecursiontypesnested-loops

Creating an efficient datatype for looking up conversion factors by initial and resulting units C#


I'm trying to create a windows form application that will convert units of measurements from one to the other. My form features navigation buttons that I figured would make it user friendly.

When the user selects a category from the main menu it fills the category of measurement combobox and sets default unit of measurements into two other combo boxes.

These comboboxes allow the user to select which unit of measurement they want. When they choose a category such as distance, it fills the initial and resulting unit of measurement comboboxes.

I therefore have already created a few string[] that contains each unit of measurement.

string[] distanceUnits = { "Inches", "Feet", "Centimeters", "Meters", "Kilometers", "Miles" };
string[] massUnits = { "Grams", "Kilograms", "Pounds", "Ounces" };

I also created a couple different decimal[] arrays filled with conversion factors that I have been looking up and editing for precision purposes since I will be doing unit testing. I'd like to easily be able to change the conversion factor or add new ones into the arrays.

For example,

decimal[] massConversionFactors =
{
    .001m,               // [0] Grams to KiloGrams
    .00220462m,          // [1] Grams to Pounds
    .035274m,            // [2] Grams to Ounces
    1000,                // [3] KiloGrams to Grams
    2.20462m,            // [4] KiloGrams to Pounds
    35.274m,             // [5] KiloGrams to Ounces
    453.592m,            // [6] Pounds to Grams
    .453592m,            // [7] Pounds to Kilograms
    16                   // [8] Pounds to Ounces
};

and

decimal[] distanceConversionFactors =
{
    .083333m,             // [0] Inches to Feet
    2.54m,                // [1] Inches to Centimeters
    .0254m,               // [2] Inches to Meters
    .0000254m,            // [3] Inches to Kilometers
    .0000157828282828m,   // [4] Inches to Miles
    12,                   // [5] Feet to Inches
    30.48m,               // [6] Feet to Centimetes
    .3048m,               // [7] Feet to Meters
    .0003048m,            // [8] Feet to Kilometers
    .000189394m,          // [9] Feet to Miles
    .3937009133858m,      // [10] Centimeters to Inches
    .03280840944882m,     // [11] Centimeters to Feet
    .01m,                 // [12] Centimeters to Meters
    .00001m,              // [13] Centimeters to Kilometers
    .0000062137m,         // [14] Centimeters to Miles
    39.3701m,             // [15] Meters to Inches
    3.28084m,             // [16] Meters to Feet
    100,                  // [17] Meters to Centimeters
    .001m,                // [18] Meters to Kilometers
    .000621371m,          // [19] Meters to Miles
    39370.1m,             // [20] Kilometers to Inches
    3280.84m,             // [21] Kilometers to Feet
    100000,               // [22] Kilometers to Centimeters
    1000,                 // [23] Kilometers to Meters
    .621371m,             // [24] Kilometers to Miles
    63360,                // [25] Miles to Inches
    5280,                 // [26] Miles to Feet
    160934,               // [27] Miles to Centimeters
    1609.34m,             // [28] Miles to Meters
    1.60934m              // [29] Miles to Kilometers
};

That way when the submission button is pressed I can do a calculation such as:

private void ConvertSubmissionButton_Click(object sender, EventArgs e)
{
    //User's input value
    decimal input = decimal.Parse(UserInputTextBox.Text);
    string initialUnits = InitialUnitsComboBox.SelectedItem.ToString();
    string resultingUnits = ResultingUnitsComboBox.SelectedItem.ToString();

    //conversion of user input to result accepting initial and resulting
    decimal result =  input * ObjectWithTooManyMethods(initalUnits, resultingUnits);

    ResultingValueLabel.Text = (initialUnits);
    ResultingValueLabel.Show();
}

This works fine for now, but as I add more methods to the object I wonder if there is a way to consolidate the already created arrays.

The problem I'm having is that I am forced to hardcode every permutation of conversion in the

ObjectWithTooManyMethods(string, string);

What I'd really like to do is create a recursive loop that will go through the distanceUnits[] massUnits[] and other unit of measurement category arrays, and creates every possible permutation based on the category's appropriate conversionFactor arrays. This way I can add on units of measurements and their conversion factors without coding an entire new method in the future.

Is this possible?

This ideal loop would create a single datatype that contains this information and be able to retrieve the decimal value for the conversion factor for initial, and resulting units when the user clicks a submission button.

Such as:

DesiredDatatype<string,string,decimal> conversionFactors = new DesiredDatatype<string,string,decimal>()
{
    {initialUnits, resultingUnits, conversionFactor}
};

    private void ConvertSubmissionButton_Click(object sender, EventArgs e)
    {
        //User's input value
        decimal input = decimal.Parse(UserInputTextBox.Text);
        string initialUnits = InitialUnitsComboBox.SelectedItem.ToString();
        string resultingUnits = ResultingUnitsComboBox.SelectedItem.ToString();

        decimal result =  input * conversionFactors.get(initialUnits, resultingUnits);

        ResultingValueLabel.Text = (result);
        ResultingValueLabel.Show();
    }

Maybe I'm approaching this the wrong way, and/or have been coding too long today.. I have looked up information about tuples and dictionarys and they seem like they would ideal for this scenario but I have failed in creating a working datatype from looping through the information in these existing arrays.

Any advice is greatly appreciated. Thanks a lot!


Solution

  • Since you don't want to use objects, what about using enumerations and dictionaries? You can define an enumeration like this...

    public enum MassUnit
    {
        Grams,
        Kilograms,
        Ounces,
        Pounds
    }
    

    To convert enum values to and from strings you can...

    var stringArrayOfEnumNames = Enum.GetNames(typeof(Mass));
    var enumFromString = Enum.Parse(typeof(MassUnit), "Ounces");
    

    Next you could use a dictionary for your conversion amounts. There's no need to have every combination of conversions, just convert to a standard unit (grams) then convert to the destination unit. This dictionary stores values to convert any mass unit to grams.

    public static Dictionary<MassUnit, decimal> MassToGramsMap = new Dictionary<MassUnit, decimal>
    {
        [MassUnit.Grams] = 1m,
        [MassUnit.Kilograms] = .001m,
        [MassUnit.Ounces] = .035274m,
        [MassUnit.Pounds] = .00220462m
    };
    

    Then a conversion would look like...

    var value = 100m;   
    var from = MassUnit.Pounds;
    var to = MassUnit.Kilograms;
    var convertedValue = value / MassToGramsMap[from] * MassToGramsMap[to];
    

    Now if you wanted to add a new mass unit, you would add it to the MassUnit enumeration and add its conversion to MassToGramsMap dictionary and you're done.