0

I want to search through a list of objects for the lowest number present in an attribute of the objects.

This is easy enough using a list comprehension and the min function. The issue comes when I want to find the index of the object.

class School:
    def __init__(self, name, num_pupils, num_classrooms):
        self.name = name
        self.num_pupils = num_pupils
        self.num_classrooms = num_classrooms

    def students_per_class(self):
        return self.num_pupils / self.num_classrooms

    def show_info(self):
        print(f"{self.name} has {self.students_per_class():.2f} students per class.")

    def string_checker(question):
        valid = False
        while not valid:
            try:
                response = str(input(question))
                if all(character.isalpha() or character.isspace() for character in response):
                    valid = True
                    return response
                else:
                    print("Enter a string containing no numbers of special characters. ")
            except ValueError:
                print("Enter a string containing no numbers of special characters. ")

    def num_checker(question):
        valid = False
        while not valid:
            try:
                response = int(input(question))
                if (response):
                    valid = True
                    return response
                else:
                    print("Enter an integer containing no letters or special characters. ")

        except ValueError:
            print("Enter an integer containing no letters or special characters. ")

    def new_school():
        school_name = string_checker("School Name: ")
        num_pupils = num_checker("Number of Pupils: ")
        num_classrooms = num_checker("Number of Classrooms: ")
        return School(school_name, num_pupils, num_classrooms)


if __name__ == "__main__":
    schools = []
    school = School("Darfield High School", 900, 37)
    schools.append(school)
    school.show_info()

    for i in range(1):
        schools.append(new_school())

    for school in schools:
        school.show_info()

    print(min(school.students_per_class() for school in schools)) 
    # This works fine and prints the value of the lowest number
    print(schools.index(min(school.students_per_class() for school in schools))) 
    # This doesn't work as it tries to find the index of the value returned
    # from the min function as it should.
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Alex Stiles
  • 151
  • 1
  • 13

5 Answers5

3

You can use min's key argument to search by index:

index = min(range(len(schools)), key=lambda i: schools[i].students_per_class())
print(schools[index])

A key provides the values that will be compared instead of the actual sequence. Here, my sequence is range(len(schools)), which is just the indices of all the elements. But instead of finding the minimum index, I am making it so that we find the minimum of schools[i].students_per_class() for each index i.

Alex Stiles
  • 151
  • 1
  • 13
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
3

If you wanted to be able to compare, sort, find the min/ max on the items directly you could use the "dunder" methods. These methods allow you to overload built in functions on classes.

For instance if you had a class like this

class Item:
    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        return self.value < other.value

    def __eq__(self, other):
        return self.value == other.value

You can then create two instances and compare them directly like this,

A = Item(1)
B = Item(2)

print(A < B) # Prints True

or if you had a list of items

items = [A, B]

You can then get the minimum item by going

min_item = min(items)

or the index of it by going

min_item_index = items.index(min(items))

although it may be enough to just have a reference to the minimum item.

tmcnicol
  • 576
  • 3
  • 14
1

Use enumerate to iterate through lists while keeping track of indices:

min(((i, school.students_per_class()) for i, school in enumerate(schools)), key=lambda x: x[1])

(i, school.students_per_class()) is a tuple and using key parameter of min(), we ask Python to find minimum of school.students_per_class(), which subsequently returns it's index along with it.

Read about lambda here.

Austin
  • 25,759
  • 4
  • 25
  • 48
  • 1
    Thank you for your answer. Could you please explain what is happening with enumerate and lambda? I have no idea. Thanks again. Edit: Could you put it into my context. – Alex Stiles Feb 11 '19 at 05:11
  • 1
    You need to swap the order of the result tuple or it will always return the first item, whose index is 0. – kindall Feb 11 '19 at 05:13
  • 1
    @AlexStiles, I have added links to official documentation for further reading on `enumerate` and `lambda`. – Austin Feb 11 '19 at 05:20
1

I think you may be looking for np.argmin function, if you provide a list as an input it will return the index of the minimal element.

in this case it would be:

import numpy as np 
min_ind = np.argmin([school.students_per_class() for school in schools])
print(schools[min_ind])
Roni
  • 151
  • 1
  • 6
0

@Tomas Ordonez

Your answer is here:

def minInt(instanceList):
    sorted_instanceList = sorted(instanceList, key=lambda instance: instance.int)
    minIndex = instanceList.index(sorted_instanceList[0])
    return minIndex
moctarjallo
  • 1,479
  • 1
  • 16
  • 33
  • This assumes the minimum is unique; or else it returns the first such index (not all such indices). – smci Jan 13 '22 at 04:30