use strict; ### Constants my $number_of_rounds = 3; my $max_per_panel = 6; my $max_per_team = 3; # Make this number bigger to get better results at the cost of running slower my $attempts_before_enlarging_panels = 1000; ### Parse Arguments unless ( scalar @ARGV ) { print "Usage: pass the number of participants from each team as arguments\n"; exit; } my @entrants = @ARGV; ### Build Teams my %teams; my $team_id = 'A'; foreach my $count ( @entrants ) { die "Too many people on team $team_id: $count" if ( $count > $max_per_team ); foreach my $j ( 1 .. $count ) { $teams{$team_id}->[ $j - 1 ] = "$team_id$j"; } $team_id ++; } my @participants = map { @$_ } values %teams; ### Declare Result Variable my @rounds; my $start_time = time(); my $attempts = 1; ### Determine Panel Size my $panels_required = int( .99 + scalar(@participants) / $max_per_panel ); $panels_required = $max_per_team if ( $panels_required < $max_per_team ); PANEL_EXPANSION: { my $base_per_panel = int( scalar(@participants) / $panels_required ); my $oversize_panels = scalar(@participants) - ($panels_required * $base_per_panel ); my $most_per_panel = $base_per_panel + ( $oversize_panels ? 1 : 0 ); my @panel_sizes = ( ( $oversize_panels ? ( $most_per_panel ) x $oversize_panels : () ), ( $base_per_panel ) x ( $panels_required - $oversize_panels ) ); ### Attempt to Build Panels ATTEMPT: { # The "zero round" contains the teams themselves @rounds = [ map $teams{$_}, sort keys %teams ]; foreach my $round ( 1 .. $number_of_rounds ) { # warn "Building round $round\n"; my %exclusions; foreach my $panel ( map @$_, @rounds ) { foreach my $a ( @$panel ) { foreach my $b ( @$panel ) { $exclusions{$a}->{$b} = 1; } } } # warn "Should exclude: " . join(', ', map { my $a = $_; map "$a-$_", sort keys %{$exclusions{$a}} } sort keys %exclusions) . "\n"; my @available = @participants; my @panels; foreach my $panel ( 1 .. $panels_required ) { # warn " Building panel $panel ($panel_sizes[$panel - 1])\n"; my @entries; foreach ( 1 .. $panel_sizes[$panel - 1] ) { last unless scalar @available; my %exclude = map { $_ => 1 } map { sort keys %{$exclusions{$_}} } @entries; # warn " Avoiding " . join(', ', sort keys %exclude) . "\n"; my @candidates = ( grep { ! $exclude{$_} } @available ) or do { # warn "Can't find a candidate in: " . join(', ', @available); if ( $attempts ++ % $attempts_before_enlarging_panels ) { # warn "Trying again..." redo ATTEMPT; } else { # warn "This is taking too long; expanding number of panels..."; $panels_required ++; redo PANEL_EXPANSION; } }; my $candidate = $candidates[ int rand scalar @candidates ]; # warn " Selecting $candidate\n"; push @entries, $candidate; @available = grep { $_ ne $candidate } @available; } # @entries = map $teams{$_}->[$panel - 1], sort keys %teams; $panels[$panel - 1] = \@entries; } $rounds[$round] = \@panels; } } ### Display Competition Info print "Competing: " . scalar(@entrants) . " schools, " . scalar(@participants) . " entrants\n"; print "Structure: $number_of_rounds rounds of $panels_required panels\n"; print "Panel Size: $most_per_panel entries " . ( $oversize_panels ? "or less (" . join(', ', @panel_sizes) . ")" : 'each' ) . "\n"; print "Calculation: required $attempts attempts in " . ( time - $start_time ) . " seconds\n"; } ### Display Teams and Rounds foreach my $round ( 0 .. $#rounds ) { print( "\n" . ( $round ? "Round $round" : "Teams" ) . ":\n" ); my @panels = @{ $rounds[$round] }; foreach my $panel ( @panels ) { print( "- " . join(', ', @$panel) . "\n" ) } }