5
\$\begingroup\$

I am new to programming and I am trying to make a simple unit converter in python. I want to convert units within the metric system and metric to imperial and vice-versa. I have started with this code and I found this method is slow and in-efficient, How can I code this more efficiently?

import math
import time
"""Unit Converter"""
#Welcome and variable setting
print ("Welcome to Sam's Unit Converter")
cat = raw_input ("Which category would you like to convert? we support length(l) and Weight(w):  ")
if cat == ("l"):
unit1 = raw_input ("Which unit would you like to convert from: ")
unit2 = raw_input ("Which unit would you like to convert to: ")
num1 = raw_input ("Enter your value: " )

    ##Calculations  

if unit1 == "cm" and unit2 == "m":
    ans = float(num1)/100       
elif unit1 == "mm" and unit2 == "cm":
    ans = float(num1)/10
elif unit1 == "m" and unit2 == "cm":
    ans = float(num1)*100
elif unit1 == "cm" and unit2 == "mm":
    ans = float(num1)*10
elif unit1 == "mm" and unit2 == "m":
    ans = float(num1)/1000
elif unit1 == "m" and unit2 == "mm":
    ans = float(num1)*1000  
elif unit1 == "km" and unit2 == "m":
    ans = float(num1)*1000
elif unit1 == "m" and unit2 == "km":
    ans = float(num1)/1000
elif unit1 == "mm" and unit2 == "km":
    ans = float(num1)/1000000
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Welcome to Code Review. Since this question was cross-posted on Stack Overflow, it is advised to add a link to the question. I went ahead and fetched it for you: stackoverflow.com/questions/32091117/…. Also, as a side note, you don't need to create an entirelly new account for each site. All StackExchange powered websites share the same login credentials. Next time, visit the site and click on the link on top that says "join this community". \$\endgroup\$ Aug 19, 2015 at 9:53
  • \$\begingroup\$ Is there a reason you are rolling your own unit converter rather than using one of the dozen or so that already exist for Python? \$\endgroup\$ Aug 19, 2015 at 11:14

3 Answers 3

6
\$\begingroup\$

I must respectfully disagree with SuperBiasedMan's recommendation for using a dictionary: while it is already a much better solution than yours, that solution is still making things too complicated.

Instead, I recommend using this:

enter image description here

Above is a chart for converting units within the metric system. And, thanks to a quick google search, a good way to remember this would be to remember the phrase:

King Henry doesn't usually drink chocolate milk.

I have bold-ed the first letter of every word to show the correlation between this and the metric system: the first letter of each word directly corresponds to a metric unit "type": kilo*, hecto*, etc.

And, again going back to the chart, we can see that there are numbers associated with each unit "type" to show how many of the basic unit is in one of that unit type.

For example, there are 10 basic units in a deka*, and 0.01 basic units in a centi*.


Now with that information be said, we can easily create a map/different of the different metric "types" and the number of basic units on one of this type.

That would look like this:

types = {
    "k": 1000,
    "h": 100,
    "da": 10,
     "": 1,
     ...
}

To find out what values the user would like, we simply can use the types dictionary and the user input as an indexer to search the dictionary for what values to add.

That would look like this:

values[input]

Now, for the converting. This process will be very easy since we already have a handy dandy dictionary that holds all the information we need for converting.

All we need to do is divide the amount of basic units of the first type by the amount of basic units for the second type, and then multiply the input number by that result.

Here is what that would look like:

def convert(from_unit_type, to_unit_type, value):
    from_type_units = types[from_unit_type]
    to_type_units = types[to_unit_type]

    new_value = value * (from_type_units / to_type_units)

    return str(new_value) + to_unit_type

Using this method, we were able to completely reduce all conditionals.


Putting it all together:

import math
import time
"""Unit Converter"""
#Welcome and variable setting

types = {
    "k": 1000,
    "h": 100,
    "da": 10,
    "": 1,
    "d": 0.1,
    "c": 0.01,
    "m": 0.001
}

def convert(from_unit_type, to_unit_type, value):
    from_type_units = types[from_unit_type]
    to_type_units = types[to_unit_type]

    new_value = value * (from_type_units / to_type_units)

    return str(new_value) + to_unit_type

print ("Welcome to Sam's Unit Converter")
cat = raw_input ("Which category would you like to convert? [g,l,m]")

