Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

Asher Orr
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Asher Orr
Python Development Techdegree Graduate 9,409 Points

Appending random items from one list to another- while excluding items that were already appended to a previous list.

Hi, everyone! I'm completing the Unit 2 Project in the Python TechDegree program.

I have a list (titled "cleaned_player_list") of dictionaries in a file called constants.py. Each dictionary represents information for a basketball player.

There are 18 players, and it looks like this:

[{
        'name': 'Karl Saygan',
        'guardians': 'Heather Bledsoe',
        'experience': True
        'height': 42
    },
    {
        'name': 'Matt Gill',
        'guardians': 'Charles Gill and Sylvia Gill',
        'experience': False
        'height': 40
    },
   ]

The project calls for balancing an equal number of players across 3 teams- the Panthers, the Bandits, and the Warriors.

I made 3 lists- panthers_players, bandits_players, and warriors_players- then wrote this code:

def balance_teams():
    num_players_team = int(len(constants.PLAYERS) / len(constants.TEAMS))
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    bandits_players.append(random.sample(cleaned_player_list, k=num_players_team))
    warriors_players.append(random.sample(cleaned_player_list, k=num_players_team))

This assigns an equal amount of players to each list, but I have a problem: some players are often on multiple teams. My code doesn't account for if a player is already on a team.

I tried this code:

def balance_teams():
    num_players_team = int(len(constants.PLAYERS) / len(constants.TEAMS))
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    bandits_players.append(random.sample(cleaned_player_list not in panthers_players, k=num_players_team))
    warriors_players.append(random.sample(cleaned_player_list not in panthers_players or bandits_players, k=num_players_team))

... and I got this error:

TypeError: Population must be a sequence. For dicts or sets, use sorted(d).

I am totally confused about this. I thought the interpreter would still recognize the population as cleaned_player_list, but understand that the population becomes elements in cleaned_player_list that are not within panthers_players or bandits_players.

Can anyone help me with a solution for appending random items from cleaned_player_list to:

  • bandits_players, as long as the items aren't already on panthers_players: AND:
  • warriors_players, as long as the items aren't already on panthers_players OR bandits_players?

Thank you so much for reading! I appreciate your time.

2 Answers

Steven Parker
Steven Parker
231,269 Points

The "not in" operation is a logical test that will return True or False. It doesn't give you a reduced list.

You could use a list comprehension here, which would apply the logical test to each entry and produce a list that fits the requirements: [player for player in cleaned_player_list if player not in panthers_players]

Also, you cant use logic on just part of a comparison, you must combine complete comparisons. So for the second case: [player for player in cleaned_player_list if player not in panthers_players and player not in bandits_players]

But if these were sets instead of lists, the set operators would allow the expressions to be much simpler: cleaned_player_set - panthers_players and cleaned_player_set - panthers_players - bandits_players.

Two other approaches that might be worth considering is to either remove each player from the original list as they are chosen, or to add an "assigned" attribute to the object model to mark when they have been selected and to avoid selecting them a 2nd time.

Asher Orr
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Asher Orr
Python Development Techdegree Graduate 9,409 Points

Hey Steven, thanks so much for your response! It was very helpful.

I tried the list comprehension method. I wrote this code:

def balance_teams():
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    potential_bandits_players = [player for player in cleaned_player_list if player not in panthers_players]
    bandits_players.append(random.sample(potential_bandits_players, k= num_players_team))

As you can see, my plan was to:

  • add 6 random players from cleaned_player_list to panthers_players.

  • create the list potential_bandits_players using list comprehension. This list would contain all players NOT currently in panthers_players.

  • add 6 random players from potential_bandits_players to bandits_players. This way, I can be sure any players added to bandits_players won't already be on panthers_players.

However, this is not working as intended. I think the problem is with the list comprehension for potential_bandits_players.

When I run my script and call potential_bandits_players, I see an empty list in the console (as opposed to a list that contains all players in cleaned_player_list, except for players in panthers_players.)

Here is my script:

import constants
import random

cleaned_player_list = []

cleaned_player_set = set()

panthers_players = []

bandits_players = []

potential_bandits_players = []

warriors_players = []

num_players_team = int(len(constants.PLAYERS) / len(constants.TEAMS))


def clean_data():
    for player in constants.PLAYERS:
        fixed = {}
        fixed["name"] = player["name"]
        fixed["guardians"] = player["guardians"]
        fixed["experience"] = player["experience"]
        fixed["height"] = player["height"]
        fixed["height"] = int(player["height"][0:2])
        if player["experience"] == "YES":
            fixed["experience"] = True
        else:
            fixed["experience"] = False
        cleaned_player_list.append(fixed)
    return cleaned_player_list

def balance_teams():
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    potential_bandits_players = [player for player in cleaned_player_list if player not in panthers_players]
    bandits_players.append(random.sample(potential_bandits_players, k= num_players_team))

Any idea where I'm going wrong here? I think my balance_teams function would work if the list comprehension method was working for potential_bandits_players, but I'm stuck on how to fix that.


