views:

1583

answers:

4

I have a c# app and I need to convert between 3 different units (say for example: litres, Gallons and Pints).

The app needs to know about certain volumes of liquid say: 1 pint, 10 pints, 20 pints and 100 pints. I intend to do the calculations and hard code the values (not ideal but necessary),

Im looking for a datastructure that will allow me to easily convert from one unit to another.

Any suggestions?

Please note: I'm not actually using volumes of liquid, its just an example!

+2  A: 

Please take a look at Explicit Interface Implementation, I think it can help you, the sample is about what you need.

EDIT: sample copied from MSDN

interface IEnglishDimensions 
{
   float Length();
   float Width();
}
// Declare the metric units interface:
interface IMetricDimensions 
{
   float Length();
   float Width();
}
// Declare the "Box" class that implements the two interfaces:
// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions 
{
   float lengthInches;
   float widthInches;
   public Box(float length, float width) 
   {
      lengthInches = length;
      widthInches = width;
   }
// Explicitly implement the members of IEnglishDimensions:
   float IEnglishDimensions.Length() 
   {
      return lengthInches;
   }
   float IEnglishDimensions.Width() 
   {
      return widthInches;      
   }
// Explicitly implement the members of IMetricDimensions:
   float IMetricDimensions.Length() 
   {
      return lengthInches * 2.54f;
   }
   float IMetricDimensions.Width() 
   {
      return widthInches * 2.54f;
   }
   public static void Main() 
   {
      // Declare a class instance "myBox":
      Box myBox = new Box(30.0f, 20.0f);
      // Declare an instance of the English units interface:
      IEnglishDimensions eDimensions = (IEnglishDimensions) myBox;
      // Declare an instance of the metric units interface:
      IMetricDimensions mDimensions = (IMetricDimensions) myBox;
      // Print dimensions in English units:
      System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
      System.Console.WriteLine("Width (in): {0}", eDimensions.Width());
      // Print dimensions in metric units:
      System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
      System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
   }
}
pablito
+4  A: 

I have done this in an other language by providing the correct access methods (properties):

for the class Volume:
  AsLitre
  AsGallon
  AsPint

for the class Distance:
  AsInch
  AsMeter
  AsYard
  AsMile

One additional advantage is that the internal format does not matter.

Gamecat
+3  A: 

You can store a matrix of conversion factors where

  • a: Is litres
  • b: Is pints
  • c: Are gallons

You'd have (not accurate, but assuming there are two pints to a litre and 4 litres to a gallon)

   a     b       c
a  1     2     0.25
b  0.5   1     0.125
c  4     8       1

Alternatively, you can decide that everything is converted to a base value (litres) before being converted to another type, then you just need the first line.

Wrap this in a method that takes a number of units and "from" type and "two" type for the conversion.

Hope this helps

EDIT: some code, as requested

    public enum VolumeType
    {
        Litre = 0,
        Pint = 1,
        Gallon = 2
    }

    public static double ConvertUnits(int units, VolumeType from, VolumeType to)
    {
        double[][] factor = 
            {
                new double[] {1, 2, 0.25},
                new double[] {0.5, 1, 0.125},
                new double[] {4, 8, 1}
            };
        return units * factor[(int)from][(int)to];
    }

    public static void ShowConversion(int oldUnits, VolumeType from, VolumeType to)
    {
        double newUnits = ConvertUnits(oldUnits, from, to);
        Console.WriteLine("{0} {1} = {2} {3}", oldUnits, from.ToString(), newUnits, to.ToString());
    }


    static void Main(string[] args)
    {
        ShowConversion(1, VolumeType.Litre, VolumeType.Litre);  // = 1
        ShowConversion(1, VolumeType.Litre, VolumeType.Pint);   // = 2
        ShowConversion(1, VolumeType.Litre, VolumeType.Gallon); // = 4
        ShowConversion(1, VolumeType.Pint, VolumeType.Pint);    // = 1
        ShowConversion(1, VolumeType.Pint, VolumeType.Litre);   // = 0.5
        ShowConversion(1, VolumeType.Pint, VolumeType.Gallon);  // = 0.125
        ShowConversion(1, VolumeType.Gallon, VolumeType.Gallon);// = 1
        ShowConversion(1, VolumeType.Gallon, VolumeType.Pint);  // = 8
        ShowConversion(1, VolumeType.Gallon, VolumeType.Litre); // = 4
        ShowConversion(10, VolumeType.Litre, VolumeType.Pint);  // = 20
        ShowConversion(20, VolumeType.Gallon, VolumeType.Pint); // = 160
    }
