4

我正在尝试创建一些逻辑来生成双淘汰锦标赛括号中的事件时间表。

这是一个示例 8 队括号:

rd1 quarter    semi    finals
A───┐
  0 ├───────A┐
B───┘        │
           4 ├────────A┐
C───┐        │         │
  1 ├───────C┘         │
D───┘                  │
                    10 ├──────A┐
E───┐                  │       │
  2 ├───────E┐         │       │
F───┘        │         │       │
           5 ├────────E┘       │
G───┐        │              13 ├───= Champ
  3 ├───────G┘                 │
H───┘                          │
                    E────┐     │
         C───┐           │     │
    B───┐  8 ├────C┐  12 ├────E┘
      6 ├B───┘     │     │
    D───┘       11 ├C────┘
         G───┐     │
    F───┐  9 ├────G┘
      7 ├F───┘
    H───┘

这些数字代表匹配数组中的索引,这是所需的输出。例如,索引 0 将代表团队 1 与团队 8(使用种子系统),索引 4 将代表索引 0 的获胜者与索引 1 的获胜者。

失败者的括号由获胜者括号的失败者填充,其中索引 6 是索引 0 的失败者与索引 1 的失败者,索引 8 是索引 4 的失败者与索引 6 的获胜者。

在视觉示例中,您可以看到用字母标记的团队,并清楚地显示获胜团队每次都在顶部分支,而失败团队在底部分支的清晰示例。指数 0 代表 A 队对 B,指数 4 代表指数 0 (A) 的获胜者对指数 1 (C) 的获胜者。指数 6 是指数 0 (B) 的输家与指数 1 (D) 的输家,指数 8 是指数 4 (C) 的输家与指数 6 (B) 的赢家

出现了一个明显的模式,但是当我尝试适应不同数量的竞争对手时,我的逻辑变得混乱和混乱。为简单起见,我将支架固定为只有 2 个团队的幂。我能够编写所有内容来为 8 队括号创建一系列匹配项,但我什至对自己的代码也失去了理解,因为它似乎不可扩展。

// round one
for( $i = 0; $i < log( count( $competitors ), 2 ); $i++ )
{
    $seeded = array( );
    foreach( $competitors as $competitor )
    {
        $splice = pow( 2, $i );

        $seeded = array_merge( $seeded, array_splice( $competitors, 0, $splice ) );
        $seeded = array_merge( $seeded, array_splice( $competitors, -$splice ) );
    }
    $competitors = $seeded;
}

$events = array_chunk( $seeded, 2 );

// quarter finals
for( $i = 0; $i < count( $competitors ) / 2; $i++ )
{
    array_push( $events, array(
        array( 'from_event_index' => $i, 'from_event_rank' => 1 ), // rank 1 = winner
        array( 'from_event_index' => ++$i, 'from_event_rank' => 1 )
    ) );
}

$round_matchups = array( );
for( $i = 0; $i < count( $competitors ) / 2; $i++ )
{
    array_push( $round_matchups, array(
        array( 'from_event_index' => $i, 'from_event_rank' => 2 ), // rank 2 = loser
        array( 'from_event_index' => ++$i, 'from_event_rank' => 2 )
    ) );
}
$events = array_merge( $events, $round_matchups );

for( $i = 0; $i < count( $round_matchups ); $i++ )
{
    array_push( $events, array(
        array( 'from_event_index' => $i + count( $competitors ) / 2, 'from_event_rank' => 2 ),
        array( 'from_event_index' => $i + count( $competitors ) / 2 + count( $competitors ) / 2 / 2, 'from_event_rank' => 1 )
    ) );
}

// semi finals
for( $i = 0; $i < count( $competitors ) / 2 / 2; $i++ )
{
    array_push( $events, array(
        array( 'from_event_index' => $i + count( $competitors ) / 2, 'from_event_rank' => 1 ),
        array( 'from_event_index' => ++$i + count( $competitors ) / 2, 'from_event_rank' => 1 )
    ) );
}

$round_matchups = array( );
for( $i = 0; $i < count( $competitors ) / 2 / 2; $i++ )
{
    array_push( $round_matchups, array(
        array( 'from_event_index' => $i + count( $competitors ), 'from_event_rank' => 1 ),
        array( 'from_event_index' => ++$i + count( $competitors ), 'from_event_rank' => 1 )
    ) );
}
$events = array_merge( $events, $round_matchups );

