0

I'm following an online Python project and having some issues. I don't feel like my code is very 'pythonic' and the code is not working correctly.

The program is supposed to read a CSV file of soccer players. The output is meant to split the players into three different soccer teams. Each team is supposed to have the same amount of players and the same amount with Soccer Experience. Out of the 18 players there are 9 with experience so that would equate to six team members with three of the six having experience. I can get it so each team has six players but the part i'm getting tripped up on is the experience part. Some of the teams are ending up with three but some are not. Here is what I've tried so far:

import csv
import random

def assign_players():
    with open('soccer_players.csv') as csvfile:
        soccerplayers = csv.DictReader(csvfile)
        players = list(soccerplayers)

        target = open('teams.txt', 'w')

        raptors=[]
        dragons=[]
        sharks=[]

        for player in players:
            experienced_player = 0
            if len(raptors)<6:
                raptors.append(player)
                if player['Soccer Experience'] == 'YES':
                    experienced_player+=1
                    if experienced_player >3:
                        break

            elif len(dragons)<6:
                dragons.append(player)
                if player['Soccer Experience'] == 'YES':
                    experienced_player+=1
                    if experienced_player >3:
                        break

            else:
                sharks.append(player)
                # if player['Soccer Experience'] == 'YES':
                #     experienced_player+=1
                #     if experienced_player >3:
                #         break

        target.write("Raptors")
        target.write("\n")
        for raptor in raptors:
            target.write(str(raptor["Name"])+ ', '),
            target.write(str(raptor["Soccer Experience"])+ ', '), " ",
            target.write(str(raptor["Guardian Name(s)"])+ ' '), " ",
            target.write("\n")

        target.write("\n")

        target.write("Dragons")
        target.write("\n")
        for dragon in dragons:
            target.write(str(dragon["Name"]) + ', '),
            target.write(str(dragon["Soccer Experience"]) + ', '), " ",
            target.write(str(dragon["Guardian Name(s)"]) + ' '), " ",
            target.write("\n")

        target.write("\n")

        target.write("Sharks")
        target.write("\n")
        for shark in sharks:
            target.write(str(shark["Name"]) + ', '),
            target.write(str(shark["Soccer Experience"]) + ', '), " ",
            target.write(str(shark["Guardian Name(s)"]) + ' '), " ",
            target.write("\n")

if __name__ == "__main__":
     assign_players()

This is a sample of how the soccer_players.csv file is formatted

Name,Height (inches),Soccer Experience,Guardian Name(s)
Joe Smith,42,YES,Jim and Jan Smith
1
  • Please specify your question explicitly. It'd be good to concise into one or two sentences, and less description, more data examples. Commented Feb 25, 2017 at 3:05

2 Answers 2

1

Here's a solution that works for any number of players and teams. It randomly shuffles the player list, sorts it by experience, then round-robin assigns the players to the available teams:

#!python3
import csv
import random
import itertools
import operator
from collections import namedtuple

Team = namedtuple('Team','name players')

teams = Team('Raptors',[]), Team('Dragons',[]), Team('Sharks',[])

with open('soccer_players.csv', newline='') as csvfile:
    players = list(csv.DictReader(csvfile))

random.shuffle(players)
players.sort(key=operator.itemgetter('Soccer Experience'))

iteam = itertools.cycle(teams)
for player in players:
        next(iteam).players.append(player)

with open('teams.txt', 'w') as target:
    for team in teams:
        target.write(team.name + '\n')
        for player in team.players:
            target.write('{Name}, {Soccer Experience}, {Guardian Name(s)}\n'.format(**player))
        target.write('\n')

