Python Forum
First Functioning Calculator
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
First Functioning Calculator
#1
Hello,

So to practice functionality, I wrote out a calculator to determine the distance between two cities using the haversine formula. User inputs the cities name and coordinates.

import math

def city_loc():
    '''User inputs city name, and coordinates in NNN.NNNNN formant. Negative values for cities in the western and southern hemisphers.'''
    city_name = input('What is your first city? ')
    city_coordN = float(input('What is the north/south coordinate? '))
    city_coordE = float(input('What is the east/west coordinates" '))
    city_coord = (city_coordN,city_coordE,city_name)
    return tuple(city_coord)

def curve_dist(city1,city2):
    '''Calculates the distance between two cities on Earth using the haversine method. Coordinates, in tuple form, are used and need to be in NNN.NNNNN formant. Negative values for cities in the western and southern hemisphers.'''
    delta_phi = (city1[0]-city2[0])*(math.pi/180)
    delta_lamda = (city1[1]-city2[1])*(math.pi/180)
    a = math.sin(delta_phi/2)**2+math.cos(city1[0]*(math.pi/180))*math.cos(city1[0]*(math.pi/180))*math.sin(delta_lamda/2)**2
    c = 2 * math.atan2(a**.5,(1-a)**.5)
    return(6371 * c)

#-----main program----------
print('Welcome to the city distance calculator, where we tell you the distance between cities as the crow flies in kilometers.')
city1 = city_loc()
city2 = city_loc()
dist = curve_dist(city1,city2)
print('The distance between {} and {} is {d:.2f} km'.format(city1[2],city2[2],d=dist))
I am open to any comments on either efficiency improvements, corrections, or if the code documentation could be better. Next version could have unit conversion and possible a text list of multiple cities with their coordinates typed out in a txt file.
Reply
#2
The main thing I'd suggest is that instead of using a tuple to hold the data, use a namedtuple, so that you can name the fields and make the function that uses it more readable - at the moment you have to be aware of which value is at which index.

Also, writing unit tests for your functions can also serve as documentation. Doing so for the function that reads the input is a little more complex, but still doable. While I'm here, you have no error handling in your input function - what happens if they enter a value that isn't a number for any of the coordinates?
Reply
#3
Thank you for the suggestions nbc85430. I had never worked with namedtuples before and implemented them to the best I was able to figure out. And I agree, I do see the advantage of using nametuples over index positions (attempting to code the names in resulted in me looking over the quick notes to makes sure I oriented things correctly, adding moments of confusion). As for the user input, yeah I was going to work on that next to keep prompting the user until at least integers were inputted. Here is my updated code:
#-----imported libraries----------
import math
from collections import namedtuple
from IPython.display import clear_output

#------------main calculation-------
def curve_dist(city1,city2):
    '''Calculates the distance between two cities on Earth using the haversine method. Coordinates, in tuple form, are used and need to be in NNN.NNNNN formant. Negative values for cities in the western and southern hemisphers.'''
    delta_phi = (city1.city_N-city2.city_N)*(math.pi/180)
    delta_lamda = (city1.city_E-city2.city_E)*(math.pi/180)
    a = math.sin(delta_phi/2)**2+math.cos(city1.city_N*(math.pi/180))*math.cos(city1.city_N*(math.pi/180))*math.sin(delta_lamda/2)**2
    c = 2 * math.atan2(a**.5,(1-a)**.5)
    return(6371 * c)

#-------------user input------------
def city_loc():
    '''User inputs city name, and coordinates in NNN.NNNNN formant. Negative values for cities in the western and southern hemisphers.'''
    city_name = input('Input a city name? ')
    while True:
        try: 
            city_coordN = float(input('What is the north/south coordinate? '))
        except:
            print('That is not a valid coordinate, try again under this format: NNN.NNNNN')
        else:
            break
    while True:
        try:
            city_coordE = float(input('What is the east/west coordinates" '))
        except:
            print('That is not a valid coordinate, try again under this format: NNN.NNNNN')
        else:
            break
    city_coord = namedtuple('city_coord',['city_N','city_E','city_Nm'])
    cc = city_coord(city_coordN,city_coordE,city_name)
    return cc

#-----------main program------------
print('Welcome to the city distance calculator, where we tell you the distance between cities as the crow flies in kilometers.')
city1 = city_loc()
city2 = city_loc()
dist = curve_dist(city1,city2)
clear_output()
print('The distance between {} and {} is {d:.2f} km'.format(city1.city_Nm,city2.city_Nm,d=dist))
Think you could expand on your idea of using a unit test as a form of documentation, not entirely sure what you mean by that.
Reply
#4
I take it you haven't written tests before? They're basically code that, well, test your code to make sure everything works as intended. The primary reason to have them is so that you can be confident you haven't broken something when you make changes (because if you have, you should see some tests failing to tell you that). They can serve as documentation because the tests for, say, a function, will show the behaviours that that function has, that is what happens in the cases when it's given good input and when it fails in some way.

For example, let's consider that I need to write a function that checks whether a password is valid and the rules for validity are

1. It has to be at least 8 characters long
2. The characters all have to be different

So, I'd have test cases like

A password containing 8 unique characters is valid
A password containing 8 characters with duplicates is not valid
A password containing fewer than 8 characters is not valid

which would translate to

def test_a_password_containing_8_unique_characters_is_valid():
    password = "abcdefdh"

    result = is_password_valid(password)

    assert result == True

def test_a_password_containing_8_characters_with_duplicates_is_invalid():
    password = "11111111"

    result = is_password_valid(password)

    assert result == False

def test_a_password_containing_fewer_than_8_characters_is_invalid():
    password = "abc"

    result = is_password_valid(password)

    assert result == False
with pytest. The function being tested is is_password_valid and the point is that you can see how that function is being used: it expects a string and returns a boolean depending on whether the password is considered valid according to the rules.

Note that I've not actually written the function; for the time being I just wanted to demonstrate what tests look like (and I'm actually a big fan of test-driven development, where one writes a test before writing the functionality).
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020