0

I have implemented a round robin generator in PHP to create fixtures between users in a league. This matches all the users in a league and pits them against each other for each gameweek.

The code looks like this;

    /**
     * Rotates an array for the round robin algorithm
     */
    function round_robin_array($array)
    {
        // we always keep index 0
        $top = array_shift($array);
        $last = array_pop($array);
        $rotate = [$last];
        foreach ($array as $_value) {
            $rotate[] = $_value;
        }
        array_unshift($rotate, $top);
        return $rotate;
    }

    /**
     * Runs a round robin to make a schedule.
     */
    function round_robin($users, $weeks)
    {
        $schedule = [];
        $count = count($users);
        foreach ($users as $_u) {
            $schedule[$_u] = array_fill(0, $weeks, []);
        }
        for ($i=0;$i<$weeks;$i++) {
            for ($a=0;$a<($count / 2) + 1;$a++) {
                $vs = $users[$a];
                $opp = $users[($count - $a) - 1];
                $at = rand(0,4);
                $pg = [$opp, $at];
                $og = [$vs, $at];
                $schedule[$vs][$i] = $pg;
                $schedule[$opp][$i] = $og;
            }
            $users = $this->round_robin_array($users);
        }
        return $schedule;
    }

    public function generateFixtures($league, $users, $weeks)
    {
        $array = [];

        foreach($users as $user) {
            array_push($array, $user['id']);
        }

        if(count($array) % 2 != 0) {
            array_push($array, '0');
        }

        $fixtures = $this->round_robin($array, $weeks);

        $gameweek = 1;

        foreach($fixtures as $key => $val) {
            if($key != 0) {
                foreach($val as $opponent) {
                    LeagueFixture::firstOrCreate([
                        'gameweek' => $gameweek,
                        'league_id' => $league,
                        'user_id' => $key,
                        'opponent_id' => $opponent[0],
                    ]);
                    $gameweek = $gameweek+1;
                }
            }
            $gameweek = 1;
        }

        return $fixtures;

    }

The generateFixtures function is passed the users of the league, the league itself and the number of weeks.

The problem is this creates 'duplicates' for each fixture as there is essentially a view from each user - for example;

Gameweek 1

  • User_1 VS User_2
  • User_3 VS User_4
  • User_2 VS User_1
  • User_4 VS User_3

As you can see, the last two fixtures there are different; but the same!

My question is whether this is a problem with the round robin or whether i could/should filter out these duplicates when pulling them through in the controller/view

Alex
  • 2,003
  • 4
  • 19
  • 30
  • create a copy the array, find pairs in that. After finding a pair, remove those from the copy, then find the next pair. Continue until exhausted. – Mike 'Pomax' Kamermans May 27 '15 at 15:26
  • Thanks @Mike'Pomax'Kamermans. When do you suggest i do this? During the round robin creation or when rendering the data for the view output? – Alex May 27 '15 at 15:29
  • the best way to avoid this is to use ordered pairs, meaning pairs which are ordered i.e for every pair (p1, p2) p1 < p2, if you maintain this condition in your construction, you will not have a problem – Nikos M. May 27 '15 at 15:33
  • @Alex: at creation. Don't generate data only to later on reject it: set up the code that it never generates that data in the first place. – Mike 'Pomax' Kamermans May 27 '15 at 17:22

1 Answers1

0

It's not the most elegant solution, but at the point of data-entry to the database, i can check if the opposite of what is about to be created already exists;

            foreach($fixtures as $key => $val) {
                if($key != 0) {
                    //loop through the values to create an entry for each
                    foreach($val as $opponent) {
                        //check if the opposite of each entry already exists
                        if(!LeagueFixture::where('gameweek', $gameweek)
                            ->where('league_id', $league)
                            ->where('user_id', $opponent[0])
                            ->where('opponent_id', $key)
                            ->exists()) {
                            //if not, we can create the entry as usual
                            LeagueFixture::firstOrCreate([
                                'gameweek' => $gameweek,
                                'league_id' => $league,
                                'user_id' => $key,
                                'opponent_id' => $opponent[0],
                            ]);
                        }
                        $gameweek = $gameweek+1;
                    }
                }
                $gameweek = 1;
            }

This is the easiest solution for me now.. although the data is generated in the above round robin (and you could easily argue this would be bad practice), the duplicate data never makes it into my database

Alex
  • 2,003
  • 4
  • 19
  • 30