| Crates.io | rotund_robin |
| lib.rs | rotund_robin |
| version | 0.1.1 |
| created_at | 2026-01-25 00:29:45.521899+00 |
| updated_at | 2026-01-25 00:48:50.138133+00 |
| description | A zero-copy round-robin schedule generator in Rust. |
| homepage | |
| repository | https://github.com/Drazbaz/rotund-robin |
| max_upload_size | |
| id | 2067774 |
| size | 14,406 |
A zero-copy round-robin schedule generator in Rust, ideal for scheduling sports tournaments or leagues. Supports full round-robin scheduling, optional limited rounds, and conversion to owned or Copy types for storage.
I was working on a game with a friend that needed a round-robin schedule for tournaments.
I started by implementing the round-robin function after reading about the algorithm in the blog post "Sports Scheduling Simplified: The Power of the Rotation Algorithm in Round Robin Tournament" by Uri Itai.
Once I had finished writing the round-robin function, I checked crates.io to see if any existing solutions may have also fitted my use case. The crates I found didn’t match the needs of my game, so I decided to extract the function and make the crate Rotund-Robin, in the hope that it might also be useful to others.
Add this to your Cargo.toml:
[dependencies]
rotund_robin = "0.1.1"
use rotund_robin::{round_robin, custom_round_robin, Schedule, RoundRobinError};
fn main() -> Result<(), RoundRobinError> {
let teams = vec!["A", "B", "C", "D"];
// Generate a full round-robin schedule (default)
let schedule: Schedule<_> = round_robin(&teams)?;
// Inspect schedule without cloning
for (i, round) in schedule.as_vec().iter().enumerate() {
println!("Round {}:", i + 1);
for (a, b) in round {
println!(" {} vs {}", a, b);
}
}
// Convert to owned Strings for storage
let owned_schedule: Vec<Vec<(String, String)>> = schedule.into_owned();
// Generate a custom number of rounds (advanced usage)
let custom_schedule = custom_round_robin(&teams, Some(2))?;
println!("Custom schedule generated with {} rounds", custom_schedule.rounds.len());
Ok(())
}
pub fn round_robin<'a, T>(teams: &'a [T]) -> Result<Schedule<'a, T>, RoundRobinError>
teams: slice of participating teams. Must be even.Schedule] on success, or a [RoundRobinError] if input is invalid.pub fn custom_round_robin<'a, T>(
teams: &'a [T],
rounds: Option<usize>
) -> Result<Schedule<'a, T>, RoundRobinError>
rounds: None means full round-robin; Some(n) schedules only n rounds.Schedule] on success, or a [RoundRobinError] if input is invalid.pub enum RoundRobinError {
NoTeams,
OddNumberOfTeams,
InvalidNumberOfRounds,
}
NoTeams – no teams were provided.OddNumberOfTeams – the number of teams is odd; round-robin requires an even number.InvalidNumberOfRounds – the number of rounds is zero or exceeds the maximum possible.Schedule Methodsas_vec(&self) → Vec<Vec<(&T, &T)>>
into_owned(&self) → Vec<Vec<(T, T)>> (for T: Clone)
into_owned_copy(&self) → Vec<Vec<(T, T)>> (for T: Copy)
Run all tests with:
cargo test
running 10 tests
test tests::test_basic_schedule ... ok
test tests::test_odd_number_of_teams ... ok
test tests::test_no_teams ... ok
test tests::test_zero_rounds_custom ... ok
test tests::test_too_many_rounds_custom ... ok
test tests::test_two_rounds_custom ... ok
test tests::test_full_round_robin_as_vec ... ok
test tests::test_full_round_robin_into_owned ... ok
test tests::test_full_round_robin_into_owned_copy ... ok
Generated schedule for 1000 teams in 17.2261ms
test tests::benchmark_large_schedule ... ok
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s