Reputation: 14662
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
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):
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
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
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
Reputation: 1637
Use NumPy's np.argmin()
or np.argmax()
functions:
import numpy as np
ind = np.argmax(mylist)
Upvotes: 159
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
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
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
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
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.
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
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
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
Reputation: 185
Pandas has now got a much more gentle solution, try it:
df[column].idxmax()
Upvotes: 7
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:
numpy
arrayI 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
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
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
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.
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)
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
Reputation:
Simple as that :
stuff = [2, 4, 8, 15, 11]
index = stuff.index(max(stuff))
Upvotes: 4
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
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
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
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
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