Reputation: 43427
I've got a structure of the form:
>>> items
[([[0, 1], [2, 20]], 'zz', ''), ([[1, 3], [5, 29], [50, 500]], 'a', 'b')]
The first item in each tuple is a list of ranges, and I want to make a generator that provides me the ranges in ascending order based on the starting index.
Since the range-lists are already sorted by their starting index this operation is simple: it is just a sorted merge. I'm hoping to do it with good computational efficiency, so I'm thinking that one good way to implicitly track the state of my merge is to simply pop the front off of the list of the tuple which has the smallest starting index in its range list.
I can use min()
to obtain [0, 1]
which is the first one I want, but how do I get the index of it?
I have this:
[ min (items[i][0]) for i in range(len(items)) ]
which gives me the first item in each list, which I can then min()
over somehow, but it fails once any of the lists becomes empty, and also it's not clear how to get the index to use pop()
with without looking it back up in the list.
To summarize: Want to build generator that returns for me:
([0,1], 'zz', '')
([1,3], 'a', 'b')
([2,20], 'zz', '')
([5,29], 'a', 'b')
([50,500], 'a', 'b')
Or even more efficiently, I only need this data:
[0, 1, 0, 1, 1]
(the indices of the tuples i want to take the front item of)
Upvotes: 48
Views: 103806
Reputation: 1
Yet another option:
max(zip(lst, range(len(lst))))[0]
This seems to be the fastest, together with
max(range(len(lst)), key=lst.__getitem__)
Upvotes: -1
Reputation: 9
The simplest and most efficient way (O(n))
arg_max, maximum = max(list(enumerate(nums)), key=lambda x: x[1]) # Returns both the maximum element and it's index
Upvotes: 0
Reputation: 315
The index of the max of a list:
def argmax(lst):
return lst.index(max(lst))
If there are duplicate max values in lst, this will return the index of the first maximum value found.
Upvotes: 29
Reputation: 2752
Yet another way to get the argmax is:
def argmax(lst):
return max(range(len(lst)), key=lst.__getitem__)
Upvotes: 4
Reputation: 1725
from operator import itemgetter
index, element = max(enumerate(items), key=itemgetter(1))
Return the index of the biggest element in items
and the element itself.
Upvotes: 68
Reputation: 27095
This method finds the index of the maximum element of any iterable and does not require any external imports:
def argmax(iterable):
return max(enumerate(iterable), key=lambda x: x[1])[0]
Upvotes: 62
Reputation: 85432
This works:
by_index = ([sub_index, list_index] for list_index, list_item in
enumerate(items) for sub_index in list_item[0])
[item[1] for item in sorted(by_index)]
Gives:
[0, 1, 0, 1, 1]
In detail. The generator:
by_index = ([sub_index, list_index] for list_index, list_item in
enumerate(items) for sub_index in list_item[0])
list(by_index)
[[[0, 1], 0], [[2, 20], 0], [[1, 3], 1], [[5, 29], 1], [[50, 500], 1]]
So the only thing needed is sorting and getting only the desired index:
[item[1] for item in sorted(by_index)]
Upvotes: 5
Reputation: 43427
I'm not sure what happened but I think everyone's a bit off the mark. I'll blame it on doing a bad job explaining the problem I'm trying to solve. Anyway, here's how much I've gotten:
items[min(range(len(items)), key = lambda x: items[x][0][0])][0].pop(0)
This takes me most of the way there but what's left to deal with is treating the situation where one list has been exhausted. Once that's taken care of it should be trivial to make this a generator as I can just put it in a loop and yield inside it, and also hopefully without too much more work this can be adapted into performing an efficient sort-merge over generators.
>>> items[min(range(len(items)), key = lambda x: items[x][0][0])][0].pop(0)
[0, 1]
>>> items[min(range(len(items)), key = lambda x: items[x][0][0])][0].pop(0)
[1, 3]
>>> items[min(range(len(items)), key = lambda x: items[x][0][0])][0].pop(0)
[2, 20]
>>> items[min(range(len(items)), key = lambda x: items[x][0][0])][0].pop(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
IndexError: list index out of range
Update:
Assembling the proper subset of still-valid-items to min
over is the ticket.
def next_value_in_sections(sections):
while 1:
idxs = []
for i, x in enumerate(sections):
if x[0]:
idxs.append(i)
print idxs
if not idxs:
break
j = min(idxs, key=lambda x: sections[x][0][0])
yield (sections[j][0].pop(0), j)
items = [([[0, 1], [2, 20]], 'zz', ''),
([[1, 3], [5, 29], [50, 500]], 'a', 'b')]
x = next_value_in_sections(items)
for i in x:
print i
Executed:
$ python test.py
[0, 1]
([0, 1], 0)
[0, 1]
([1, 3], 1)
[0, 1]
([2, 20], 0)
[1]
([5, 29], 1)
[1]
([50, 500], 1)
[]
I'll note this still can be improved, the idxs list is being rebuilt each iteration. It does not need to be, but doing that does not improve asymptotic bound... Of course, one has to wonder if we really care about performance, whether the use of the lambda is a good idea either, though I really don't see a way around that without taking apart min
, which is simply a descent into madness.
Upvotes: 0
Reputation: 17829
so this is a real quick and easy way to get that efficient version you are looking for:
a = []
count = 0
for i in items:
for x in i[0]:
#place a list with the index next to it in list a for sorting
a.append((x,count))
#continually grabs the smallest list and returns the index it was in
sort = [a.pop(a.index(min(a)))[1] for i in range(len(a))]
Here is it with your items to show that it works:
>>> items = [([[0, 1], [2, 20]], 'zz', ''), ([[1, 3], [5, 29], [50, 500]], 'a', 'b')]
>>> a = []
>>> count = 0
>>> for i in items:
... for x in i[0]:
... a.append((x,count))
... count += 1
...
>>> sort = [a.pop(a.index(min(a)))[1] for i in range(len(a))]
>>> sort
[0, 1, 0, 1, 1]
Upvotes: 0
Reputation: 31
It's easy if you don't try to use the fact that the internal range lists are sorted
sorted(sum([ [(rng,) + i[1:] for rng in i[0]] for i in items ], []), lambda i: i[0][0])
It sounds like you want a function that returns the index of the smallest value though
def min_idx(l, key=lambda x: x):
min_i, min_key = None, float('inf')
for i, v in enumerate(l):
key_v = key(v)
if key_v < min_key:
mini_i = i
min_key = key_v
return min_i
def merge_items(items):
res = []
while True:
i = min_idx(items, key=lambda i: i[0][0][0])
item = items[i]
res.append((item[0][0],) + item[1:])
return res
Upvotes: 0