0

I am trying to make a fair placement into two groups based on people's skill levels. However, I cannot get the values to work in an array unless I use list() which makes the values be presented character by character which is not ideal.

I need a way to get non repeating values from an array using random in a for loop and a way to make the array thing work.

import random


class student:
    def __init__(self, lvl, name):
        self.lvl = lvl
        self.name = name


Omer = student(3, "omer")
Michael = student(1, "Michael")
Sami = student(4, "sami")
Umar = student(5, "Umar")
Samuel = student(4, "Samuel")
Badmos = student(5, "Badmos")
Nabil = student(2, "Nabil")
Inacio = student(1, "Inacio")
Jesse = student(2, "student")
Ameerah = student(2, "Ameerah")
Bilal = student(2, "Bilal")
Joseph = student(2, "Joseph")


x = [Omer.lvl, Michael.lvl, Sami.lvl, Umar.lvl, Samuel.lvl, Badmos.lvl, Nabil.lvl, 
Inacio.lvl, Jesse.lvl, Ameerah.lvl, Bilal.lvl, Joseph.lvl]

z = [Omer.name, Michael.name, Sami.name, Umar.name, Samuel.name, Badmos.name, Nabil.name, Inacio.name, Jesse.name, Ameerah.name, Bilal.name, Joseph.name]
group1 = []

for i in range(6):
    xy = (random.randint(0, 11))
    group1 = group1 + list(z[xy])
    group1 = group1 + list(str(x[xy]))

print(group1)
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
omzy54
  • 45
  • 6
  • Do you want a a pair of groups where the student levels are approximately the same? So the combined levels of group1 is the same as group2 ? – Kingsley Mar 28 '22 at 22:23
  • @Kingsley yes that is what i am going for but the main problem is that i get repeating values and the array is character by character each a new item in the array – omzy54 Mar 28 '22 at 22:29

2 Answers2

0

This is an example of the Bin Packing Problem.

A simple way to get an approximate result is to sort your input (the list of students), then iteratively examine what's left in the pool, dropping it into a set based on the "weight" (in this case sum of student levels) of the set.

So in the code below, we loop through all the students, looking at the existing weight of each group. So if we had a student of say, level=10 and they went into a group first. Then we may spend the next 3 iterations adding students into the other group to rebalance the group-weight.

Sorting the objects in descending order allows the algorithm to "catch up" after a big object is placed. Using a random order is not as good for this, because you may process that "high level" student last, and then cannot "fix" the packing. It will work better on larger lists. (Of course, in a lot of applications of bin packing, it's not possible to pre-sort the input.)

Anyway, there's lots of other ways to do this. Have a read of the Wikipedia article.

Code:

#! /usr/bin/env python3

# Ref: https://stackoverflow.com/questions/71654059/i-am-trying-to-make-it-so-i-can-sort-some-people-into-groups-fairly-based-on-the

class Student:
    def __init__(self, lvl, name):
        self.lvl = lvl
        self.name = name

    def __str__( self ):
        return "%s:%d" % ( self.name, self.lvl )

    @staticmethod
    def sorter( s ):
        """ Comparison function, used to sort a list of students by level, then name """
        return ( s.lvl, s.name )


class StudentGroup:
    def __init__( self, name ):
        self.name     = name
        self.level_sum= 0
        self.students = []

    def add( self, new_student ):
        """ Add a new student to the group """
        # TODO: reject multiple additions of the same student
        self.students.append( new_student )
        self.level_sum += new_student.lvl
        # keep the list sorted
        self.students = sorted( self.students, key=Student.sorter, reverse=True )

    def getName( self ):
        return self.name

    def getSize( self ):
        """ Return the number of students in the group """
        return len( self.students )

    def getLevel( self ):
        """ Return the sum of the students' levels """
        return self.level_sum

    def getStudents( self ):
        """ Access to the internal list """
        return self.students

    def removeFirst( self ):
        """ Get the first student in the list """
        first = self.students[0]
        self.removeStudent( first )
        return first

    def removeStudent( self, student ):
        """ Remove the given student from the group """
        if ( student in self.students ):
            self.level_sum -= student.lvl
            self.students.remove( student )
        else:
            return None  # unknown student


### MAIN

Omer    = Student(3, "Omer")
Michael = Student(1, "Michael")
Sami    = Student(4, "Sami")
Umar    = Student(5, "Umar")
Samuel  = Student(4, "Samuel")
Badmos  = Student(5, "Badmos")
Nabil   = Student(2, "Nabil")
Inacio  = Student(1, "Inacio")
Jesse   = Student(2, "Jesse")
Ameerah = Student(2, "Ameerah")
Bilal   = Student(2, "Bilal")
Joseph  = Student(2, "Joseph")


### Make a group of everyone
all_students = StudentGroup( "All" )
for student in ( Omer, Michael, Sami, Umar, Samuel, Badmos, Nabil, Inacio, Jesse, Ameerah, Bilal, Joseph ):
    all_students.add( student )

group1 = StudentGroup( "Group1" )
group2 = StudentGroup( "Group2" )

# Keep placing students into group1 or group2
# trying to balance them by the sum of "Student.lvl"
while ( all_students.getSize() > 0 ):
    if ( group1.getLevel() <= group2.getLevel() ):
        group1.add( all_students.removeFirst() )
    else:
        group2.add( all_students.removeFirst() )
        
        
        
print( "Group1 [%s] - Weight [%d]" % ( group1.getName(), group1.getLevel() ) )
for s in group1.getStudents():
    print( "    %s" % str ( s ) )

print( "Group2 [%s] - Weight [%d]" % ( group2.getName(), group2.getLevel() ) )
for s in group2.getStudents():
    print( "    %s" % str ( s ) )

Output:

Group1 [Group1] - Weight [17]
    Umar:5
    Samuel:4
    Omer:3
    Jesse:2
    Ameerah:2
    Inacio:1
Group2 [Group2] - Weight [16]
    Badmos:5
    Sami:4
    Nabil:2
    Joseph:2
    Bilal:2
    Michael:1
Kingsley
  • 14,398
  • 5
  • 31
  • 53
0

Choose without repetition:

You can use random.sample to avoid repetition:

indexes = random.sample(range(12), 12)

This will give you a list of 12 random indexes (from 0 to 11)

Adding to list:

Use append to add an item to list. (Whether item is a string or an integer)

group1.append(z[xy])
group1.append(x[xy]))

