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:

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'