Getting the index of the returned max or min item using max()/min() on a list
Asked Answered
J

22

707

I'm using Python's max and min functions on lists for a minimax algorithm, and I need the index of the value returned by max() or min(). In other words, I need to know which move produced the max (at a first player's turn) or min (second player) value.

for i in range(9):
    new_board = current_board.new_board_with_move([i / 3, i % 3], player)

    if new_board:
        temp = min_max(new_board, depth + 1, not is_min_level)  
        values.append(temp)

if is_min_level:
    return min(values)
else:
    return max(values)

I need to be able to return the actual index of the min or max value, not just the value.

Jinja answered 18/3, 2010 at 23:20 Comment(1)
The builtin divmod exists to prevent having to say [i / 3, i % 3] much.Petty
A
608

Find the minimum value with min() then find that value's index with .index():

values.index(min(values))

Or the maximum:

values.index(max(values))

If your list contains repeats of the minimum or maximum value this will return the index of the first one.

Alcove answered 18/3, 2010 at 23:23 Comment(8)
@KevinGriffin, Note that this gets you only one of possibly several occurrences of the minimum/maximum. This may not be what you want, for example if it's possible to increase your gain the same two ways, but one of them hurts the other player more. I do not know if this is a case you need to consider.Petty
@Kashyap It's actually O(N), not O(N^2). In the min case, first min(values) is evaluated, which is O(N), then values.index() is called, which is also O(N). O(N) + O(N) = O(N). The argument to index is only evaluated once. It's equivalent to: tmp = min(values); return values.index(tmp)Losing
@too much php what to do when there is repetition of elements.?Spock
@ShashiTunga [list].index() returns only the first occurence of something, it is not guaranteed that it is exclusive, the minimum value might not be unique within the listUrceolate
@TomKarzes This is true, however it is still (potentially) twice as slow as it needs to be. If the minimum of a list is calculated by doing a linear scan, the index could be calculated in the same way. Ideally, the list should only be scanned once.Marzi
This is the fastest for single value AFAIK.Eliciaelicit
The Python documentation states that the min is the first smallest element found in the list. Therefore, it is consistent with index(), which returns the first element in the list that matches the key. I also find this method to be much more readable than using itemgetter.Phooey
This is the fastest way to do it (even for a list with 500,000 elements) on Python 3.11Bootblack
E
721

Say that you have a list values = [3,6,1,5], and need the index of the smallest element, i.e. index_min = 2 in this case.

Avoid the solution with itemgetter() presented in the other answers, and use instead

index_min = min(range(len(values)), key=values.__getitem__)

because it doesn't require to import operator nor to use enumerate, and it is always faster(benchmark below) than a solution using itemgetter().

If you are dealing with numpy arrays or can afford numpy as a dependency, consider also using

import numpy as np
index_min = np.argmin(values)

This will be faster than the first solution even if you apply it to a pure Python list if:

  • it is larger than a few elements (about 2**4 elements on my machine)
  • you can afford the memory copy from a pure list to a numpy array

as this benchmark points out: enter image description here

I have run the benchmark on my machine with python 2.7 for the two solutions above (blue: pure python, first solution) (red, numpy solution) and for the standard solution based on itemgetter() (black, reference solution). The same benchmark with python 3.5 showed that the methods compare exactly the same of the python 2.7 case presented above

Edington answered 6/8, 2012 at 9:43 Comment(2)
The complicated one-liner is fast, but it's also hard to understand. Unless your app is too slow, and from profiling you know the simple values.index(max(values)) is too slow, preferring complicated code is micro-optimization.Earnestineearnings
I did a little benchmark in this answer that includes list.index (because the above benchmark doesn’t include it), which shows that the method using list.index is almost twice as fast as this method.Prop
A
608

Find the minimum value with min() then find that value's index with .index():

values.index(min(values))

Or the maximum:

values.index(max(values))

If your list contains repeats of the minimum or maximum value this will return the index of the first one.

Alcove answered 18/3, 2010 at 23:23 Comment(8)
@KevinGriffin, Note that this gets you only one of possibly several occurrences of the minimum/maximum. This may not be what you want, for example if it's possible to increase your gain the same two ways, but one of them hurts the other player more. I do not know if this is a case you need to consider.Petty
@Kashyap It's actually O(N), not O(N^2). In the min case, first min(values) is evaluated, which is O(N), then values.index() is called, which is also O(N). O(N) + O(N) = O(N). The argument to index is only evaluated once. It's equivalent to: tmp = min(values); return values.index(tmp)Losing
@too much php what to do when there is repetition of elements.?Spock
@ShashiTunga [list].index() returns only the first occurence of something, it is not guaranteed that it is exclusive, the minimum value might not be unique within the listUrceolate
@TomKarzes This is true, however it is still (potentially) twice as slow as it needs to be. If the minimum of a list is calculated by doing a linear scan, the index could be calculated in the same way. Ideally, the list should only be scanned once.Marzi
This is the fastest for single value AFAIK.Eliciaelicit
The Python documentation states that the min is the first smallest element found in the list. Therefore, it is consistent with index(), which returns the first element in the list that matches the key. I also find this method to be much more readable than using itemgetter.Phooey
This is the fastest way to do it (even for a list with 500,000 elements) on Python 3.11Bootblack
M
370

You can find the min/max index and value at the same time if you enumerate the items in the list, but perform min/max on the original values of the list. Like so:

import operator
min_index, min_value = min(enumerate(values), key=operator.itemgetter(1))
max_index, max_value = max(enumerate(values), key=operator.itemgetter(1))

This way the list will only be traversed once for min (or max).

Musgrave answered 19/3, 2010 at 0:18 Comment(3)
Or use a lambda: key=lambda p: p[1]Sachi
min([(j, i) for i, j in enumerate(values)]) to avoid expensive function calls.Workshop
@Workshop Tested all three methods (for in loop, lambda, and itemgetter) just now for a array of 1000 values with "%timeit", and itemgetter was the fastest, followed by for-in, and lambda in the last place.Tenuto
A
156

Use NumPy's np.argmin() or np.argmax() functions:

import numpy as np
ind = np.argmax(mylist)
Agenesis answered 23/11, 2012 at 17:41 Comment(1)
In case of multiple occurrences of the maximum values, the indices corresponding to the first occurrence are returned.Current
A
55

Turn the array of values into an array of (value,index)-pairs, and take the max/min of that. This returns the largest/smallest index that has the max/min (i.e. pairs are compared by first comparing the value, and then comparing the index if the values are the same).

values = [3, 5, 4, 5]
m, i = max((v, i) for i, v in enumerate(values))
print((m, i))  # (5, 3)
Agitprop answered 21/12, 2012 at 11:53 Comment(1)
Using this code, min() returns the index of the first minimum and max() returns the index of the last maximum, which could be surprising because it's inconsistent.Bootblack
E
30

I benchmarked the main answers using perfplot (a pet project of mine) on Python 3.11 and it turns out that

values.index(min(values))

is the fastest (lower is better):

enter image description here

unless your array is already a numpy array.


Code for generating the plot:

import numpy as np
import operator
import perfplot


def min_enumerate(a):
    return min(enumerate(a), key=lambda x: x[1])[0]

def min_enumerate_itemgetter(a):
    min_index, min_value = min(enumerate(a), key=operator.itemgetter(1))
    return min_index

def getitem(a):
    return min(range(len(a)), key=a.__getitem__)

def np_argmin(a):
    return np.argmin(a)

def index_min(a):
    return a.index(min(a))


b = perfplot.bench(
    setup=lambda n: np.random.rand(n).tolist(),
    kernels=[
        min_enumerate,
        min_enumerate_itemgetter,
        getitem,
        np_argmin,
        index_min,
    ],
    labels = [
        "key=lambda x: x[1]",
        "key=itemgetter(1)",
        "key=.__getitem__",
        "np.argmin()",
        ".index()"
    ],
    xlabel="len(list)",
    n_range=[2**k for k in range(20)],
)
b.show()
Edible answered 23/5, 2017 at 7:58 Comment(0)
L
11

If you need all the indexes of the minimum (because the minimum might appear more than once in the list):

minval = min(mylist)
ind = [i for i, v in enumerate(mylist) if v == minval]
Laurellaurella answered 14/4, 2011 at 18:22 Comment(1)
I really appreciate this answer as it deals with multiple occurences and most of the other answers deal with just one occurence, which is unusable for me. +1Mellen
P
11

There are two answers (1, 2) that include benchmark but for some reason, neither of them include list.index() into their benchmark, even though it was suggested in the accepted answer that was posted at least 2 years before these answers.

list.index() is the fastest option given on this page, including enumerate (all versions that involve it), __getitem__ and numpy.argmin.

Moreover, if the list has a non-unique minimum value and you want to get all indices where the minimum value occurs, list.index in a while-loop outperforms other options such as numpy and enumerate as well. Note that you can limit its search to begin from a particular index by passing the starting point (which is the second argument to list.index), which is crucial for performance because we don't want to search from the beginning in every iteration of the while-loop.

# get the index of the minimum value
my_list = [1, 2, 0, 1]
idxmin = my_list.index(min(my_list))
print(idxmin)   # 2


# get all indices where the min value occurs
my_list = [1, 2, 3, 1]
idxmins = []
min_val = min(my_list)
pos = -1
while True:
    try:
        pos = my_list.index(min_val, pos+1)
        #                            ^^^^^   <---- pick up where we left off in the previous iteration
        idxmins.append(pos)
    except ValueError:
        break

print(idxmins)   # [0, 3]

The following benchmarks (performed on Python 3.11.4 and numpy 1.25.2) show that list.index is almost twice as fast as all other options no matter the length of the list. The left graph also shows that getitem performs the same as enumerate (and numpy.argmin) for long lists, which shows that gg349 and Nico's benchmarks are outdated.

The right graph shows that if the minimum value is non-unique and we want to find all indices of the minimum value, then list.index in a while loop as outlined above performs so much better than competing options involving enumerate or numpy, especially for long lists.

benchmark

The code used to produce the figure above:

from operator import itemgetter
import numpy as np
import matplotlib.pyplot as plt
import perfplot


def enumerate_1(a):
    return min(enumerate(a), key=itemgetter(1))[0]


def np_argmin_1(a):
    return np.argmin(a)


def getitem(a):
    return min(range(len(a)), key=a.__getitem__)


def list_index_1(a):
    return a.index(min(a))


def enumerate_2(a):
    min_val = min(a)
    return [i for i, v in enumerate(a) if v == min_val]


def np_argmin_2(a):
    arr = np.array(a)
    return np.arange(len(a))[arr==arr.min()]


def list_index_2(a):
    result = []
    min_val = min(a)
    pos = -1
    while True:
        try:
            pos = a.index(min_val, pos+1)
            result.append(pos)
        except ValueError:
            break
    return result


kernels_list = [[enumerate_1, list_index_1, np_argmin_1, getitem], 
                [enumerate_2, list_index_2, np_argmin_2]]
n_range = [2**k for k in range(1, 20)]
su = lambda n: list(range(n, 0, -1))
titles = ['Get index of a unique min value', 
          'Get indices of a non-unique min value']
labels = ['enumerate', 'list_index', 'np_argmin', 'getitem']
xlabel = 'List length'


fig, axs = plt.subplots(1, 2, figsize=(12, 5), facecolor='white', dpi=60)
for ax, ks, t in zip(axs, kernels_list, titles):
    plt.sca(ax)
    perfplot.plot(ks, n_range, su, None, labels, xlabel, t, relative_to=1)
    ax.xaxis.set_tick_params(labelsize=13)
plt.setp(axs, ylim=(0, 5), yticks=range(1, 6), 
         xlim=(1, 1100000), xscale='log', xticks=[1, 100, 10000, 1000000]);
fig.tight_layout();
fig.savefig('benchmark.png', dpi=60);
Prop answered 26/8, 2023 at 3:53 Comment(6)
I was suspicious that you were putting the minimum element first and that's why it was so fast, but you're not, .index() is indeed the fastest way and it's the most concise/clear to boot.Bootblack
@BorisVerkhovskiy yeah, I was kind of surprised to see an overcomplicated solution as a top answer based on a benchmark that doesn’t include .index(), so wanted to point out that .index() is the best way. I know my answer is very new and buried way down but thanks for scrolling all the way here :-)Prop
it might be better to edit the top answer instead, we'd need to re-write the code to recreate the benchmark graph though (and preferably add it, commented out, into the answer for future reference).Bootblack
I updated the benchmark answerBootblack
@BorisVerkhovskiy I don’t think that edit adheres to SO guidelines as it changes the author’s intent. Also makes the main point of my answer their point, which I don’t think is fair.Prop
The author's intent was to benchmark the answers, he just missed one.Bootblack
T
10

