6
\$\begingroup\$
def smoothen(heightProfile, travelTime):
    newHeightProfile = []

    # Berechne einen gleitenden Mittelwert
    smoothingInterval = 30 * travelTime

    for i in xrange(smoothingInterval):
        heightProfile.append(heightProfile[-1])

    for i in xrange(len(heightProfile) - smoothingInterval):
        mean = 0
        for j in xrange(i, i + smoothingInterval):
            mean += heightProfile[j]
        newHeightProfile.append(mean/smoothingInterval)

    return newHeightProfile

Input:

  • heightProfile: an array of the size travelTime * 60
  • travelTime: an integer

travelTime can be, for example, 500. heightProfile contains an array of random numbers of the size travelTime * 60. The height profile should describe the height of a random landscape. The function is used to smoothen the values.

However, this code is very slow and expensive, even when using xrange:

  • Tests for travel time: 500
  • CPython: 38.3s
  • Pypy: 0.98s

Questions:

  1. Is there a construct in this code which is very inefficient?
  2. Would this be faster if I were to implement this function in C?

Any other recommendations for improvement are welcome.

\$\endgroup\$
5
\$\begingroup\$

Currently you are calculating the mean each time in the loop, so that's slice + sum for each of the len(heightProfile) - smoothingInterval iterations. We can remove this by calculating the mean of first smoothingInterval items and then inside of the loop simply subtract current ith item from the sum of the previous mean and add (i + smoothingInterval)th item to the sum and calculate the mean for the current window. With this approach we can do this in almost linear time.

Also as you're using Python 2 don't forget to add this line at the top of the file, otherwise normal division will result in integer division.

from __future__ import division

You can replace:

for i in range(smoothingInterval):
        heightProfile.append(heightProfile[-1])

with:

heightProfile.extend([heightProfile[-1]]*smoothingInterval)

If smoothingInterval is very huge then consider passing a generator expression to list.extend, but in most cases [heightProfile[-1]]*smoothingInterval is as fast as you can get:

last_val = heightProfile[-1]
heightProfile.extend(last_val for _ in xrange(smoothingInterval))

Full Code:

from __future__ import division


def smoothen_fast(heightProfile, travelTime):

    smoothingInterval = 30 * travelTime

    heightProfile.extend([heightProfile[-1]]*smoothingInterval)
    # Get the mean of first `smoothingInterval` items  
    first_mean = sum(heightProfile[:smoothingInterval]) / smoothingInterval
    newHeightProfile = [first_mean]

    for i in xrange(len(heightProfile)-smoothingInterval-1):
        prev = heightProfile[i]  # the item to be subtracted from the sum
        new = heightProfile[i+smoothingInterval] # item to be added
        # Calculate the sum of previous items by multiplying
        # last mean with smoothingInterval
        prev_sum = newHeightProfile[-1] * smoothingInterval
        new_sum = prev_sum - prev + new
        mean = new_sum / smoothingInterval
        newHeightProfile.append(mean)

    return newHeightProfile

Timing comparisons:

>>> from random import sample
>>> heightProfile = sample(xrange(1, 10**5), 10**4)
>>> travelTime = 1000
>>> %timeit smoothen(heightProfile[:], travelTime)
1 loops, best of 3: 11.3 s per loop
>>> %timeit smoothen_fast(heightProfile[:], travelTime)
100 loops, best of 3: 2.69 ms per loop

Note that in my function call I am passing a slice of the list because we are modifying the passed list object using its .extend method, which is usually a not a good idea unless the caller expects such modifications.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.