Kevin Griffin
Kevin Griffin

Reputation: 14662

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

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.

Upvotes: 723

Views: 1613070

Answers (22)

Nico Schlömer
Nico Schlömer

Reputation: 58881

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()

Upvotes: 30

Veiga
Veiga

Reputation: 222

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])

Upvotes: 5

Ant6n
Ant6n

Reputation: 2007

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)

Upvotes: 57

dr.haz
dr.haz

Reputation: 1637

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

import numpy as np
ind = np.argmax(mylist)

Upvotes: 159

Burak Bağdatlı
Burak Bağdatlı

Reputation: 131

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]

Upvotes: 11

John Misquita
John Misquita

Reputation: 107

Use a numpy array and the argmax() function

a = np.array([1, 2, 3])
b = np.argmax(a)
print(b)  # 2

Upvotes: 6

Simon Hänisch
Simon Hänisch

Reputation: 4968

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.

Upvotes: 9

too much php
too much php

Reputation: 91068

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.

Upvotes: 630

cottontail
cottontail

Reputation: 23371

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);

Upvotes: 14

The Demz
The Demz

Reputation: 7362

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)]

Upvotes: 2

Hadi Mir
Hadi Mir

Reputation: 5133

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

Upvotes: 2

HardRock4Life
HardRock4Life

Reputation: 185

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

df[column].idxmax()

Upvotes: 7

gg349
gg349

Reputation: 22701

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

Upvotes: 736

Dr.Simplisist
Dr.Simplisist

Reputation: 925

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.

Upvotes: 1

alpha_989
alpha_989

Reputation: 5350

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.

Upvotes: 10

Pablo MPA
Pablo MPA

Reputation: 69

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)

Upvotes: 5

user1270589
user1270589

Reputation:

Simple as that :

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

index = stuff.index(max(stuff))

Upvotes: 4

Ishan Tomar
Ishan Tomar

Reputation: 1554

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])

Upvotes: 5

antoninstuppa
antoninstuppa

Reputation: 19

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]

Upvotes: 1

sophist
sophist

Reputation: 41

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)

Upvotes: 4

Matt Anderson
Matt Anderson

Reputation: 19809

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).

Upvotes: 372

hyperbolic
hyperbolic

Reputation: 49

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.

Upvotes: 1

Related Questions