After you get the maximum values, try this:

max_val = max(list)
index_max = list.index(max_val)

Much simpler than a lot of options.

Tarrasa answered 11/1, 2018 at 16:57 Comment(1)
Duplicate of @too much php's answerBootblack
A
9

This is possible using the built-in enumerate() and max() functions and the optional key= argument of the max() function and a simple lambda expression:

values = [1, 5, 10]
max_index, max_value = max(enumerate(values), key=lambda v: v[1])
# => (2, 10)

In the docs for max() it says that the key= argument expects a function like in the list.sort() function. Also see the Sorting HOW TO.

It works the same for min(). Btw, it returns the first max/min value.

Alcoholism answered 28/10, 2016 at 9:35 Comment(0)
R
7

Use a numpy array and the argmax() function

a = np.array([1, 2, 3])
b = np.argmax(a)
print(b)  # 2
Running answered 29/1, 2018 at 18:7 Comment(1)
Duplicate of @dr.haz's answerBootblack
C
7

Pandas has now got a much more gentle solution, try it:

df[column].idxmax()

Chemist answered 10/8, 2020 at 9:44 Comment(0)
M
6

Say you have a list such as:

a = [9,8,7]

The following two methods are pretty compact ways to get a tuple with the minimum element and its index. Both take a similar time to process. I better like the zip method, but that is my taste.

