Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# function to convert text input of feet/inches/meters/centimeters/millimeters into numeric values

I'm writing a function to take shorthand values and convert them into a standardized numeric format. Is there any standard code out there that would do "best possible" conversion of arbitrary measurement text and turn it into numeric measurements if the text is valid?

I guess I'm looking for something like bool TryParseMeasurement(string s, out decimal d). Does anyone know of a function like this?

Here's an example of some of the input values I've seen:

Imperial

  • 6 inches
  • 6in
  • 6”
  • 4 feet 2 inches
  • 4’2”
  • 4 ‘ 2 “
  • 3 feet
  • 3’
  • 3 ‘
  • 3ft
  • 3ft10in
  • 3ft 13in (should convert to 4’1”)

Metricc

  • 1m
  • 1.2m
  • 1.321m
  • 1 meter
  • 481mm
like image 538
Ted Spence Avatar asked May 27 '11 21:05

Ted Spence


2 Answers

Here's some code we wrote in an app quite some time ago, where we were doing something similar. It's not the best, but you may be able to adapt, or get some sort of jumping off point.

public static class UnitConversion
{
    public static string[] lstFootUnits = new string[] {"foots", "foot", "feets", "feet", "ft", "f", "\""};
    public static string sFootUnit = "ft";

    public static string[] lstInchUnits = new string[] { "inches", "inchs", "inch", "in", "i", "\'" };
    public static string sInchUnit = "in";

    public static string[] lstPoundUnits = new string[] { "pounds", "pound", "pnds", "pnd", "lbs", "lb", "l", "p" };
    public static string sPoundUnit = "lbs";

    public static string[] lstOunceUnits = new string[] { "ounces", "ounce", "ozs", "oz", "o" };
    public static string sOunceUnit = "oz";

    public static string[] lstCentimeterUnits = new string[] { "centimeters", "centimeter", "centimetres", "centimetre", "cms", "cm", "c"};
    public static string sCentimeterUnit = "cm";

    public static string[] lstKilogramUnits = new string[] { "kilograms", "kilogram", "kilos", "kilo", "kgs", "kg", "k" };
    public static string sKilogramsUnit = "kgs";

    /// <summary>
    /// Attempt to convert between feet/inches and cm
    /// </summary>
    /// <param name="sHeight"></param>
    /// <returns></returns>
    public static string ConvertHeight(string sHeight)
    {
        if (!String.IsNullOrEmpty(sHeight))
        {
            sHeight = UnitConversion.CleanHeight(sHeight);

            if (sHeight.Contains(UnitConversion.sFootUnit))
            {
                sHeight = sHeight.Replace(UnitConversion.sFootUnit, "|");
                sHeight = sHeight.Replace(UnitConversion.sInchUnit, "|");

                string[] sParts = sHeight.Split('|');

                double? dFeet = null;
                double? dInches = null;
                double dFeetParsed;
                double dInchesParsed;

                if (sParts.Length >= 2 && double.TryParse(sParts[0].Trim(), out dFeetParsed))
                {
                    dFeet = dFeetParsed;
                }
                if (sParts.Length >= 4 && double.TryParse(sParts[2].Trim(), out dInchesParsed))
                {
                    dInches = dInchesParsed;
                };

                sHeight = UnitConversion.FtToCm(UnitConversion.CalculateFt(dFeet ?? 0, dInches ?? 0)).ToString() + " " + UnitConversion.sCentimeterUnit;
            }
            else if (sHeight.Contains(UnitConversion.sCentimeterUnit))
            {
                sHeight = sHeight.Replace(UnitConversion.sCentimeterUnit, "|");
                string[] sParts = sHeight.Split('|');

                double? dCentimeters = null;
                double dCentimetersParsed;

                if (sParts.Length >= 2 && double.TryParse(sParts[0].Trim(), out dCentimetersParsed))
                {
                    dCentimeters = dCentimetersParsed;
                }

                int? iFeet;
                int? iInches;
                if (UnitConversion.CmToFt(dCentimeters, out iFeet, out iInches))
                {
                    sHeight = (iFeet != null) ? iFeet.ToString() + " " + UnitConversion.sFootUnit : "";
                    sHeight += (iInches != null) ? " " + iInches.ToString() + " " + UnitConversion.sInchUnit : "";
                    sHeight = sHeight.Trim();
                }
                else
                {
                    sHeight = "";
                }
            }
            else
            {
                sHeight = "";
            }
        }
        else
        {
            sHeight = "";
        }

        return sHeight;
    }

