PHP数组随机化但不复制名册

I'm trying to make a randomizing roster. I am having trouble with it.

I need to take 1 C 2 P SS CF 3 LF RF

And create 9 random orders where they do not repeat in order, nor does one value take the same spot twice. Like a grid where each position is used once 9 times up and 9 times across. But I want to be able to randomize it. So its not always the same. I mean I guess I could just randomize the names, but the names would typically be in a specific order, whereas the positions would need not be, but not duped.

123456789    
234567891    
345678912    
456789123    
567891234    
678912345   
789123456    
891234567    
912345678 

I started with this, but it makes no sense. At first I was making sure as to not duplicate any positions, but I realized it didn't really, and I got kinda brain stuck.

shuffle($positions);
    $p1 = $positions;
    echo '<table style="width:100%;"><tr>';
    foreach($p1 as $pA){
        echo '<td>'.$pA.'</td>';
    }   
    echo '</tr>';   


    shuffle($positions);
    $p2 = $positions;
    if($p2 === $p1){    
        while($p2 === $p1){
            shuffle($positions);
            $p2 = $positions; 
            $a++; 
            echo '<br>=a=<br>'.$a.'<br>==<br>';
            //if($p2 == $p1){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p2 as $pB){
        echo '<td>'.$pB.'</td>';
    }   
    echo '</tr>';




    shuffle($positions);
    $p3 = $positions;
    if($p3 === ($p1 || $p2)){   
        while($p3 === ($p1 || $p2)){
            shuffle($positions);
            $p3 = $positions; 
            $b++; 
            echo '<br>=b=<br>'.$b.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p3 as $pC){
        echo '<td>'.$pC.'</td>';
    }   
    echo '</tr>';



    shuffle($positions);
    $p4 = $positions;
    if($p4 === ($p1 || $p2 || $p3)){    
        while($p4 === ($p1 || $p2 || $p3)){
            shuffle($positions);
            $p4 = $positions; 
            $c++; 
            echo '<br>=c=<br>'.$c.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p4 as $pD){
        echo '<td>'.$pD.'</td>';
    }   
    echo '</tr>';



    shuffle($positions);
    $p5 = $positions;
    if($p5 === ($p1 || $p2 || $p3 || $p4)){ 
        while($p5 === ($p1 || $p2 || $p3 || $p4)){
            shuffle($positions);
            $p5 = $positions; 
            $d++; 
            echo '<br>=d=<br>'.$d.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p5 as $pE){
        echo '<td>'.$pE.'</td>';
    }   
    echo '</tr>';


    shuffle($positions);
    $p6 = $positions;
    if($p6 === ($p1 || $p2 || $p3 || $p4 || $p5)){  
        while($p6 === ($p1 || $p2 || $p3 || $p4 || $p5)){
            shuffle($positions);
            $p6 = $positions; 
            $e++; 
            echo '<br>=d=<br>'.$e.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p6 as $pF){
        echo '<td>'.$pF.'</td>';
    }   
    echo '</tr>';



    shuffle($positions);
    $p7 = $positions;
    if($p7 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6)){   
        while($p7 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6)){
            shuffle($positions);
            $p7 = $positions; 
            $f++; 
            echo '<br>=d=<br>'.$f.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p7 as $pG){
        echo '<td>'.$pG.'</td>';
    }   
    echo '</tr>';

    shuffle($positions);
    $p8 = $positions;
    if($p8 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7)){    
        while($p8 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7)){
            shuffle($positions);
            $p8 = $positions; 
            $g++; 
            echo '<br>=d=<br>'.$g.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p8 as $pH){
        echo '<td>'.$pH.'</td>';
    }   
    echo '</tr>';



    shuffle($positions);
    $p9 = $positions;
    if($p9 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7 || $p8)){ 
        while($p9 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7 || $p8)){
            shuffle($positions);
            $p9 = $positions; 
            $h++; 
            echo '<br>=d=<br>'.$h.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p9 as $pI){
        echo '<td>'.$pI.'</td>';
    }   
    echo '</tr>';



    shuffle($positions);
    $p10 = $positions;
    if($p10 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7 || $p8 || $p9)){ 
        while($p10 === ($p1 || $p2 || $p3 || $p4 || $p5 || $p6 || $p7 || $p8 || $p9)){
            shuffle($positions);
            $p10 = $positions; 
            $i++; 
            echo '<br>=d=<br>'.$i.'<br>==<br>';
            //if($p2 == ($p1 || $p2)){continue;}else{break;}
        }       
    }
    echo '<tr>';
    foreach($p10 as $pJ){
        echo '<td>'.$pJ.'</td>';
    }   
    echo '</tr>';


    echo '</table>';


?>

James, I know it can be hard when you have a bunch of allstars and you want to be a fair coach who gives everyone an equal shot. Randomizing code to the rescue. The only thing this table doesn't account for is roster fluctuations -- if you have a sick/injured player, you will have to make an adjustment.

This code will output the grid that you are looking for. While it is not optimized for speed, it only takes ~.25 seconds to complete -- so performance is not going to be an issue for your case. Performance Test

<?php
$players=array(
    "Mickey Mantle",
    "Babe Ruth",
    "Yogi Berra",
    "Lou Gehrig",
    "Joe DiMaggio",
    "Derek Jeter",
    "Whitey Ford",
    "Roger Maris",
    "Reggie Jackson"
);
$positions=array("P","C","1","2","SS","3","LF","CF","RF");
$batch=array();

echo "<table border=1>";
echo "<tr><th>#</th><th>",implode("</th><th>",$players),"</th></tr>";
    for($week=1; $week<11; ++$week){
        if($week==9){
            // see what's missing (use remaining value versus needless looping)
            foreach($players as $i=>$v){
                $batch[$week][$i]=current(array_diff($positions,array_column($batch,$i)));
            }
        }elseif($week==10){
            // purely random, as every player has played every position by now.
            shuffle($positions);
            $batch[$week]=$positions;           
        }else{    // weeks 1 - 8
            while(!isset($batch[$week])){
                shuffle($positions);
                $batch[$week]=$positions;
                //echo "<tr><td>",implode("</td><td>",$batch[$week]),"</td></tr>";
                if($week>1){     // check for no duplicate positions for any player
                    foreach($batch[$week] as $i=>$v){
                        //echo "<tr><td colspan=9>",implode(', ',array_column($batch,$i)),"</td></tr>";
                        if(sizeof(array_unique(array_column($batch,$i)))<$week){  // if vertical duplicates
                            unset($batch[$week]);   // trash the week, trigger rerun of loop
                            break;  // don't bother to assign positions for remaining players
                        }
                    }
                }
            }
        }
        echo "<tr><th>$week</th><td>",implode("</td><td>",$batch[$week]),"</td></tr>";
    }
echo "</table>";
?>

Output:

PositionSchedule

If my code doesn't earn your green tick of approval, please clarify your question with an edit. I am happy to explain any of the lines if you have questions. Truth is, I loved this challenge; I'd like one of these per week!


Answer Extension -- a function with improved flexibility and efficiency

To be honest, I never did actually quit working on this -- I'm just not very good at letting things go unresolved. Your invoice is in the mail ;)

THIS IS BY NO MEANS PERFECT -- don't go and try to sell it! I have tested the following function/code using 9 innings/periods, 9 positions, and (9, 13, and 18) players and I wasn't able to break it.

When I set the periods to 4 while the roster and positions are much higher, it did break. This may be a matter of logic that a mathematician could explain in detail -- I'm not going to go down that rabbit hole.

I am leaving all of my comments and debugging echos in place, in case anyone needs to understand/modify/improve it in the future.

Please read the fineprint just before the function call.

function fairRotation($roster=[],$positions=[],$rotations=9,$off="X"){
    $roster_count=sizeof($roster);
    $positions_count=sizeof($positions);
    echo "<div>roster_count=$roster_count, positions_count=$positions_count</div>";
    $ons_avg=$positions_count/$roster_count;
    $ons_max=ceil($ons_avg);
    $ons_min=floor($ons_avg);
    echo "<div>ons_avg=$ons_avg, ons_max=$ons_max, ons_min=$ons_min</div>";
    $off_avg=($roster_count-$positions_count)*$rotations/$roster_count;
    $off_max=ceil($off_avg);
    $off_min=floor($off_avg);
    echo "<div>off_avg=$off_avg, off_max=$off_max, off_min=$off_min</div><br>";
    $positions=array_pad($positions,$roster_count,$off);  // sync positions with roster using sub identifier    
    $positions_count=sizeof($positions);    // overwrite with updated count
    for($r=0; $r<$rotations; ++$r){
        shuffle($positions);
        $result[$r]=$positions;
    }
    //$color_result=$result;
    // unfiltered result:
    /*echo "<table border=1>";
        echo "<tr><th>#</th><th>",implode("</th><th>",$roster),"</th></tr>";
        foreach($result as $key=>$row){
            echo "<tr><th>",($key+1),"</th><td>",implode("</td><td>",$row),"</td></tr>";
        }
    echo "</table>";    */

    // Assess result and address conflicts...
    $iterations=0;
    $fair="?";
    $must_drop_count=0;
    $must_gain_count=0;
    while($fair!="true" && $iterations<500){
        $must_gain=$must_drop=$may_gain=$may_drop=[];  // reset
        for($c=0; $c<$roster_count; ++$c){  // triage each column
            $col=array_column($result,$c);
            $val_counts[$c]=array_merge(array_fill_keys($positions,0),array_count_values($col));
            foreach($val_counts[$c] as $pos=>$cnt){
                if(($pos!=$off && $cnt<$ons_min) || ($pos==$off && $cnt<$off_min)){
                    $must_gain[$c][$pos]=array_keys($col,$pos);  // column/player must gain this position, but not where row = value(s)
                }elseif(($pos!=$off && $cnt>$ons_max) || ($pos==$off && $cnt>$off_max)){
                    $must_drop[$c][$pos]=array_keys($col,$pos);  // column/player must drop this position, but only where row = value(s)
                }elseif(($pos!=$off && $cnt<$ons_max) || ($pos==$off && $cnt<$off_max)){
                    $may_gain[$c][$pos]=array_keys($col,$pos);  // column/player may gain this position, but not where row = value(s)
                }elseif(($pos!=$off && $cnt>$ons_min) || ($pos==$off && $cnt>$off_min)){
                    $may_drop[$c][$pos]=array_keys($col,$pos);  // column/player may drop this position, but only where row = value(s)
                }
            }
        }

        if(sizeof($must_gain)==0 && sizeof($must_drop)==0){
            $fair="true";
        }elseif(sizeof($must_drop)==$must_drop_count && sizeof($must_gain)==$must_gain_count){
            //var_export($must_drop);
            //echo "<br>Program Deadlock @ $iterations Iterations.";
            //echo "<br>Desperately reshuffle one of the deadlocked rows.";
            shuffle($positions);
            if($must_drop_count>0){
                $result[current(current(current($must_drop)))]=$positions;
                //$color_result[current(current(current($must_drop)))]=$positions;
            }else{
                $result[current(current(current($must_gain)))]=$positions;
                //$color_result[current(current(current($must_gain)))]=$positions;
            }
        }else{
            $must_drop_count=sizeof($must_drop);
            $must_gain_count=sizeof($must_gain);
            //echo "<br><div>MustDrop:$must_drop_count , MustGain:$must_gain_count</div>"; 

            foreach($must_drop as $d1col=>$d1array){  // $must_drop1[0]["SS"]=array(3,8);
                ++$iterations;
                //echo "<div>({$iterations}x): {$roster[$d1col]} must drop ";
                    //var_export($d1array);
                    foreach($d1array as $d1pos=>$d1keys){
                        foreach(array_diff_key($must_drop,array($d1col=>"")) as $d2col=>$d2array){  // dual-solution swap
                            foreach($d2array as $d2pos=>$d2keys){
                                //echo "<div>..seeking a new home for $d1pos</div>";
                                if($d1pos!=$d2pos && (isset($must_gain[$d1col][$d2pos]) || isset($may_gain[$d1col][$d2pos]))){
                                    //echo "<div>{$roster[$d1col]} may drop $d1pos for {$roster[$d2col]}'s $d2pos</div>";
                                    foreach($d2keys as $row){
                                        //echo "<div>checking {$roster[$d2col]}'s row($row) holding $d2pos vs {$roster[$d1col]}'s $d1pos ";
                                        //var_export($d1keys);
                                        //echo "</div>";
                                        if(in_array($row,$d1keys)){
                                            //echo "<div>row match on $row between {$roster[$d1col]} & {$roster[$d2col]}</div>";
                                            if(isset($must_gain[$d1col][$d2pos])){
                                                //echo "<div>Success: {$roster[$d1col]} must gain holds $d2pos</div>";
                                                //$color_result[$row][$d1col]="<span style='background-color:green;'>$d2pos</span>";
                                                //$color_result[$row][$d2col]="<span style='background-color:blue;'>$d1pos</span>";
                                                $result[$row][$d1col]=$d2pos;
                                                $result[$row][$d2col]=$d1pos;
                                                //var_export($result[$row]);
                                                //echo "<br>";
                                                //unset($must_drop[$d1col][$d1pos],$must_gain[$d2col][$d2pos],$must_gain[$d1col][$d2pos],$must_gain[$d2col][$d1pos]);
                                                break(5);
                                            }elseif(isset($may_gain[$d1col][$d2pos])){
                                                //echo "<div>Success: {$roster[$d1col]} may gain holds $d2pos</div>";
                                                //$color_result[$row][$d1col]="<span style='background-color:red;'>$d2pos</span>";
                                                //$color_result[$row][$d2col]="<span style='background-color:orange;'>$d1pos</span>";
                                                $result[$row][$d1col]=$d2pos;
                                                $result[$row][$d2col]=$d1pos;
                                                //var_export($result[$row]);
                                                //echo "<br>";
                                                //unset($must_drop[$d1col][$d1pos],$must_gain[$d2col][$d2pos],$may_gain[$d1col][$d2pos],$may_gain[$d2col][$d1pos]);
                                                break(5);
                                            }else{
                                                //echo "<div>No Eligible Swap: {$roster[$d1col]} doesn't need/want $d2pos @ row$row";
                                                //var_export(array_merge(array(),$must_gain[$d1col],$may_gain[$d1col]));
                                                //echo "</div>";
                                            }
                                        }
                                        //echo "<br>";
                                    }
                                }
                            }
                        }                   
                    }
                //echo "</div><br>";
            }

        }
    }
    if($fair=="true"){
        echo "<div>FAIR! after $iterations adjustments</div>";
        return $result;  // $color_result for color
    }else{
        echo "Runaway Deadlock, Call Full Rerun<br>";
        return false;
    }
}

$roster=array(
    "Mickey Mantle","Babe Ruth","Yogi Berra","Lou Gehrig","Joe DiMaggio",
    "Derek Jeter","Whitey Ford","Roger Maris","Reggie Jackson","sub1","sub2","sub3","sub4","sub5","sub6","sub7","sub8","sub9");
$positions=array("P","C","1st","2nd","SS","3rd","LF","CF","RF");
$rotations=9; // rotating each inning of a 9 inning baseball game

// roster array must be larger than positions array
// if roster is short, remove positions which will be vacant
// values in the positions array MUST NOT be purely numeric, due to array_fill_keys() type-feature/glitch
// subs/inactive assignments will be marked by an X, unless otherwise declared in the function call

$result=false;
while(!$result){
    $result=fairRotation($roster,$positions,$rotations);
}

echo "<table border=1>";
    echo "<tr><th>#</th><th>",implode("</th><th>",$roster),"</th></tr>";
    foreach($result as $key=>$row){
        echo "<tr><th>",($key+1),"</th><td>",implode("</td><td>",$row),"</td></tr>";
    }
echo "</table>";

Displays something like this:

PosSched2