zip method

element, index = min(list(zip(a, range(len(a)))))

min(list(zip(a, range(len(a)))))
(7, 2)

timeit min(list(zip(a, range(len(a)))))
1.36 µs ± 107 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

enumerate method

index, element = min(list(enumerate(a)), key=lambda x:x[1])

min(list(enumerate(a)), key=lambda x:x[1])
(2, 7)

timeit min(list(enumerate(a)), key=lambda x:x[1])
1.45 µs ± 78.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Milomilon answered 26/2, 2018 at 10:29 Comment(1)
"zip method" is a duplicate of @sophist's answer and "enumerate method" is a duplicate of @Simon Hänisch's answerBootblack
N
5

Use numpy module's function numpy.where

import numpy as n
x = n.array((3,3,4,7,4,56,65,1))

For index of minimum value:

idx = n.where(x==x.min())[0]

For index of maximum value:

idx = n.where(x==x.max())[0]

In fact, this function is much more powerful. You can pose all kinds of boolean operations For index of value between 3 and 60:

idx = n.where((x>3)&(x<60))[0]
idx
array([2, 3, 4, 5])
x[idx]
array([ 4,  7,  4, 56])
Newborn answered 16/4, 2015 at 8:29 Comment(4)
index in python starts at 0. index returned shall be 6 (for 65), while your code returns 7 (OP's question was "Getting the index ...")Erdah
In the command, I have queried for index of minimum value (here: 1) whose index IS 7. 65 is the maximum value of elements in the array. If you type: n.where(x==x.max())[0] you will get index of max. value which is 65 here. Its index will come out to be 6Newborn
use of numpy: probably prohibited in this application. But if you are going to use numpy, you're much better of just using argmin() instead of what you did here.Avaavadavat
Thanks @Avaavadavat I will check it out.Newborn
A
5

You can pass a lambda as the key= argument to max()/min():

max_index = max(range(len(my_list)), key=lambda index: my_list[index])
Autocorrelation answered 24/1, 2017 at 22:41 Comment(1)
Very clean! And unlike the accepted answer, this is true O(n), right? I know that O(2n) is considered O(n), but for very large n it can be noticeably slower.Marcelina
J
4

Why bother to add indices first and then reverse them? Enumerate() function is just a special case of zip() function usage. Let's use it in appropiate way:

my_indexed_list = zip(my_list, range(len(my_list)))

min_value, min_index = min(my_indexed_list)
max_value, max_index = max(my_indexed_list)
Journalism answered 17/3, 2015 at 13:45 Comment(1)
Using this code with a list that repeats the min or the max value (eg my_list = [1, 2, 2]), min() returns the index of the first minimum whereas max() returns the index of the last maximum, which could be surprising because it's inconsistent.Bootblack
B
4

Simple as that :

stuff = [2, 4, 8, 15, 11]

index = stuff.index(max(stuff))
Byrom answered 12/3, 2017 at 19:51 Comment(1)
Duplicate of @too much php's answerBootblack
T
2

https://docs.python.org/3/library/functions.html#max

If multiple items are maximal, the function returns the first one encountered. This is consistent with other sort-stability preserving tools such as sorted(iterable, key=keyfunc, reverse=True)[0]

To get more than just the first encountered, use the sort method.

import operator

x = [2, 5, 7, 4, 8, 2, 6, 1, 7, 1, 8, 3, 4, 9, 3, 6, 5, 0, 9, 0]

min = False
max = True

min_val_index = sorted( list(zip(x, range(len(x)))), key = operator.itemgetter(0), reverse = min )

max_val_index = sorted( list(zip(x, range(len(x)))), key = operator.itemgetter(0), reverse = max )


min_val_index[0]
>(0, 17)

max_val_index[0]
>(9, 13)

import ittertools

max_val = max_val_index[0][0]

maxes = [n for n in itertools.takewhile(lambda x: x[0] == max_val, max_val_index)]
Tribble answered 17/9, 2015 at 20:42 Comment(1)
@Burak Bağdatlı's answer is a more efficient way to do this if you just need the minimum or maximum and don't care about the 2nd smallest or largest value, etc.Bootblack
M
2

Assuming you have a following list my_list = [1,2,3,4,5,6,7,8,9,10] and we know that if we do max(my_list) it will return 10 and min(my_list) will return 1. Now we want to get the index of the maximum or minimum element we can do the following.

my_list = [1,2,3,4,5,6,7,8,9,10]

max_value = max(my_list) # returns 10
max_value_index = my_list.index(max_value) # retuns 9

#to get an index of minimum value

min_value = min(my_list) # returns 1
min_value_index = my_list.index(min_value) # retuns 0
Mesh answered 14/11, 2020 at 12:5 Comment(1)
Duplicate of @too much php's answerBootblack
R
1

Just a minor addition to what has already been said. values.index(min(values)) seems to return the smallest index of min. The following gets the largest index:

    values.reverse()
    (values.index(min(values)) + len(values) - 1) % len(values)
    values.reverse()

The last line can be left out if the side effect of reversing in place does not matter.

To iterate through all occurrences

    indices = []
    i = -1
    for _ in range(values.count(min(values))):
      i = values[i + 1:].index(min(values)) + i + 1
      indices.append(i)

For the sake of brevity. It is probably a better idea to cache min(values), values.count(min) outside the loop.

Rory answered 6/4, 2012 at 16:9 Comment(1)
reversed(…) instead of ….reverse() is likely preferable as it doesn't mutate and returns a generator anyway. And all occurrences could also be minv = min(values); indices = [i for i, v in enumerate(values) if v == minv]Artiste
H
1

A simple way for finding the indexes with minimal value in a list if you don't want to import additional modules:

min_value = min(values)
indexes_with_min_value = [i for i in range(0,len(values)) if values[i] == min_value]

Then choose for example the first one:

choosen = indexes_with_min_value[0]
Headset answered 9/8, 2016 at 12:17 Comment(0)
F
1

What about this:

a=[1,55,2,36,35,34,98,0]
max_index=dict(zip(a,range(len(a))))[max(a)]

It creates a dictionary from the items in a as keys and their indexes as values, thus dict(zip(a,range(len(a))))[max(a)] returns the value that corresponds to the key max(a) which is the index of the maximum in a. I'm a beginner in python so I don't know about the computational complexity of this solution.

Freightage answered 12/10, 2019 at 9:10 Comment(1)
This is about 3-5 times slower than the right way to do it and uses twice the memory.Bootblack

© 2022 - 2025 — McMap. All rights reserved.