Input file (not very original but there wasn't one provided):

Name,Height (inches),Soccer Experience,Guardian Name(s)
A,40,YES,aaa
B,40,YES,bbb
C,40,YES,ccc
D,40,YES,ddd
E,40,YES,eee
F,40,YES,fff
G,40,YES,ggg
H,40,YES,hhh
I,40,YES,iii
J,40,NO,jjj
K,40,NO,kkk
L,40,NO,lll
M,40,NO,mmm
N,40,NO,nnn
O,40,NO,ooo
P,40,NO,ppp
Q,40,NO,qqq
R,40,NO,rrr

Output file:

Raptors
M, NO, mmm
O, NO, ooo
J, NO, jjj
A, YES, aaa
H, YES, hhh
B, YES, bbb

Dragons
L, NO, lll
N, NO, nnn
P, NO, ppp
C, YES, ccc
I, YES, iii
E, YES, eee

Sharks
R, NO, rrr
Q, NO, qqq
K, NO, kkk
G, YES, ggg
D, YES, ddd
F, YES, fff
Sign up to request clarification or add additional context in comments.

3 Comments

I like your single-level allocation strategy (sort then loop). I wanted to reach for itertools as well, but decided to avoid it for simpler (albeit slightly longer and lower-level) code.
Mark this is great--thank you for the help. Between yours and Jonathan's help I can only say that I truly appreciate the things that you guys do! Thanks
@JohnRogerson You're welcome. Please upvote helpful answers and select one as the accepted answer if appropriate as well :)
0

The biggest concerns I'd have with that code is how specific it is. For example, the names of the teams are encoded into specific variables, which are individually handled. The parceling of players to teams assumes specific number of teams and available players. Etc. Every bit of the processing works only with the specific expected data.

The second concern would be how everything is done in a single function. Reading player data, parceling out players to teams, writing out the team rosters--it's all crammed into one omnibus function.

So, how to improve? First, let's get a little more modular and generic:

def read_players(filepath):
    """
    Read a list of players (each one represented by a 
    dictionary), and return the list.
    """
    with open(filepath) as csvfile:
        soccerplayers = csv.DictReader(csvfile)
        return list(soccerplayers)

This function focuses on just one thing: reading in the players and returning a list. Once read, we can then assign players to teams:

def assign_teams(players, teams):
    """
    Given a list of players and a list of teams, randomly assign
    players to teams--but in a fair way that balances experienced 
    players as evenly as possible across teams. Returns a dictionary 
    mapping team name -> list of players on the team.
    """
    # shuffle order of teams and players so there's no hint of 
    # favoritism
    random.shuffle(teams)
    random.shuffle(players)

    # partition player list into experienced and inexperienced players
    experienced   = [ p for p in players if p['Soccer Experience'] == 'YES']
    inexperienced = [ p for p in players if p['Soccer Experience'] == 'NO']

    # create a roster
    n_teams = len(teams)
    roster = {team_name: [] for team_name in teams}

    # pick the experienced players (round-robin)
    for i, player in enumerate(experienced):
        team = teams[i % n_teams]
        roster[team].append(player)

    # add inexperienced players (round-robin)
    for i, player in enumerate(inexperienced):
        team = teams[i % n_teams]
        roster[team].append(player)

    return roster

Note that specific teams are never mentioned. This makes the routine scalable to an arbitrary number of teams, or players. It uses a common index-modulo-list-size trick to rotate through the team names, assigning each in turn.

Once teams are assigned on the roster, it's time to write them out to a results file:

def write_roster(roster, filepath):
    """
    Write a roster dictionary to the given filepath.
    """
    fields = ["Name", "Soccer Experience", "Guardian Name(s)"]
    with open(filepath, 'w') as f:
        for team in roster.keys():
            f.write(team + '\n')
            for player in roster[team]:
                # select just the desired fields for output
                row = [player[f] for f in fields]
                # construct and write out a formatted record
                formatted_row = ', '.join(row) + '\n'
                f.write(formatted_row)
            # separate teams with a little more white space
            f.write('\n')

Again note that the code is not specific to the team names. It doesn't care if they're the Dragons or the Fighting Unicorns. The values are handled generically.

Finally, you'll need some command and control code to stitch these functions together:

if __name__ == "__main__":
    players = read_players('soccer_players.csv')
    teams = ['Raptors', 'Dragons', 'Sharks']
    roster = assign_teams(players, teams)
    write_roster(roster, 'teams.txt')
    print('done!')

While the assignments are randomized (by design), one run emits a file teams.txt that look as follows. Notice I didn't worry about having a full 18 players. It will work for 18, but it works equally well for 6, 9, 12, ... and will in fact also work for 5, 19, and 67. (Though, if you were going to use this in real life, thus likely to see many odd-sized lists of players, you'd probably want to further improve the rather simplistic fairness algorithm used here.)

Dragons
Todd Jacobs, YES, Robert and Virginia Jacobs
Doug Jones, NO, Mary and Perry Jones
David Nork, NO, Dan and Stacy Nork

Raptors
Joe Smith, YES, Jim and Jan Smith
Will Smith, NO, Bill and Nancy Smith
Mark Jackson, NO, Jack and Frank Jackson

Sharks
Bill Smith, YES, Jim and Jan Smith
Andy Able, NO, Noah Able
Paul Pork, NO, Paul and Paulette Pork

1 Comment

Jonathan--thank you for taking the time to work on this and explain everything in step by step detail. Some of this is above my head at my current experience with Python--but I can tell you that the code works perfectly. I will be going over this to try and study up on some of the things that I don't fully understand yet. Thank you so much once again, this is truly invaluable for me.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.