    /// <summary>
    /// Attempt to convert between Kgs and Lbs
    /// </summary>
    /// <param name="sWeight"></param>
    /// <returns></returns>
    public static string ConvertWeight(string sWeight)
    {
        if (!String.IsNullOrEmpty(sWeight))
        {
            sWeight = UnitConversion.CleanWeight(sWeight);

            if (sWeight.Contains(UnitConversion.sKilogramsUnit))
            {
                sWeight = sWeight.Replace(UnitConversion.sKilogramsUnit, "|");

                string[] sParts = sWeight.Split('|');

                double? dKilograms = null;
                double dKilogramsParsed;

                if (sParts.Length >= 2 && double.TryParse(sParts[0].Trim(), out dKilogramsParsed))
                {
                    dKilograms = dKilogramsParsed;
                }

                sWeight = UnitConversion.KgToLbs(dKilograms).ToString("#.###") + " " + UnitConversion.sPoundUnit;
            }
            else if (sWeight.Contains(UnitConversion.sPoundUnit))
            {
                sWeight = sWeight.Replace(UnitConversion.sPoundUnit, "|");

                string[] sParts = sWeight.Split('|');

                double? dPounds = null;
                double dPoundsParsed;

                if (sParts.Length >= 2 && double.TryParse(sParts[0].Trim(), out dPoundsParsed))
                {
                    dPounds = dPoundsParsed;
                }

                sWeight = UnitConversion.LbsToKg(dPounds).ToString("#.###") + " " + UnitConversion.sKilogramsUnit;
            }
            else
            {
                sWeight = "";
            }
        }
        else
        {
            sWeight = "";
        }

        return sWeight;
    }

    public static double? CalculateFt(double dFt, double dInch)
    {
        double? dFeet = null;
        if (dFt >= 0 && dInch >= 0 && dInch <= 12)
        {
            dFeet = dFt + (dInch / 12);
        }
        return dFeet;
    }

    public static double KgToLbs(double? dKg)
    {
        if (dKg == null)
        {
            return 0;
        }
        return dKg.Value * 2.20462262;
    }

    public static double LbsToKg(double? dLbs)
    {
        if (dLbs == null)
        {
            return 0;
        }
        return dLbs.Value / 2.20462262;
    }

    public static double FtToCm(double? dFt)
    {
        if (dFt == null)
        {
            return 0;
        }
        return dFt.Value * 30.48;
    }

    public static bool CmToFt(double? dCm, out int? iFt, out int? iInch)
    {

        if (dCm == null)
        {
            iFt = null;
            iInch = null;
            return false;
        }

        double dCalcFeet = dCm.Value / 30.48;
        double dCalcInches = dCalcFeet - Math.Floor(dCalcFeet);
        dCalcFeet = Math.Floor(dCalcFeet);
        dCalcInches = dCalcInches * 12;

        iFt = (int)dCalcFeet;
        iInch = (int)dCalcInches;
        return true;
    }

