December 14th
Sleigh Ride
In order to deliver presents to all over the known world (Yorkshire) Father Christmas must carefully calculate the total distance to ensure he has enough magic carrots for the reindeer.
2:40-2:50am is the delivery slot for the whole of Yorkshire. Since the reindeer travel at 100 miles per minute you need to check that every postcode in Yorkshire can be visited in a Sleigh Ride of less than 1000 miles (actual present delivery time is negligible - Father Christmas is THAT good).
Example
If Harrogate were the only place worth visiting (the general consensus in Harrogate), the data set would be:
PC Lat Long
HG1 54.00 -1.54
HG2 53.99 -1.53
HG3 54.03 -1.62
HG4 54.16 -1.57
HG5 54.01 -1.46
Total the diagonal distance from HG1 to HG2, then HG2 to HG3, and so on, to HG5.
Since longitude (west/east) and latitude (north/south) slice the Earth in 360 degrees, you need to multiply the total distance by 69 to convert it to miles (69 * 360 = 24840 miles ≈ the equator).
Use Pythagoras' Theorem (up² + along² = diagonal²) to calculate diagonal distance.
Sources: Office for National Statistics & UK Postcodes
Decomposition
Create the following functions:
def split_list(data:str) -> list takes the raw TOWNS string and returns a list of [postcode, lat, long] triplets, eg:
towns = [['HG1', '54.00', '-1.54'], ['HG2', '53.99', '-1.53'], ['HG3', '54.03', '-1.62'], ['HG4', '54.16', '-1.57'], ['HG5', '54.01', '-1.46']]def lat_long(postcode:str) -> list: takes a postcode eg 'BD1' and returns its latitude and longitude eg [53.80,-1.75]
def pythag(x1, y1, x2, y2) -> float: takes two x,y pairs and returns the diagonal distance between them, in miles, rounded to the nearest whole number.
def dist_between(pc1, pc2) -> int: takes two postcodes, looks them up using lat_long, then calls pythag to find their distance in miles
def total_distance(towns:list, verbose=False) -> int: returns the total distance between all the towns.
verbose is a boolean flag to output the intermediate steps, so you can manually check the sum is correct, eg
BD1 to BD10 is 3 miles
BD10 to BD11 is 6 miles
BD11 to BD12 is 6 miles
Your program should output the total distance travelled between all the postcodes in the order given.
Tips (Python)
The ** operator does 'to the power of':
5 ** 2 # evaluates to 25.
Conversely, to square root, 'invert' the power, from 2 to 1/2:
25 ** (1/2) # evals to 5.0
The 'walrus' operator := lets you evaluate an expression AND capture the result at the same time. It neatly side-steps a common issue where you want to work something out / call a function inside a condition AND then do something else with the result. That you now don't have :(
# assign to temp variable
result = double_add3(10)
if result != 23:
print(f'Expected 23 but got {result}')
# evaluate twice (expensive?!?)
if double_add3(10) != 23:
print(f'Expected 23 but got {double_add3(10)}')
# walrus - mmmm, yummy!
if (result := double_add3(10)) != 23:
print(f'Expected 23 but got {result}')
Note that, to avoid confusion with the normal variable assignment operator =, the walrus expression MUST be wrapped in parentheses (highlighted in yellow above).
Testing
If your tests DON'T pass, the 'walrus' operator := can be used to capture the (unexpected/wrong) value that WAS returned from the function, and display it after the assertion error report. Very helpful for debugging (saves adding 'print' everywhere)!
towns = split_list(TOWNS)
assert (res := towns[0]) == ['BD1', '53.80', '-1.75'], res
assert (res := towns[-1]) == ['YO19', '53.91', '-1.02'], res
towns # comment this out once you're happy!
assert (res := pythag(53.83, -1.78, 53.82, -1.73)) == 4 # BD18 to BD2, about 4 miles
assert (res := pythag(100, 100, 103, 104)) == 345.0 # 3, 4, 5 triangle, 5 degrees = 345 miles
assert (res := lat_long('BD18')) == [53.83, -1.78], res
assert (res := lat_long('YO1')) == [53.96, -1.08], res
# Integration tests
assert (res := dist_between('BD18', 'BD2')) == 4, res # 4 miles from house to school
assert (res := dist_between('HG4', 'HG1')) == 11, res # 11 miles from Ripon to Bettys Tea Rooms
assert (res := total_distance(towns[:9], verbose=False)) == 14, res # Bradford centre
assert (res := total_distance(towns[19:24], verbose=False)) == 12, res # Huddersfield centre
assert (res := total_distance(towns[59:67], verbose=False)) == 17, res # Sheffield centre
miles = total_distance(towns)
Your data set:
DEG = 69 # 69 miles = 1 degree (latitude or longitude)
TOWNS = 'BD1,53.80,-1.75 BD2,53.82,-1.73 BD3,53.80,-1.73 BD4,53.78,-1.72 BD5,53.78,-1.76 BD6,53.76,-1.79 BD7,53.78,-1.79 BD8,53.80,-1.78 BD9,53.81,-1.79 BD10,53.83,-1.72 BD11,53.75,-1.68 BD12,53.75,-1.76 BD13,53.79,-1.86 BD14,53.78,-1.82 BD15,53.81,-1.84 BD16,53.85,-1.83 BD17,53.85,-1.77 BD18,53.83,-1.78 BD19,53.73,-1.71 HD1,53.65,-1.79 HD2,53.67,-1.78 HD3,53.65,-1.83 HD4,53.63,-1.80 HD5,53.65,-1.75 HD6,53.70,-1.78 HD7,53.62,-1.88 HD8,53.60,-1.68 HD9,53.58,-1.80 HG1,54.00,-1.54 HG2,53.99,-1.53 HG3,54.03,-1.62 HG4,54.16,-1.57 HG5,54.01,-1.46 HX1,53.72,-1.87 HX2,53.74,-1.91 HX3,53.73,-1.84 HX4,53.68,-1.88 HX5,53.68,-1.84 HX6,53.70,-1.93 HX7,53.74,-2.01 LS1,53.80,-1.55 LS2,53.80,-1.55 LS3,53.80,-1.56 LS4,53.81,-1.58 LS5,53.82,-1.61 LS6,53.82,-1.57 LS7,53.82,-1.54 LS8,53.82,-1.51 LS9,53.80,-1.51 LS10,53.76,-1.53 LS11,53.78,-1.56 LS12,53.79,-1.60 LS13,53.81,-1.64 LS14,53.83,-1.46 LS15,53.81,-1.45 LS16,53.85,-1.60 LS17,53.86,-1.53 LS18,53.84,-1.64 LS19,53.86,-1.68 S1,53.38,-1.47 S2,53.37,-1.45 S3,53.39,-1.47 S4,53.40,-1.45 S5,53.42,-1.46 S6,53.40,-1.51 S7,53.36,-1.49 S8,53.34,-1.48 S9,53.40,-1.42 S10,53.38,-1.52 S11,53.36,-1.51 S12,53.35,-1.41 S13,53.36,-1.38 S14,53.35,-1.44 S17,53.32,-1.53 S18,53.30,-1.47 WF1,53.69,-1.49 WF2,53.67,-1.51 WF3,53.72,-1.53 WF4,53.65,-1.52 WF5,53.68,-1.58 WF6,53.70,-1.42 WF7,53.67,-1.35 WF8,53.69,-1.30 WF9,53.61,-1.32 YO1,53.96,-1.08 YO7,54.22,-1.35 YO8,53.78,-1.06 YO10,53.95,-1.06 YO11,54.26,-0.40 YO12,54.27,-0.42 YO13,54.29,-0.50 YO14,54.20,-0.30 YO15,54.09,-0.18 YO16,54.10,-0.20 YO17,54.14,-0.76 YO18,54.26,-0.77 YO19,53.91,-1.02'