Code style query: multiple assignments in if/elif tree

Steven D'Aprano steve at pearwood.info
Tue Apr 1 00:26:01 EDT 2014


On Tue, 01 Apr 2014 01:33:09 +1100, Chris Angelico wrote:

> Call this a code review request, if you like. I'm wondering how you'd go
> about coding something like this.

I wouldn't. I'd start off by analysing the problem, and putting it into 
the simplest format possible, and *then* start writing code if and only 
if needed. See below.

The first mistake of computational mathematics is to do the computation 
before the mathematics, and the Holy Grail is to avoid the computation 
altogether.

 
> Imagine you're in a train, and the brakes don't apply instantly. The
> definition, in the interests of passenger comfort, 

Ah, you're using comfort in the modern sense, i.e. what people used to 
call discomfort :-P

The scenario you describe has (effectively) infinite rate-of-change-of-
acceleration, often called "jerk". (A jerk is a rapid change in 
acceleration.) Human comfort is (within reasonable limits) more affected 
by jerk than acceleration. The passengers will feel three quite 
distinctive jerks, one when the brakes are first applied (which is 
probably reasonable), then one at 1s, then again at 2s. That's not 
comfortable by any stretch of the imagination.


> is that the first
> second of brake application has an acceleration of 0.2 m/s/s, the next
> second has 0.425 m/s/s, and thereafter full effect of 0.85 m/s/s. 

In mathematics, this is called  hybrid function, and is usually written 
like this:

         g(t) for t < 0
f(t) = { 42 for t == 0
         h(t) for t > 0

or something similar. (The opening brace { ought to be large enough to 
cover all three lines, and there is no closing brace.)

In your case, you have three constant functions.


> You
> have a state variable that says whether the brakes have just been
> applied, have already been applied for at least two seconds, or haven't
> yet been applied at all. 

I wouldn't model it that way. Especially since you've missed at least one 
state :-) I'd model the acceleration as a function of time, otherwise you 
have to convert a time into a state. Nevertheless, if you insist, we can 
use a state variable instead:

          0 for state == "BRAKES NOT APPLIED YET"
accel = { 0.2 for state == "BRAKES ON FOR BETWEEN 0 AND 1 SECOND"
          0.425 for STATE == BRAKES ON FOR BETWEEN 1 AND 2 SECOND"
          0.85 for state == "BRAKES ON FOR MORE THAN 2 SECONDS"

Implied, but not stated, is that once the train stops, acceleration also 
goes to zero -- the train does not start moving backwards.

Haskell has nifty pattern-matching syntax for this that looks quite close 
to the mathematical hybrid function syntax, but in Python, we're limited 
to explicitly using an if. If I were coding this, and I'm not, I'd wrap 
it in a function. One advantage of a state variable rather than a 
continuous time function is that we can do this:

def accel(state):
    return {NO_BRAKING: 0.0,
            LOW_BRAKING: 0.2,
            MID_BRAKING: 0.425,
            HIGH_BRAKING: 0.85}[state]


which is simple enough to skip using a function in the first place, and 
just use the dict lookup directly. But for a more general question you'll 
want acceleration as a function of time.

If you prefer if...elif over a dict, I'd still hide it in a function.


> Problem: Work out how far you'll go before the
> brakes reach full power, and how fast you'll be going at that point.

Given that problem, we actually only care about LOW_BRAKING and 
MID_BRAKING. The problem becomes quite simple:

At t=0, the train is travelling at u m/s and the brakes are applied with 
acceleration of 0.2m/s^2 for one second, then 0.425m/s^2 for an 
additional one second. What is the speed of the train after those two 
seconds, and the distance travelled.

This becomes a simple question for the four standard equations of motion:

(1) v = u + at
(2) s = 1/2(u + v)t
(3) s = ut + 1/2(at^2)
(4) v^2 = u^2 + 2as

Only (1) and (3) are needed.


The distance travelled in the first second is:

    s1 = u - 0.1  m

and the speed of the train at that time becomes:

    v = u - 0.2  m/s

Applying this for the second second gives:

    s2 = (u - 0.2) - 0.2125  m

    v = (u - 0.2) - 0.425  m/s

and the total distance:

    s = s1 + s2


No programming required, just a calculator :-)

Given that solving the entire problem is barely five lines, writing code 
to do so is probably unnecessary.

The only thing I haven't done is check that the train isn't travelling so 
slowly that it comes to a complete halt within two seconds. I leave that 
as an exercise. (Hint: if the final speed is negative, the train came to 
a halt.)


> Here's how I currently have the code. The variable names are a tad long,
> as this was also part of me teaching my brother Python.

Whatever you used to post this, ate the indentation.

> # Already got the brakes fully on
> if mode=="Brake2": distance_to_full_braking_power, speed_full_brake =
> 0.0, curspeed
> # The brakes went on one second ago, they're nearly full elif
> mode=="Brake1": distance_to_full_braking_power, speed_full_brake =
> curspeed - 0.2125, curspeed - 0.425 # Brakes aren't on.
> else: distance_to_full_braking_power, speed_full_brake = (curspeed -
> 0.1) + (curspeed - 0.4125), curspeed - 0.625 # If we hit the brakes now
> (or already have hit them), we'll go another d meters and be going at s
> m/s before reaching full braking power.

Using sequence unpacking just to save a newline is naughty :-)

dist, speed = (curspeed - 0.1) + (curspeed - 0.4125), curspeed - 0.625

is an abuse of syntax, not far removed from:

dist = (curspeed - 0.1) + (curspeed - 0.4125); speed = curspeed - 0.625

It will be much more comprehensible written as two statements. 


 
> But I don't like the layout. I could change it to a single assignment
> with expression-if, but that feels awkward too. How would you lay this
> out?
> 
> (Note that the "else" case could have any of several modes in it, so I
> can't so easily use a dict.)

It could have, but doesn't. Is your aim to build a general purpose 
Newtonian linear motion solver, or to solve this one problem?


-- 
Steven



More information about the Python-list mailing list