for( $i = 0; $i < count( $round_matchups ); $i++ )
{
    array_push( $events, array(
        array( 'from_event_index' => $i + count( $competitors ) + count( $competitors ) / 2 - 2, 'from_event_rank' => 2 ),
        array( 'from_event_index' => $i + count( $competitors ) + count( $competitors ) / 2 - 1, 'from_event_rank' => 1 )
    ) );
}

// finals
for( $i = 0; $i < count( $competitors ) / 2 / 2 / 2; $i++ )
{
    array_push( $events, array(
        array( 'from_event_index' => $i + count( $competitors ) / 2 * 3 - 2, 'from_event_rank' => 1 ),
        array( 'from_event_index' => ++$i + count( $competitors ) / 2 * 3 - 1, 'from_event_rank' => 1 )
    ) );
}

上面代码的输出:

$events = array(14) {
  [0]=>
  array(2) {
    [0]=>
    array(4) {
      ["team"]=>int(1)
    }
    [1]=>
    array(4) {
      ["team"]=>int(8)
    }
  }
  [1]=>
  array(2) {
    [0]=>
    array(4) {
      ["team"]=>int(4)
    }
    [1]=>
    array(4) {
      ["team"]=>int(5)
    }
  }
  [2]=>
  array(2) {
    [0]=>
    array(4) {
      ["team"]=>int(2)
    }
    [1]=>
    array(4) {
      ["team"]=>int(7)
    }
  }
  [3]=>
  array(2) {
    [0]=>
    array(4) {
      ["team"]=>int(3)
    }
    [1]=>
    array(4) {
      ["team"]=>int(6)
    }
  }
  [4]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(0)
      ["from_event_rank"]=>int(1)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(1)
      ["from_event_rank"]=>int(1)
    }
  }
  [5]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(2)
      ["from_event_rank"]=>int(1)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(3)
      ["from_event_rank"]=>int(1)
    }
  }
  [6]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(0)
      ["from_event_rank"]=>int(2)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(1)
      ["from_event_rank"]=>int(2)
    }
  }
  [7]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(2)
      ["from_event_rank"]=>int(2)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(3)
      ["from_event_rank"]=>int(2)
    }
  }
  [8]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(4)
      ["from_event_rank"]=>int(2)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(6)
      ["from_event_rank"]=>int(1)
    }
  }
  [9]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(5)
      ["from_event_rank"]=>int(2)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(7)
      ["from_event_rank"]=>int(1)
    }
  }
  [10]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(4)
      ["from_event_rank"]=>int(1)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(5)
      ["from_event_rank"]=>int(1)
    }
  }
  [11]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(8)
      ["from_event_rank"]=>int(1)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(9)
      ["from_event_rank"]=>int(1)
    }
  }
  [12]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(10)
      ["from_event_rank"]=>int(2)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(11)
      ["from_event_rank"]=>int(1)
    }
  }
  [13]=>
  array(2) {
    [0]=>
    array(2) {
      ["from_event_index"]=>int(10)
      ["from_event_rank"]=>int(1)
    }
    [1]=>
    array(2) {
      ["from_event_index"]=>int(12)
      ["from_event_rank"]=>int(1)
    }
  }
}

关于如何修改它以适用于 4 队、16 队或 2^n 队支架的任何想法?我觉得“半决赛”标题下的逻辑是应该重复 0+ 次,但每次我尝试根据总轮数循环它时,它只是重复与上一轮相同的匹配。

4

4 回答 4

4

好吧,我一直在努力研究现有的逻辑,并且能够生成 4 队、8 队、16 队和 32 队双败淘汰赛的时间表。逻辑不是必须简洁的,但它至少让我明白发生了什么。将来,我希望对其进行修改和清理,但现在必须这样做。

$rounds = log( count( $competitors ), 2 ) + 1;

// round one
for( $i = 0; $i < log( count( $competitors ), 2 ); $i++ )
{
    $seeded = array( );
    foreach( $competitors as $competitor )
    {
        $splice = pow( 2, $i );

        $seeded = array_merge( $seeded, array_splice( $competitors, 0, $splice ) );
        $seeded = array_merge( $seeded, array_splice( $competitors, -$splice ) );
    }
    $competitors = $seeded;
}

$events = array_chunk( $seeded, 2 );