unit1 = raw_input ("Which unit would you like to convert from: ")
unit2 = raw_input ("Which unit would you like to convert to: ")
num1 = raw_input ("Enter your value: " )

print(convert(unit1, unit2, float(num1)))

If you have an issues or questions, please let me know in a comment.

\$\endgroup\$
1
  • \$\begingroup\$ What if there is a new unit needs to be implemented? the dictionary will be updated then? I was thinking of using a class here and maybe adding a new method to be able to add new unit into types dictionary. \$\endgroup\$
    – Enis Arik
    Jul 4, 2022 at 17:21
4
\$\begingroup\$

I recommend nesting dictionaries for your conversions. Usually if you have a long chain of elif comparisons to a string or number, you should use a dictionary.

You can store the first unit as the primary key, and then the second value is the key of the second unit. The values then all need to be a number that you can multiply by in order to perform the conversion. So to divide by 1000, instead the value to multiply by is 1/1000. You could technically store functions to call in order to evaluate the conversions but just numbers is simpler.

conversions = {
                "mm": {"mm": 1, "cm": 1/10, "m": 1/1000, "km": 1/1000000},
                "cm": {"mm": 10, "cm": 1, "m": 1/100, "km": 1/100000},
                "m":  {"mm": 1000, "cm": 100, "m": 1, "km": 1/1000},
                "km": {"mm": 100000, "cm": 10000, "m": 1000, "km": 1},
              }

This makes it easier to see if all of them are implemented, they're kept in neat rows so you can make sure none of the math seems off. Another advantage is that you can automatically generate a list of values to tell the user about.

unit1 = raw_input ("Which unit would you like to convert from?\n"
                   "We support: " + ", ".join(conversions.keys()))
unit2 = raw_input ("Which unit would you like to convert to?\n")
                   "We support: " + ", ".join(conversions[unit1].keys()))

Also you could now make sure that the user is typing in a valid key this way.

while True:
    try:
        unit1 = raw_input ("Which unit would you like to convert from?\n"
                           "We support: "
                           ", ".join(conversions.keys())).lower()
        unit2 = raw_input ("Which unit would you like to convert to?\m")
                           "We support: "
                           ", ".join(conversions[unit1].keys())).lower()
        convert = conversions[unit1][unit2]
    except KeyError:
        print ("That is not a valid key, please try again")

conversions[unit1][unit2] is just called in order to test whether the key exists and will raise an error if it doesn't so that the user will be told to enter new keys. I also added .lower() so that if the user inputs CM, Cm or even cM they'll all be converted to cm to match the key.

I recommend wrapping the number input similarly, as it prevents errors later.

while True:
    try:
        num1 = float(raw_input("Enter your value: " ))
    except ValueError:
        print ("That is not a valid float, please try again.")

Now when it comes to the actual logic, it's really quite simple since both values are already prepared:

ans = num1 * convert
\$\endgroup\$
2
  • \$\begingroup\$ Using dictionaries is much better than those ifs. Nice answer \$\endgroup\$
    – Caridorc
    Aug 19, 2015 at 13:39
  • 1
    \$\begingroup\$ This is an improvement, but still suffers from the poor scaling of the original: when you add a new unit, you need to update all the existing units as well as adding conversions for the new unit. Converting to the base unit and then to the result unit (or, equivalently, forming a single conversion from the two base-unit conversions) is easier to maintain. \$\endgroup\$ Oct 9, 2018 at 15:14
3
\$\begingroup\$

Instead of converting directly between arbitrary units, choose a standard unit and do the conversion in two steps, to and from the standard unit. That way you only need one conversion factor for each unit. Store the conversion factors in a dictionary keyed by the unit name.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ In practice this can cause problems due to issues with floating-point numbers. \$\endgroup\$ Aug 19, 2015 at 14:56
  • 1
    \$\begingroup\$ @TheBlackCat We're discussing a beginner's programming exercise here. I stand by my straightforward suggestion. \$\endgroup\$ Aug 20, 2015 at 6:33
  • \$\begingroup\$ @TheBlackCat - the biggest conversion is 1e6. That can be represented exactly, so we're no less accurate than the original (we need to shuffle the arithmetic slightly so that we create the conversion factor from input and output units, and then multiply or divide by that; we might be able to streamline the code by embedding the multiply/divide decision in an integer-rational number). \$\endgroup\$ Oct 9, 2018 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.