Python control flow sal's shipping


#1

so im at the last part of this problem and i did everything i could to check my error, i even looked up the youtube video and i still cant find what is wrong here.
so my function is this:
def print_cheapest_shipping_method(weight):

ground = shipping_cost_ground(weight)
premium = shipping_cost_premium
drone = shipping_cost_drone(weight)

if ground < premium and ground < drone :
method = “standard ground”
cost = ground
elif premium < ground and premium < drone :
method = “premium ground”
cost = premium
else:
method = "drone "
cost = drone
print(
“the cheapest option available is $%.2f with $s shipping.”
% (cost, method)
)

i try to call it with :
print_cheapest_shipping_method(4.8)

and i get error :
Traceback (most recent call last):
File “script.py”, line 50, in
print_cheapest_shipping_method(4.8)
File “script.py”, line 48, in print_cheapest_shipping_method
% (cost, method)
TypeError: not all arguments converted during string formatting


#2

Read the error message carefully, it is in fairly plain English.
If it’s not entirely clear how to fix it, then keep in mind that there’s nothing stopping you from modifying that code, for example to make it simpler - make it completely trivial and then start adding things back.
"" % ()
Watch out with 1-tuples though because you could easily write them so that they just mean regular parenthesis instead (and is one reason why % for strings is no longer the preferred method for string interpolation)


#3

Could we please have a link to this project, for context and to review the instruction text? As much as the modulo formatting method is losing its shine, it may still be a requirement in the exercise. We cannot critique that aspect until we know as much.


Found it

Sal’s Shipping

The video shows the author using modulo formatting.


When working with sequential data, we can use indices rather than if..elif..else chains…

def get_rate_index(weight):
  return tabs.index([x for x in tabs if weight > x][-1])

def ground_shipping(weight):
  return b_rate + g_rates[get_rate_index(weight)] * weight

def drone_shipping(weight):
  return d_rates[get_rate_index(weight)] * weight

def lowest_cost(weight):
  ground = ground_shipping(weight)
  drone = drone_shipping(weight)
  return ('Premium', premium) if premium < ground or premium < drone else ('Ground', ground) if ground < drone else ('Drone', drone)

premium = 125.00
b_rate = 20.00
tabs = [0, 2, 6, 10]
g_rates = [1.50, 3.00, 4.00, 4.75]
d_rates = [4.50, 9.00, 12.00, 14.25]
print ("{:.2f}".format(ground_shipping(8.4)))
print ("{:.2f}".format(drone_shipping(1.5)))
print ("{}: {:.2f}".format(lowest_cost(4.8)[0], lowest_cost(4.8)[1]))
print ("{}: {:.2f}".format(lowest_cost(41.5)[0], lowest_cost(41.5)[1]))
53.60
6.75
Ground: 34.40
Premium: 125.00

Of particular note is that there are no literals in any of the functions.

The purpose of this post is to increase the difficulty of the lesson by imploring the learner to seek out the relationship between these decision making processes. I prefer imperative values be stored in a list, and not as literals in code. The imperative here is to compare if chains to iterables in conditionals.


#4

i have no idea what indices are. i havent learned it yet


#5

ill try and do that thx


#6

Indices is plural of index


#7

Isn’t that really hard to get a bird’s eye view on though?

    return (('Premium', premium) if premium < ground or premium < drone else
            ('Ground', ground) if ground < drone else
            ('Drone', drone))

That’s objectively okay, but it’s an uncommon pattern and sequential if-statements will do the job:

    if premium < ground or premium < drone:
        return ('Premium', premium)
    if ground < drone:
        return ('Ground', ground)
    return ('Drone', drone)

But with any refactoring I sure want to know what the actual intention is, the function’s name is:

print_cheapest_shipping_method(weight)
(clearly we lost the print part somewhere, but it probably shouldn’t anyway)

Surely, that should just be a call to min.

min([(premium, 'Premium'), (ground, 'Ground'), (drone, 'Drone')])

(the tuples need to be flipped afterwards)
But I sill find it super weird to pair up values and strings that way, so something smells fishy about whatever is calling that code too.

Tuples/lists can be super useful for sorting/min/max etc. because they get compared by first elements, and then move on to following ones if needed. This can for example be used to sort two lists in sync (probably a bad idea, turn it into one list instead… oh wait that’s exactly the suggestion here), turn them into pairs instead, and sort that.


#8

This (from codecademy’s solution) makes me unhappy:

def cheapest_shipping(weight):
    if (drone_shipping(weight) < prem_ground_shipping) and (drone_shipping(weight) < ground_shipping(weight)):
        print("You should ship using drone shipping, it will cost $" + str(drone_shipping(weight)))
    elif ground_shipping(weight) < prem_ground_shipping:
        print("You should ship using ground shipping, it will cost $" + str(ground_shipping(weight)))
    else:
        print("You should ship using premium ground shipping, it will cost $" + str(prem_ground_shipping))

All three strings are identical except for price/name. It should say:

print("You should ship using {name} shipping, it will cost ${priceusd}".format(**locals()))

Or python3.7+

print(f"You should ship using {name} shipping, it will cost ${priceusd}")

And to get those it would be that min function from my previous post:

def print_cheapest_shipping(weight):
    priceusd, name = min([(prem_ground_shipping, 'premium ground'),
                          (ground_shipping(weight), 'ground'),
                          (drone_shipping(weight), 'drone')])
    print(f"You should ship using {name} shipping, it will cost ${priceusd}")

(compare that to the codecademy-provided solution)
There’s still the issue of that it has some weird multi-purpose thing going on where it’s 1) figuring out what’s cheapest 2) formatting a string 3) printing it. This is not a well behaved function!