Checking your work: implied total time. Total distance and time in the
input example are 1030 and 120. Your code's output speeds can be used to compute
total time, along the lines sketched below. Unfortunately, that calculation
produces 105 rather than 120, so we know that something is off in your speeds.
speeds = [... your results ...]
last = len(speeds) - 1
tot_time = sum(
(30 if i == last else 100) / s # time = dist / speed
for i, s in enumerate(speeds)
) # 105 seconds
Checking your work: expected partitioning. We can also examine the example
input distances and quickly figure out how the partitioning into distance
chunks of 100 should play out, as shown in the table below. Notice the last
row: it implies that the last four average speeds should be the same -- namely,
the speed associated with 400 in the input data. That speed is 5.
Unfortunately, your output contains only three values of 5 at the end. I did
not bother to figure out where your code went wrong, but my guess is that it
occurred when you needed to merge 3 distances to achieve 100 (10 + 20 + 70).
Input distances | Partitions into 100s
--------------------------------------
230 | 100 100 30
80 | 70 10
300 | 90 100 100 10
20 | 20
400 | 70 100 100 100 30
Your speeds (rounded for display here):
11.5, 11.5, 16.4, 20, 20, 20, 20, 6.5, 5, 5, 5
Very quick code review. Your code isn't in functions (it should be). Your
code has a cramped, hard-to-read layout (it should add spaces around
operators/etc and include blank lines to separate the code into meaningful
sections). Your code relies on primitive data objects -- a list of triples --
and thus forces the reader to interpret and remember list-index numbers (it
should used data objects that are readable and self-documenting).
How I might start a rewrite. Begin with a meaningful object to represent
your data. You have distance-time data representing travel
segments/chunks/spans of a moving object (not sure whether there is a proper
physics term for this). Vocabulary aside, you might define an object holding
distance and time, and then leave speed as a derived attribute. Both for
computing the speed and for other parts of your algorithm, it might be useful
to include a property indicating whether the span is empty (zero time or
distance). For example:
from dataclasses import dataclass
@dataclass
class Span:
dist: float
time: float
@property
def speed(self):
return None if self.empty else self.dist / self.time
@property
def empty(self):
return self.time == 0
Next steps. With that data-object defined, you might add some useful
behaviors to it, such as the ability for one span to merge with all/part of
another (up to some needed amount of distance) or the ability to take a span
and a distance-limit and split it apart into two new spans (the second one
might be empty).
class Span:
...
def merge_with(self, other, needed = float('inf')):
# Compute distance we are adding.
dist = min(other.dist, needed)
ratio = dist / other.dist
# Add to self.
self.dist += dist
self.time += other.time * ratio
# Subtract from other.
...
@classmethod
def split(cls, s, limit):
remainder = s.dist - limit
if remainder > 0:
ratio = remainder / s.dist
return (
cls(limit, s.time * (1 - ratio)),
cls(remainder, s.time * ratio),
)
else:
return (s, cls(0, 0))
With those building blocks in place, writing a function to convert the
raw-triples into spans is not necessarily easy, but the resulting code would be
a lot more readable.