if( $rounds > 2 )
{
    $round_index = count( $events );

    // second round
    for( $i = 0; $i < count( $competitors ) / 2; $i++ )
    {
        array_push( $events, array(
            array( 'from_event_index' => $i, 'from_event_rank' => 1 ), // rank 1 = winner
            array( 'from_event_index' => ++$i, 'from_event_rank' => 1 )
        ) );
    }

    $round_matchups = array( );
    for( $i = 0; $i < count( $competitors ) / 2; $i++ )
    {
        array_push( $round_matchups, array(
            array( 'from_event_index' => $i, 'from_event_rank' => 2 ), // rank 2 = loser
            array( 'from_event_index' => ++$i, 'from_event_rank' => 2 )
        ) );
    }
    $events = array_merge( $events, $round_matchups );

    for( $i = 0; $i < count( $round_matchups ); $i++ )
    {
        array_push( $events, array(
            array( 'from_event_index' => $i + count( $competitors ) / 2, 'from_event_rank' => 2 ),
            array( 'from_event_index' => $i + count( $competitors ) / 2 + count( $competitors ) / 2 / 2, 'from_event_rank' => 1 )
        ) );
    }
}

if( $rounds > 3 )
{
    // subsequent rounds
    for( $i = 0; $i < $rounds - 3; $i++ )
    {
        $round_events = pow( 2, $rounds - 3 - $i );
        $total_events = count( $events );

        for( $j = 0; $j < $round_events; $j++ )
        {
            array_push( $events, array(
                array( 'from_event_index' => $j + $round_index, 'from_event_rank' => 1 ),
                array( 'from_event_index' => ++$j + $round_index, 'from_event_rank' => 1 )
            ) );
        }

        for( $j = 0; $j < $round_events; $j++ )
        {
            array_push( $events, array(
                array( 'from_event_index' => $j + $round_index + $round_events * 2, 'from_event_rank' => 1 ),
                array( 'from_event_index' => ++$j + $round_index + $round_events * 2, 'from_event_rank' => 1 )
            ) );
        }

        for( $j = 0; $j < $round_events / 2; $j++ )
        {
            array_push( $events, array(
                array( 'from_event_index' => $j + $total_events, 'from_event_rank' => 2 ),
                array( 'from_event_index' => $j + $total_events + $round_events / 2, 'from_event_rank' => 1 )
            ) );
        }

        $round_index = $total_events;
    }

}

if( $rounds > 1 )
{
    // finals
    array_push( $events, array(
        array( 'from_event_index' => count( $events ) - 3, 'from_event_rank' => 1 ),
        array( 'from_event_index' => count( $events ) - 1, 'from_event_rank' => 1 )
     ) );
}

我已经验证了多达 32 个团队的结果(仅 2 的幂),并且能够生成一个包含 64 个团队的时间表,看起来是正确的。有时,坚持是有回报的。

于 2014-02-04T00:47:40.543 回答
2

在正常锦标赛中,比赛可以借助格雷码进行。也许您也可以将代码用于第二棵树。例如创建一个 2-pass 算法。你可以在这里阅读我的问题:锦标赛图表中的格雷码模式?.

于 2014-02-03T20:16:52.497 回答
0

已接受答案中的此算法非常复杂且难以遵循。我很努力,但感觉不像是开发人员会理解的东西,更像是数据科学家的解决方案。无论如何,这是我在 Kotlin 中的解决方案:

actual fun generateDE(teams: List<Team>, bracketSize: BracketSize): Bracket {
    val matchesWQueue: Queue<Match> = ArrayDeque<Match>()
    val matchesLQueue: Queue<Match> = ArrayDeque<Match>()
    val backfillQ: Queue<Match> = ArrayDeque<Match>()
    val participants = teams.toMutableList()
    val bracket = Bracket()

    for (i in 1..bracketSize.value / 2) { // Seed Round
        var team1: Team? = null
        var team2: Team? = null

        if (participants.isNotEmpty()) {
            team1 = participants[(0..participants.size - 1).random()]
            participants.remove(team1)
        }
        if (participants.isNotEmpty()) {
            team2 = participants[(0..participants.size - 1).random()]
            participants.remove(team2)
        }

        val seedMatch = Match(
            id = UUID.randomUUID().toString(),
            team1 = team1,
            team2 = team2,
            winner = null,
            match1 = null,
            match2 = null
        )

        matchesWQueue += seedMatch
        matchesLQueue += seedMatch
        bracket.upper += seedMatch
    }

    while (matchesWQueue.size > 1) { // Generate upper bracket matches
        val match1 = matchesWQueue.remove()
        val match2 = matchesWQueue.remove()

        val matchW = Match(
            id = UUID.randomUUID().toString(),
            team1 = null,
            team2 = null,
            winner = null,
            match1 = match1,
            match2 = match2
        )

        matchesWQueue += matchW
        bracket.upper += matchW
        backfillQ += matchW // add match to backfill for Lower Queue
    }

    var roundSwitch = bracketSize.value / 2
    var switch = false
    var counter = 0
    var switchedCounter = 0
    while (matchesLQueue.size > 0 && backfillQ.size > 0) { // Generate losers bracket matches
        var match1: Match?
        var match2: Match?
        if (switch) {
            match1 = matchesLQueue.remove()
            match2 = backfillQ.remove()
            switchedCounter += 2
            if (switchedCounter == roundSwitch) {
                // switch back
                roundSwitch /= 2
                switch = false
                // reset counters
                switchedCounter = 0
            }
        } else {
            match1 = matchesLQueue.remove()
            match2 = matchesLQueue.remove()
            counter += 2
            if (counter == roundSwitch) {
                switch = true
                counter = 0
            }
        }

        val matchL = Match(
            id = UUID.randomUUID().toString(),
            team1 = null,
            team2 = null,
            winner = null,
            match1 = match1,
            match2 = match2
        )
        matchesLQueue += matchL
        bracket.lower += matchL
    }

    // Add final match
    bracket.lower += Match(
        id = UUID.randomUUID().toString(),
        team1 = null,
        team2 = null,
        winner = null,
        match1 = matchesWQueue.remove(),
        match2 = matchesLQueue.remove()
    )

    return bracket
}

