我正在尝试创建一些逻辑来生成double-elimination tournament bracket中的事件计划。
以下是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次以上,但每次我尝试根据总轮数循环它时,它只重复与前一轮相同的匹配。
答案 0 :(得分:3)
好吧,我一直在跋涉现有的逻辑,并能够为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个团队的计划,这似乎是正确的。有时候,持久性会得到回报。
答案 1 :(得分:2)
在正常比赛中,可以借助灰色代码放置游戏。也许您也可以将代码用于第二棵树。例如,创建一个2遍算法。您可以在此处阅读我的问题:Gray code pattern in tournament chart?。
答案 2 :(得分: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;
}
}
}
答案 3 :(得分: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