Reputation: 265
I am looking for a method to lower the execution time that Python 3.5 takes for two for-loops in a list comprehension that looks something like this:
[[(k1-k2)**power for k2 in range(m,n)] for k1 in range(m,n)]
Upvotes: 2
Views: 167
Reputation: 6574
So, I started off with your current approach and found that while it does work, it is quite slow. My first attempt involved simply transforming your list comprehension to a suitable approach with numpy
arrays. That was about three times faster than your original approach, but that's when I noticed something quite beautiful: this is a symmetric Toeplitz matrix. From that wiki page:
In linear algebra, a Toeplitz matrix or diagonal-constant matrix, named after Otto Toeplitz, is a matrix in which each descending diagonal from left to right is constant.
I first used the default scipy
implementation of a Toeplitz matrix, but that approach was unnecessary slow for your problem. So I wrote myself a similar approach, which is the third attempt below.
I ran 10 tests for each approach, with each individual test comprising 1000 runs. I set the parameters to m = 10, n = 100
. The results can be found in the table below:
Your approach Numpy #1 Numpy #2 Numpy #3
1 4.573965 1.432406 1.060242 0.186767
2 4.341466 1.432237 1.060404 0.186872
3 4.442438 1.434460 1.144850 0.183120
4 4.318919 1.456928 1.072072 0.185626
5 4.249392 1.450684 1.072217 0.183273
6 4.202730 1.508863 1.070299 0.183019
7 4.224226 1.457543 1.065354 0.183591
8 4.234505 1.432971 1.082438 0.185711
9 4.256538 1.431828 1.080051 0.184290
10 4.241055 1.557204 1.083070 0.185845
AVG 4.308523 1.459512 1.079100 0.184811
STD 0.117433 0.041693 0.024538 0.001521
The scipy
Toeplitz approach (Numpy #2
in the table) is almost four times faster than your current approach, but all these results are dwarfed by the third and final approach: a whopping 23 times speed up over your initial implementation!
Now, since you were interested in the time complexity, I let n
vary, keeping m = 10
. The results for each approach can be found in the figure below:
Clearly, the third approach is the way to go!
Full code:
import timeit
import numpy as np
from scipy.linalg import toeplitz
def your_approach(m, n):
print("\n\tlist comprehension")
k = range(m, n)
for i in range(1, 11):
start = timeit.default_timer()
for j in range(1, 1001):
data_list_comp = [[(k1 - k2) ** 2 for k2 in k] for k1 in k]
print("\t{}".format(timeit.default_timer() - start))
return data_list_comp
def numpy1(m, n):
print("\n\tnumpy")
k_n = np.array(range(m, n))
for i in range(1, 11):
start = timeit.default_timer()
for j in range(1, 1001):
data_numpy = [list((k_n - x) ** 2) for x in k_n]
print("\t{}".format(timeit.default_timer() - start))
return data_numpy
def numpy2(m, n):
print("\n\ttoeplitz")
k_n = np.array(range(0, n - m)) ** 2
toep = toeplitz(k_n)
for i in range(1, 11):
start = timeit.default_timer()
for j in range(1, 1001):
data_numpy = [list(toep[:, i]) for i in range(n - m)]
print("\t{}".format(timeit.default_timer() - start))
return data_numpy
def numpy3(m, n):
print("\n\ttoeplitz2")
k_n = list(np.array(range(0, n - m)) ** 2) # can obviously be done without numpy, but I was a bit lazy. :)
for i in range(1, 11):
start = timeit.default_timer()
for j in range(1, 1001):
data_numpy = [(k_n[i::-1] + k_n[1:-i]) if i != 0 else k_n for i in range(0, n - m)]
print("\t{}".format(timeit.default_timer() - start))
return data_numpy
m = 10
for n in [25, 50, 100, 150, 200]:
assert your_approach(m, n) == numpy1(m, n) == numpy2(m, n) == numpy3(m, n)
Upvotes: 6