Reinhart Steckin
Reinhart Steckin

Reputation: 67

How can I iterate over a list with two different criteria?

Hey guys I'm facing a problem with a for loop that I couldn't find a solution for online.

Lets say I create two classes, one for people and one for animals. By setting up a loop method for the animal class that iterates over a list of Peoples ages I wanted to find the owner for each pet which has the same age or at least the smallest age gap (this example is just for making the code look simpler). I used a min() function in order to find the person with the smallest age gap which works fine.

But what if I added one more criteria to the selection process? For example I would like to assign the animal only to those people who own less than 3 pets, meaning that even though a person has the smallest age gap, the pet cannot be assigned to that person if it owns already 3 pets. In that case the loop would have to find the next person with the smallest age gap which owns less than 3 pets. In my case A1 would have to be assigned to P1, since it is the person with the smallest age gap which owns less than 3 pets.

Here's my code so far:

class People:
    def __init__(self, name, age, pets_owned):
        self.name=name
        self.age=age
        self.pets_owned=pets_owned

P1=People("John",16, 1)
P2=People("Alex",10, 4)
P3=People("Anna", 20, 3)


People_List=[P1, P2, P3]
People_Age=[P1.age, P2.age, P3.age]

class Animal:
    def __init__(self, name, age, owner):
        self.name=name
        self.age=age
        self.owner=owner

    def find(self):
        closest_age = (min(People_Age, key=lambda x: abs(x - self.age)))
        for a in People_List:
            if a.age ==closest_age and a.pets_owned<3:
                self.owner=a.name
                a.pets_owned+=1
                break

            elif a.age==closest_age and a.pets_owned >=3:
                pass #this is where I`m stuck


        print(self.owner)


A1=Animal("Snoopy",7,"not_owned_yet")


A1.find()

Upvotes: 0

Views: 110

Answers (2)

Paul M.
Paul M.

Reputation: 10799

You can sort based on multiple attributes, by having your sorting key return a tuple. In my example, first we sort based on age gap (higher priority), then by the number of pets owned (lower priority). You don't need to use sorted like I did, since I'm only using it to demonstrate how sorting works based on multiple attributes. You may want to use min with the same key to get the most eligible person. You'll also want to modify assign_new_owner to actually assign a new owner rather (using min) than print people:

class Person:

    def __init__(self, name, age, pets_owned):
        self.name = name
        self.age = age
        self.pets_owned = pets_owned

    def __str__(self):
        return f"{self.name}, age {self.age} owns {self.pets_owned} pet(s)."

class Animal:

    def __init__(self, name, age, owner=None):
        self.name = name
        self.age = age
        self.owner = owner

    def assign_new_owner(self, people):
        sorted_people = sorted(people, key=lambda p: (abs(p.age - self.age), p.pets_owned))
        for person in sorted_people:
            print(person)

def main():

    people = [
        Person("Alex", 16, 0),
        Person("Nigel", 15, 2),
        Person("Fred", 10, 3),
        Person("Tom", 10, 0),
        Person("Tyler", 15, 0),
        Person("Sam", 15, 1)
        ]

    animal = Animal("Snoopy", 10)
    animal.assign_new_owner(people)

    return 0

if __name__ == "__main__":
    import sys
    sys.exit(main())

Output:

 Tom, age 10 owns 0 pet(s).
 Fred, age 10 owns 3 pet(s).
 Tyler, age 15 owns 0 pet(s).
 Sam, age 15 owns 1 pet(s).
 Nigel, age 15 owns 2 pet(s).
 Alex, age 16 owns 0 pet(s).

EDIT: After using min, the code might look more like this:

class Person:

    def __init__(self, name, age, pets_owned):
        self.name = name
        self.age = age
        self.pets_owned = pets_owned

    def __str__(self):
        return f"{self.name}, age {self.age} owns {self.pets_owned} pet(s)."

class Animal:

    def __init__(self, name, age, owner=None):
        self.name = name
        self.age = age
        self.owner = owner

    def __str__(self):
        return f"{self.name}, age {self.age} is owned by {self.owner.name if self.owner else 'no one'}."

    def assign_new_owner(self, people):
        self.owner = min(people, key=lambda p: (abs(p.age - self.age), p.pets_owned))

def main():

    people = [
        Person("Alex", 16, 0),
        Person("Nigel", 15, 2),
        Person("Fred", 10, 3),
        Person("Tom", 10, 0),
        Person("Tyler", 15, 0),
        Person("Sam", 15, 1)
        ]

    animal = Animal("Snoopy", 10)
    print(animal)
    animal.assign_new_owner(people)
    print(animal)

    return 0

if __name__ == "__main__":
    import sys
    sys.exit(main())

Output:

Snoopy, age 10 is owned by no one.
Snoopy, age 10 is owned by Tom.

Upvotes: 0

Ryan Schaefer
Ryan Schaefer

Reputation: 3120

If you know you aren't going to include certain people because of a criteria, I would pre-filter the incoming list to exclude those people. Basically, instead of using a for loop at all, just filter the list, then find the min, then add the pet.

class People:
    def __init__(self, name, age, pets_owned):
        self.name=name
        self.age=age
        self.pets_owned=pets_owned

P1=People("John",16, 1)
P2=People("Alex",10, 4)
P3=People("Anna", 20, 3)


People_List=[P1, P2, P3]
People_Age=[P1.age, P2.age, P3.age]

class Animal:
    def __init__(self, name, age, owner):
        self.name=name
        self.age=age
        self.owner=owner

    def find(self):
        people_with_less_than_3 = filter(lambda x: x.pets_owned<3, People_List) # filter the list to only include people that have less than 3 pets
        try:
            person_with_closest_age = min(people_with_less_than_3, key=lambda x: abs(x.age - self.age)) # change this to return a person as well
        except:
            # do something if no person with < 3 pets
        self.owner = person_with_closest_age.name
        print(self.owner)


A1=Animal("Snoopy",7,"not_owned_yet")


A1.find()

Upvotes: 1

Related Questions