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