还决定玩得开心,并将其变成 Kotlin 多平台库:https ://github.com/hshar7/kotlin-brackets-multiplatform/blob/master/src/jvmMain/kotlin/brackets/BracketsJvm.kt

于 2020-01-06T16:25:12.387 回答
0

感谢您与我们分享此解决方案。我将您的算法翻译成 C#。也许有人可以使用它:

using System;
using System.Collections.Generic;
using System.Linq;
using TmBackend.Entities;

namespace TmBackend.Services
{
    partial class TournamentRepository
    {
        private void GenerateDoubleEliminationMatches(Tournament tournament)
        {
            var roundsCount = Convert.ToInt32(Math.Log(tournament.Teams.Count, 2)) + 1;

            var rand = new Random();
            var shuffledTeams = tournament.Teams.OrderBy(t => rand.Next()).ToArray();

            // Round 0
            var rounds = new List<TournamentRound>
            {
                new TournamentRound
                {
                    Matches = new List<TournamentMatch>(),
                    Order = 1.0,
                    Type = TournamentRoundType.Winnerbracket,
                }
            };

            for (var i = 0; i < shuffledTeams.Length; i += 2)
            {
                rounds[0].Matches.Add(new TournamentMatch
                {
                    MatchIndex = rounds[0].Matches.Count,
                    Scores = new List<TournamentMatchScore>
                    {
                        new TournamentMatchScore
                        {
                            TournamentTeamId = shuffledTeams[i].Id
                        },
                        new TournamentMatchScore
                        {
                            TournamentTeamId = shuffledTeams[i + 1].Id
                        },
                    },
                });
            }

            var matchIndex = rounds[0].Matches.Count();
            if (roundsCount > 2)
            {
                // second round
                var winnerRound = new TournamentRound
                {
                    Matches = new List<TournamentMatch>(),
                    Order = 2,
                    Type = TournamentRoundType.Winnerbracket
                };
                rounds.Add(winnerRound);
                var loserRound = new TournamentRound
                {
                    Matches = new List<TournamentMatch>(),
                    Order = 2,
                    Type = TournamentRoundType.Loserbracket
                };
                rounds.Add(loserRound);
                var loserRound2 = new TournamentRound
                {
                    Matches = new List<TournamentMatch>(),
                    Order = 2.5,
                    Type = TournamentRoundType.Loserbracket
                };
                rounds.Add(loserRound2);

                // winner bracket
                for (var i = 0; i < shuffledTeams.Length / 2; i++)
                {
                    var m = new TournamentMatch
                    {
                        MatchIndex = matchIndex++,
                        Scores = new List<TournamentMatchScore>
                        {
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = i,
                                PreviousMatchRank = MatchRank.Winner
                            },
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = ++i,
                                PreviousMatchRank = MatchRank.Winner
                            },
                        }
                    };
                    winnerRound.Matches.Add(m);
                }