    private static string CleanUnit(string sOriginal, string[] lstReplaceUnits, string sReplaceWithUnit)
    {
        System.Text.StringBuilder sbPattern = new System.Text.StringBuilder();

        foreach (string sReplace in lstReplaceUnits)
        {
            if (sbPattern.Length > 0)
            {
                sbPattern.Append("|");
            }
            sbPattern.Append(sReplace);
        }
        sbPattern.Insert(0,@"(^|\s)(");
        sbPattern.Append(@")(\s|$)");


        System.Text.RegularExpressions.Regex rReplace = new System.Text.RegularExpressions.Regex(sbPattern.ToString(), System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        sOriginal = rReplace.Replace(sOriginal, sReplaceWithUnit);

        /*foreach (string sReplace in lstReplaceUnits)
        {

            sOriginal = sOriginal.Replace(sReplace, " " + sReplaceWithUnit);
        }*/


        return sOriginal;
    }

    private static bool StringHasNumbers(string sText)
    {
        System.Text.RegularExpressions.Regex rxNumbers = new System.Text.RegularExpressions.Regex("[0-9]+");

        return rxNumbers.IsMatch(sText);
    }

    private static string ReduceSpaces(string sText)
    {
        while (sText.Contains("  "))
        {
            sText = sText.Replace("  ", " ");
        }

        return sText;
    }

    private static string SeperateNumbers(string sText)
    {
        bool bNumber = false;
        if (!String.IsNullOrEmpty(sText))
        {
            for (int iChar = 0; iChar < sText.Length; iChar++)
            {
                bool bIsNumber = (sText[iChar] >= '0' && sText[iChar] <= '9') || 
                    (sText[iChar] == '.' && iChar < sText.Length - 1 && sText[iChar + 1] >= '0' && sText[iChar + 1] <= '9');

                if (iChar > 0 && bIsNumber != bNumber)
                {
                    sText = sText.Insert(iChar, " ");
                    iChar++;
                }

                bNumber = bIsNumber;
            }
        }

        return sText;
    }

    public static string CleanHeight(string sHeight)
    {
        if (UnitConversion.StringHasNumbers(sHeight))
        {
            sHeight = SeperateNumbers(sHeight);
            sHeight = CleanUnit(sHeight, UnitConversion.lstFootUnits, UnitConversion.sFootUnit);
            sHeight = CleanUnit(sHeight, UnitConversion.lstInchUnits, UnitConversion.sInchUnit);
            sHeight = CleanUnit(sHeight, UnitConversion.lstCentimeterUnits, UnitConversion.sCentimeterUnit);
            sHeight = SeperateNumbers(sHeight);
            sHeight = ReduceSpaces(sHeight);
        }
        else
        {
            sHeight = "";
        }

        return sHeight;
    }

    public static string CleanWeight(string sWeight)
    {
        if (UnitConversion.StringHasNumbers(sWeight))
        {
            sWeight = SeperateNumbers(sWeight);
            sWeight = CleanUnit(sWeight, UnitConversion.lstOunceUnits, UnitConversion.sOunceUnit);
            sWeight = CleanUnit(sWeight, UnitConversion.lstPoundUnits, UnitConversion.sPoundUnit);
            sWeight = CleanUnit(sWeight, UnitConversion.lstKilogramUnits, UnitConversion.sKilogramsUnit);
            sWeight = SeperateNumbers(sWeight);
            sWeight = ReduceSpaces(sWeight);
        }
        else
        {
            sWeight = "";
        }

        return sWeight;
    }
}
like image 200
Jeremy Avatar answered Sep 28 '22 17:09

Jeremy


It should serve you well to build an extension method of string for this purpose. When you build an extension method you attach a new function call to an existing class. In this we are go to attach a method to the 'string' class that returns a double, as the number of millimeters in a given imperial value, PROVIDED that the value can be parsed based on the examples you provide.

using System;
using System.Text;

namespace SO_Console_test
{
    static class ConversionStringExtensions
    {
        //this is going to be a simple example you can 
        //fancy it up a lot...
        public static double ImperialToMetric(this string val)
        {
            /*
             * With these inputst we want to total inches.
             * to do this we want to standardize the feet designator to 'f' 
             * and remove the inch designator altogether.
                6 inches
                6in
                6”
                4 feet 2 inches
                4’2”
                4 ‘ 2 “
                3 feet
                3’
                3 ‘
                3ft
                3ft10in
                3ft 13in (should convert to 4’1”) ...no, should convert to 49 inches, then to metric.
             */

            //make the input lower case and remove blanks:
            val = val.ToLower().Replace(" ", string.Empty);

            //make all of the 'normal' feet designators to "ft"
            string S = val.Replace("\'", "f").Replace("feet", "f").Replace("ft", "f").Replace("foot", "f").Replace("‘", "f").Replace("’", "f");

            //and remove any inch designator
            S = S.Replace("\"", string.Empty).Replace("inches", string.Empty).Replace("inch", string.Empty).Replace("in", string.Empty).Replace("“", string.Empty).Replace("”", string.Empty);

            //finally we have to be certain we have a number of feet, even if that number is zero
            S = S.IndexOf('f') > 0 ? S : "0f" + S;

            //now, any of the inputs above will have been converted to a string 
            //that looks like 4 feet 2 inches => 4f2

            string[] values = S.Split('f'); 

            int inches = 0;
            //as long as this produces one or two values we are 'on track' 
            if (values.Length < 3)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    inches += values[i] != null && values[i] != string.Empty ? int.Parse(values[i]) * (i == 0 ? 12 : 1) : 0 ;
                }
            }

            //now inches = total number of inches in the input string.

            double result = inches * 25.4;

            return result;

        }
    }
}

With that in place "ImperialToMetric()" becomes a method of any string, and can be invoked anywhere the extension containing class ConversionStringExtensions is referenced. You can use it like:

namespace SO_Console_test
{
    class Program
    {
        static void Main(string[] args)
        {    
            showConversion();
            Console.ReadLine();

        }

        private static void showConversion()
        {
            //simple start:
            Console.WriteLine("6ft 2\"".ImperialToMetric().ToString() + " mm");

            //more robust:
            var Imperials = new List<string>(){"6 inches",
                "6in",
                "6”",
                "4 feet 2 inches",
                "4’2”",
                "4 ‘ 2 “",
                "3 feet",
                "3’",
                "3 ‘",
                "3ft",
                "3ft10in",
                "3ft 13in"};

            foreach (string imperial in Imperials)
            {
                Console.WriteLine(imperial + " converted to " + imperial.ImperialToMetric() + " millimeters");
            }


        }
}

Obviously, at this point a call to "Fred".ImperialToMetric is not going to play nice. You will need to had error handling and perhaps some options to turn 1234 mm 1.234 km etc. but once you flush this out you have a method you can use where ever you choose.

like image 23
Cos Callis Avatar answered Sep 28 '22 17:09

Cos Callis