Reputation: 352
I'm writing a function that takes in a list of integers and returns a list of relative positioned elements.
That is to say, if my input into said function is [1, 5, 4] the output would be [0, 2, 1], since 1 is the lowest element, 5 is the highest and 4 in the middle, all elements are unique values, or a set()
But code speaks, the function i have so far is
def relative_order(a):
rel=[]
for i in a:
loc = 0
for v in a:
if i > v:
loc += 1
rel.append(loc)
return rel
It does work, but since i'm sending big lists into this function, and i have to compare each element to all elements in each iteration it's taking ~5sec with a list of 10.000 elements.
My question is how can i improve the speed on said function and maybe be a bit more Pythonic, i tried comprehension lists, but my Python skills are lacking and i only came up with an imperative way of implementing this problem.
Upvotes: 9
Views: 5526
Reputation: 269
Your question is about sorting. I would recommend the use of Numpy or 'Numeric Python'. Numpy is a Python module that is optimised for "fast, compact, multidimensional array faciliities". It is the package of choice for scientific computing in Python. http://www.numpy.org/
import numpy as np
input_array = np.array([1, 5, 4])
sorted_indices = np.argsort(input_array)
print sorted_indices
#[0 2, 1]
I have also added profiler output based on an array of size 50000
. It shows this method is (around 4x) faster than using the Python sorted
function as per earlier answers.
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.009 0.009 0.009 0.009 {method 'argsort' of 'numpy.ndarray' objects}
1 0.034 0.034 0.034 0.034 {sorted}
Warning: Commentary suggested the answer is not inline with the authors function. This is true. I guess the whole point of argsort is that:
sorted_array = input_array[sorted_indices]
gives you a sorted array.
The OP is, curious to my mind, asking for a result which requires a sorted array to be available via:
for i, val in enumerate(sorted_indices):
sorted_array[val] = input_array[i]
Upvotes: -1
Reputation: 236034
This can be written as a list comprehension like this:
lst = [1, 5, 4]
s = sorted(lst)
[s.index(x) for x in lst]
=> [0, 2, 1]
And here's another test, using @frb's example:
lst = [10, 2, 3, 9]
s = sorted(lst)
[s.index(x) for x in lst]
=> [3, 0, 1, 2]
Upvotes: 12
Reputation: 9044
def relative_order(a):
l = sorted(a)
# hash table of element -> index in ordered list
d = dict(zip(l, range(len(l))))
return [d[e] for e in a]
print relative_order([1, 5, 4])
print relative_order([2, 3, 1])
print relative_order([10, 2, 3, 9])
[0, 2, 1]
[1, 2, 0]
[3, 0, 1, 2]
the algorithm should be as efficient as sort, but use additional space.
Upvotes: 1
Reputation: 142176
Here's another go that should be more efficient that keeping .index
'ing into the list as it's stated that no duplicate values will occur, so we can do the lookup O(1) instead of linear... (and actually meets the requirements):
>>> a = [10, 2, 3, 9]
>>> indexed = {v: i for i, v in enumerate(sorted(a))}
>>> map(indexed.get, a)
[3, 0, 1, 2]
Upvotes: 11
Reputation: 21
The method you have a̶n̶d̶ ̶t̶h̶e̶ ̶c̶u̶r̶r̶e̶n̶t̶ ̶a̶n̶s̶w̶e̶r̶ takes order n^2 time.
This should work in log(n) time:
def relative_order(a): positions = sorted(range(len(a)), key=lambda i: a[i]) return sorted(range(len(a)), key = lambda i: positions[i])
It's still order log(n) and so should work for your large lists too.
Edit:
Outside of lambda.
Upvotes: 1