                // loser bracket 2.0
                for (var i = 0; i < shuffledTeams.Length / 2; i++)
                {
                    var m = new TournamentMatch
                    {
                        MatchIndex = matchIndex++,
                        Scores = new List<TournamentMatchScore>
                        {
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = i,
                                PreviousMatchRank = MatchRank.Loser
                            },
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = ++i,
                                PreviousMatchRank = MatchRank.Loser
                            }
                        }
                    };
                    loserRound.Matches.Add(m);
                }

                // loser bracket 2.5
                var loserRoundCount = loserRound.Matches.Count;
                for (var i = 0; i < loserRoundCount; i++)
                {
                    var m = new TournamentMatch
                    {
                        MatchIndex = matchIndex++,
                        Scores = new List<TournamentMatchScore>
                        {
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = i + shuffledTeams.Length / 2,
                                PreviousMatchRank = MatchRank.Loser
                            },
                            new TournamentMatchScore
                            {
                                PreviousMatchIndex = i + shuffledTeams.Length / 2 + shuffledTeams.Length / 2 / 2,
                                PreviousMatchRank = MatchRank.Winner
                            }
                        }
                    };
                    loserRound2.Matches.Add(m);
                }
            }


            if (roundsCount > 3)
            {
                // subsequent rounds
                for (var i = 0; i < roundsCount - 3; i++)
                {
                    var matchesCountRound = (int) Math.Pow(2, roundsCount - 3 - i);
                    var matchesCountTotalBefore = rounds.Take(rounds.Count - 3).Sum(r => r.Matches.Count);
                    var matchesCountTotal = rounds.Sum(r => r.Matches.Count);

                    var winnerRound = new TournamentRound
                    {
                        Matches = new List<TournamentMatch>(),
                        Order = 3 + i,
                        Type = TournamentRoundType.Winnerbracket
                    };
                    rounds.Add(winnerRound);
                    var loserRound = new TournamentRound
                    {
                        Matches = new List<TournamentMatch>(),
                        Order = 3 + i,
                        Type = TournamentRoundType.Loserbracket
                    };
                    rounds.Add(loserRound);
                    var loserRound2 = new TournamentRound
                    {
                        Matches = new List<TournamentMatch>(),
                        Order = 3.5 + i,
                        Type = TournamentRoundType.Loserbracket
                    };
                    rounds.Add(loserRound2);

                    // winner bracket
                    for (var j = 0; j < matchesCountRound; j++)
                    {
                        var m = new TournamentMatch
                        {
                            MatchIndex = matchIndex++,
                            Scores = new List<TournamentMatchScore>
                            {
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = j + matchesCountTotalBefore,
                                    PreviousMatchRank = MatchRank.Winner
                                },
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = ++j + matchesCountTotalBefore,
                                    PreviousMatchRank = MatchRank.Winner
                                },
                            }
                        };
                        winnerRound.Matches.Add(m);
                    }

                    // loser bracket X.0
                    for (var j = 0; j < matchesCountRound; j++)
                    {
                        var m = new TournamentMatch
                        {
                            MatchIndex = matchIndex++,
                            Scores = new List<TournamentMatchScore>
                            {
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = j + matchesCountTotalBefore + matchesCountRound * 2,
                                    PreviousMatchRank = MatchRank.Winner
                                },
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = ++j + matchesCountTotalBefore + matchesCountRound * 2,
                                    PreviousMatchRank = MatchRank.Winner
                                },
                            }
                        };
                        loserRound.Matches.Add(m);
                    }

                    // loser bracket X.5
                    for (var j = 0; j < matchesCountRound / 2; j++)
                    {
                        var m = new TournamentMatch
                        {
                            MatchIndex = matchIndex++,
                            Scores = new List<TournamentMatchScore>
                            {
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = j + matchesCountTotal,
                                    PreviousMatchRank = MatchRank.Loser
                                },
                                new TournamentMatchScore
                                {
                                    PreviousMatchIndex = j + matchesCountTotal + matchesCountRound / 2,
                                    PreviousMatchRank = MatchRank.Winner
                                },
                            }
                        };
                        loserRound.Matches.Add(m);
                    }
                }
            }

            // finals
            if (roundsCount > 1)
            {
                var winnerRound = new TournamentRound
                {
                    Matches = new List<TournamentMatch>(),
                    Order = rounds.Count / 3 + 2,
                    Type = TournamentRoundType.Final
                };
                rounds.Add(winnerRound);

                var m = new TournamentMatch
                {
                    MatchIndex = matchIndex,
                    Scores = new List<TournamentMatchScore>
                    {
                        new TournamentMatchScore
                        {
                            PreviousMatchIndex = matchIndex - 3,
                            PreviousMatchRank = MatchRank.Winner
                        },
                        new TournamentMatchScore
                        {
                            PreviousMatchIndex = matchIndex - 1,
                            PreviousMatchRank = MatchRank.Winner
                        },
                    }
                };
                winnerRound.Matches.Add(m);
            }

            tournament.Rounds = rounds;
        }
    }
}
于 2019-07-26T19:05:26.357 回答