I also have a second question, if you would be kind enough to answer!

When I saw your comment "But if these were sets instead of lists...", I tried to take the contents of cleaned_player_list and turn it into a set.

Cleaned_player_list is a single list that contains multiple dictionaries. I tried this function:

#def convert_to_set():
    cleaned_player_set = set()
    for player in cleaned_player_list:
        cleaned_player_set.add(player)
    return(cleaned_player_set)

Then I got the TypeError: unhashable type: 'dict'.

If I understand correctly, converting cleaned_player_list to a set requires converting its contents (each dictionary) into a hashable data type- like a tuple.

So, I updated the function to this:

def convert_to_set():
    for player in cleaned_player_list:
        cleaned_player_tuple = player.items()
    cleaned_player_set = set(cleaned_player_tuple)
    print(cleaned_player_set)

When I print cleaned_player_set, though, I only get 4 tuples:

>>> convert_to_set()
{('height', 41), ('experience', False), ('name', 'Kimmy Stein'), ('guardians', 'Bill Stein and Hillary Stein')}

Here's my line of thinking as to why this is happening:

  • Sets can't have duplicate values
  • Cleaned_player_tuple contains many tuples representing key:value pairs
  • In those tuples, the "keys" are all the same (name, guardian, experience, and height.)
  • The set constructor function is considering those tuples as duplicates (I'm a bit confused here, as I'm not sure why this is happening. No tuple within cleaned_player_tuple is exactly the same, as the values are different.)

That being said, would you advise that I not use sets in this scenario? Or am I creating my set in a problematic way? It seems that my balance_teams function would be much easier if I could use set operators.

Again, thank you so much! I appreciate you greatly.

Steven Parker
Steven Parker
231,269 Points

Since the lists contain complex objects, simple comparisons (like what "not in" uses) aren't going to work unless you redefine the comparison method (__eq__) for the object class to make it compare data (like just the name) instead of references.

This would probably also be necessary for the set operators to work AND you'd need to define a __hash__ operation.

Steven Parker
Steven Parker
231,269 Points

I think getting remove to work will also require custom comparisons. Until you're ready to redefine the internal comparison mechanisms in a class, how about managing a list of just index values and populating the object lists from it?

def balance_teams():
    available_ids = range(len(constants.PLAYERS))
    # pick first team
    panthers_ids = random.sample(available_ids, num_players_team)
    # remove choices from available list
    available_ids = [id for id in available_ids if id not in panthers_ids]
    # repeat for next team
    bandits_ids = random.sample(available_ids, num_players_team)
    available_ids = [id for id in available_ids if id not in bandits_ids]
    # anyone not picked goes in last team
    warriors_ids = available_ids
    # finally, populate the object lists
    global panthers_players, warriors_players, bandits_players
    panthers_players = [constants.PLAYERS[id] for id in panthers_ids]
    bandits_players  = [constants.PLAYERS[id] for id in bandits_ids]
    warriors_players = [constants.PLAYERS[id] for id in warriors_ids]
Asher Orr
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Asher Orr
Python Development Techdegree Graduate 9,409 Points

Wow, thank you Steven! That is a brilliant solution. It didn't even occur to me to conceptualize solving the problem in that way. It performs the balances_team function perfectly.

I learned so much about Python from this thread, all thanks to you. I appreciate you! Thank you again for your time!

Asher Orr
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Asher Orr
Python Development Techdegree Graduate 9,409 Points

NOTE TO READERS: I intended for this post to be a comment on Steven Parker's answer marked "best answer."

It's supposed to go after the second comment on that answer (... "Since the lists contain complex objects, simple comparisons (like what "not in" uses) aren't going to...")


Hey Steven, I cannot reply to your comment: "Since the lists contain complex objects..."

So I will respond here:

Thank you again! You are right. I tried this list comprehension:

def balance_teams():
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    remaining_players = [x for x in cleaned_player_list if x not in panthers_players]
    bandits_players.append(random.sample(remaining_players, k=num_players_team))

When I ran this in the interactive shell, remaining_players would return ALL players in my original list (cleaned_player_list.)

I am still a beginner in Python, and I think that redefining the comparison method and defining a new hash operation is outside my scope of knowledge at the moment.

Now, I think I must:

  1. remove each player from the original list as they are chosen.

OR:

  1. Add an "assigned" attribute to the object model to mark when they have been selected and to avoid selecting them a 2nd time.

I tried option 1:

def balance_teams():
    panthers_players.append(random.sample(cleaned_player_list, k=num_players_team))
    remaining_players = cleaned_player_list.remove(panthers_players)
    bandits_players.append(random.sample(remaining_players, k=num_players_team))

However, this returns ValueError: list.remove(x): x not in list.

Do you have any suggestions for how I could generate a new list of players from cleaned_player_list (EXCLUDING the players in panthers_players)..

without using list comprehension, as that is not working for this particular list?

Thank you again for your time!

Steven Parker
Steven Parker
231,269 Points

To add a comment onto another comment, use the "Add Comment" on the original answer.
See the new comment I added to my answer that way.