Binary Worrier
I like the idea of using a matrix. Could you perhaps elaborate upon how it would be implemented in the current scenario? TIA.
Cerebrus
+1 I like the matrix solution too.
Nano HE
A: 

Here's the source code in its full entirety:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

namespace UnitConversion
{
    internal delegate double Converter(double value);

    class UnitConverter
    {
        private readonly IDictionary<string, IDictionary<string, Converter>> converters =
            new Dictionary<string, IDictionary<string, Converter>>();
        private readonly NumberFormatInfo numberFormatInfo;

        public UnitConverter()
        {
            numberFormatInfo = (NumberFormatInfo)NumberFormatInfo.InvariantInfo.Clone();
            numberFormatInfo.NumberDecimalSeparator = ".";
            numberFormatInfo.NumberGroupSeparator = String.Empty;
        }

        public void ParseConverterDefinition(string converterDefinition)
        {
            string[] parts = converterDefinition.Split(' ');
            double sourceUnitsValue = double.Parse(parts[0], NumberFormatInfo.InvariantInfo);
            double targetUnitsValue = double.Parse(parts[3], NumberFormatInfo.InvariantInfo);

            AddConverters(parts[1], sourceUnitsValue, parts[4], targetUnitsValue);
            AddConverters(parts[4], targetUnitsValue, parts[1], sourceUnitsValue);
        }

        private void AddConverters(string sourceUnits, double sourceUnitsValue, string targetUnits, double targetUnitsValue)
        {
            if (!converters.ContainsKey(sourceUnits))
                converters.Add(sourceUnits, new Dictionary<string, Converter>());

            converters[sourceUnits][targetUnits] =
                delegate(double value)
                { return value * targetUnitsValue / sourceUnitsValue; };
        }

        public double? Convert(double value, string sourceUnits, string targetUnits, params string[] skipUnits)
        {
            if (!converters.ContainsKey(sourceUnits))
                return null;

            if (converters[sourceUnits].ContainsKey(targetUnits))
                return converters[sourceUnits][targetUnits](value);

            foreach (KeyValuePair<string, Converter> pair in converters[sourceUnits])
            {
                if (Array.IndexOf(skipUnits, pair.Key) != -1)
                    continue;

                List<string> skip = new List<string>(skipUnits);
                skip.Add(sourceUnits);

                double? result = Convert(converters[sourceUnits][pair.Key](value), pair.Key, targetUnits, skip.ToArray());
                if (result != null)
                    return result;
            } // foreach

            return null;
        }

        public string Convert(string conversionRequest)
        {
            string[] parts = conversionRequest.Split(' ');
            return ConvertFormatted(double.Parse(parts[0], NumberFormatInfo.InvariantInfo), parts[1], parts[4]);
        }

        public string ConvertFormatted(double value, string sourceUnits, string targetUnits)
        {
            double? convertedValue = Convert(value, sourceUnits, targetUnits);
            if (convertedValue == null)
                return "No conversion is possible.";

            return string.Format("{0} {1} = {2} {3}", value.ToString("N6", numberFormatInfo), sourceUnits,
                convertedValue < 0.01 || convertedValue > 1000000 ?
                    convertedValue.Value.ToString("#.######e+00", numberFormatInfo) :
                    convertedValue.Value.ToString("N6", numberFormatInfo),
                targetUnits);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            UnitConverter unitConverter = new UnitConverter();

            foreach (string s in File.ReadAllLines("Conversions.txt"))
            {
                if (s.IndexOf("?") == -1)
                    unitConverter.ParseConverterDefinition(s);
                else
                    Console.WriteLine(unitConverter.Convert(s));
            } // foreach
        }
    }
}

It processes the file in the following format

7200.0 second = 2 hour
10.0 glob = 1 decaglob
1 day = 24.0 hour
1 minute = 60 second
1 glob = 10 centiglob
1 day = 24 hour
1 year = 365.25 day
50 centiglob = ? decaglob
5.6 second = ? hour
3 millisecond = ? hour
5.6 second = ? day
1 day = ? glob
1 hour = ? second
1 year = ? second

and calculates, for instance, 50 centiglobs in decaglobs.

This code is capable of doing chained conversions (e.g. year -> day -> hour -> second).

Anton Gogolev