Created
December 13, 2020 21:02
-
-
Save miketwo/8e51714f260413954c183fb5346ba44f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'set' | |
# I have a group of 65 people that is divided into 10 subgroups. From the group of 65 I want to generate 17 teams. 14 teams of 4 people, 3 teams of 3. | |
# - First restriction: Each team must contain no more than 1 person from one of the subgroups. For example, a correct team would be {1, 2, 3, 4}, {2, 5, 6, 3}, and an incorrect team {2, 2, 5, 8} or {10, 2, 5, 2}. (the numbers 1-10 standing for the 10 subgroups) | |
# - Second restriction: I have to generate team combinations for multiple weeks, whereby people cannot be matched to a person they have been on a team with in the past. So if person A and B where in the same team in week 1, they cannot be matched up in any of the following weeks | |
# - Third (optional) restriction: The group contains of 22 males and 43 females. Besides the first two restrictions, it would be ideal to keep the male/female ration the same across all the teams. However, this might be hard to sustain after a few rounds, so it's not a priority. It would just be nice if the program maximised this. | |
class Person | |
attr_accessor :name | |
attr_accessor :group | |
attr_accessor :sex | |
def initialize(name, group, sex=:male) | |
@name = name | |
@group = group | |
@sex = sex | |
@former = [].to_set | |
end | |
def to_s | |
return "#{@name}, #{@group}, #{@sex}" | |
end | |
def add_former_teammate(other) | |
if other.name == self.name | |
return | |
end | |
if @former.member?(other) | |
raise "Error!" | |
end | |
@former = @former.add(other) | |
end | |
def valid_teammate?(other) | |
return @group != other.group && @former.none? {|f| f.name == other.name } | |
end | |
def show_former() | |
puts "[" + @former.map { |e| e.name }.join(",") + "]" | |
end | |
end | |
def fillCurrentTeam(person, notInGroup, currentGroup, team_size) | |
notInGroup.delete(person) | |
currentGroup.append(person) | |
# puts "Adding #{person}. Length is #{currentGroup.length}" | |
if currentGroup.length >= team_size | |
return currentGroup, notInGroup | |
end | |
for candidate in notInGroup | |
if currentGroup.all? {|p| p.valid_teammate?(candidate) } | |
return fillCurrentTeam(candidate, notInGroup, currentGroup, team_size) | |
end | |
end | |
return currentGroup, notInGroup | |
end | |
def create_teams(notInGroup, num_teams, team_size) | |
teams = [] | |
while ! notInGroup.empty? && teams.length < num_teams | |
currentTeam = [] | |
person = notInGroup.pop | |
currentTeam, notInGroup = fillCurrentTeam(person, notInGroup, currentTeam, team_size) | |
teams.append(currentTeam) | |
end | |
return teams, notInGroup | |
end | |
def team_stats(team) | |
fempercent = team.count { |m| m.sex == :female }.to_f / team.length.to_f * 100 | |
# return "#{fempercent.round(2)}%% female" | |
return fempercent | |
end | |
def display(teams, verbose=false) | |
puts "#{teams.length} Teams" | |
puts "#{teams.flatten.length} People" | |
if verbose | |
puts "-"*40 | |
for team in teams | |
puts "[" + team.map { |e| e.name }.join(",") + "] " | |
end | |
end | |
end | |
def update_restrictions(teams) | |
for team in teams | |
for person in team | |
team.map { |other| person.add_former_teammate(other) } | |
end | |
end | |
end | |
def print_and_flush(str) | |
print str | |
$stdout.flush | |
end | |
def create_and_display_teams(people) | |
backup = people.dup() | |
teams = [] | |
remaining = [] | |
loop do | |
teams, remaining = create_teams(people, 14, 4) | |
tmp, remaining = create_teams(remaining, 3, 3) | |
teams += tmp | |
#print_and_flush "." | |
break if remaining.length == 0 #&& teams.none? {|team| team_stats(team) < 1.0 || team_stats(team) > 99.0} | |
people = backup.dup().shuffle | |
end | |
display(teams, true) | |
puts "#{remaining.length} remaining" | |
puts "="*40 | |
update_restrictions(teams) | |
return (teams + remaining).flatten.shuffle | |
end | |
def create_people() | |
people = [] | |
(1..65).each do |cnt| | |
people.append(Person.new(cnt, cnt%10+1)) | |
end | |
people.shuffle! | |
return people | |
end | |
def main | |
people = create_people() | |
(1..10).each do |week| | |
puts "Week #{week}" | |
people = create_and_display_teams(people) | |
end | |
end | |
if __FILE__ == $0 | |
main() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment