Finding the index of the value which is the min or max in Python

Question:

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)

Asked By: Steven Lu

||

Answers:

 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.

Answered By: MatthieuBizien

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
Answered By: Phil Reinhold

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)]
Answered By: Mike Müller

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]
Answered By: Ryan Saxe

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.

Answered By: Steven Lu

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]
Answered By: 1''

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.

Answered By: stupidsven

Yet another way to get the argmax is:

def argmax(lst):
    return max(range(len(lst)), key=lst.__getitem__)
Answered By: yoniLavi

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 
Answered By: Yoni Kremer

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__)
Answered By: Mi chael
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.