This will add z[xy] and x[xy] to the group 1 (As separate elements).

Formatting string:

If you want to have both student name and his/her level as one item, use string formatting.

group1.append("{} {}".format(z[xy], x[xy]))

"{} {}".format(z[xy], x[xy]) part will create a string by replacing {}s with items inside format input, respectively. For example one result could be "Samuel 4"

Working code examples

Grouping with fixed size:

If size of each group should be exactly half (6), with fair skills, you can skip randomness. You can sort the students by their level and then starting from bottom add one to group one and one to group two and so one. (Or assign even numbers to group 1 and odd ones to group 2.
import random

class student:
    def __init__(self, lvl, name):
        self = lvl
        self.name = name

Omer = student(3, "omer")
Michael = student(1, "Michael")
Sami = student(4, "sami")
Umar = student(5, "Umar")
Samuel = student(4, "Samuel")
Badmos = student(5, "Badmos")
Nabil = student(2, "Nabil")
Inacio = student(1, "Inacio")
Jesse = student(2, "student")
Ameerah = student(2, "Ameerah")
Bilal = student(2, "Bilal")
Joseph = student(2, "Joseph")


students = [Omer, Michael, Sami, Umar, Samuel, Badmos, Nabil, 
Inacio, Jesse, Ameerah, Bilal, Joseph]

# Sort students by their level
students_sorted = sorted(students, key=lambda stu: stu.lvl)

# Create a list where each element contains a string of student name and level
students_with_names = ["{}: {}".format(stu.name, stu.lvl) for stu in students]

# Choose even ones for group1 and odd ones for group2
group1 = students_with_names[0::2]
group2 = students_with_names[1::2]

print(group1)
print(group2)

Grouping with variant size:

If group size is not important, you can shuffle students, then going over each student assign him/her to the group with lower sum of level.

import random

class student:
    def __init__(self, lvl, name):
        self = lvl
        self.name = name


Omer = student(3, "omer")
Michael = student(1, "Michael")
Sami = student(4, "sami")
Umar = student(5, "Umar")
Samuel = student(4, "Samuel")
Badmos = student(5, "Badmos")
Nabil = student(2, "Nabil")
Inacio = student(1, "Inacio")
Jesse = student(2, "student")
Ameerah = student(2, "Ameerah")
Bilal = student(2, "Bilal")
Joseph = student(2, "Joseph")

# Create a list of students
students = [Omer, Michael, Sami, Umar, Samuel, Badmos, Nabil, 
Inacio, Jesse, Ameerah, Bilal, Joseph]

number_of_students = len(students)

# Create random indexes
random_indexes = random.sample(range(number_of_students), number_of_students)

group1 = []
group2 = []
# Keep sum of each group skills
group1_skill = 0
group2_skill = 0

for index in random_indexes:
    
    # If sum of group skills are the same or sum of group 1 is lower
    if group1_skill == group2_skill or group1_skill < group2_skill:
        group1.append("{}:{}".format(students[index].name, students[index].lvl))
        # Update group1 sum of skills
        group1_skill = group1_skill + students[index].lvl
        
    else:
        group2.append("{}:{}".format(students[index].name, students[index].lvl))
        # Update group2 sum of skills
        group2_skill = group2_skill + students[index].lvl
        
print(group1)
print(group2)
farshad
  • 764
  • 10
  • 25