Fork me on GitHub

Project Notes

#357 Analyze a Baseball Game

Using rust to analyze a baseball game; cassidoo’s interview question of the week (2025-09-15). This was the day I advanced to consciously incompetent in the rules of baseball.

Notes

The interview question of the week (2025-09-15) asks us to analyze baseball stats in a 2D array:

You are given an array of arrays, where each inner array represents the runs scored by each team in an inning of a baseball game: [[home1, away1], [home2, away2], …]. Write a function that returns an object with the total runs for each team, which innings each team led, and who won the game.

Example:

const innings = [[1, 0], [2, 2], [0, 3], [4, 1]];


> analyzeBaseballGame(innings)
> {
   homeTotal: 7,
   awayTotal: 6,
   homeLedInnings: [1, 2, 4],
   awayLedInnings: [3],
   winner: "home"
 }

A Diversion into the Rules of Baseball

As a citizen of the rest of the world in which baseball is not a major sport, I soon realised I needed a refresher to do this properly! Wikipedia and ChatGPT to the rescue.. here’s what learned:

In baseball, the winner is the team that scores more runs than the other team by the end of the game.

  • A standard baseball game lasts nine innings
  • Runs are scored when a player safely reaches home plate after touching all four bases in order (first, second, third, home).
  • After nine innings, if one team has more runs, that team wins.

If the score is tied after nine innings, the game goes into extra innings until one team finishes an inning with more runs than the other, making them the winner. In MLB, there can normally be no “draw”, however a draw is possible in some circumstances:

  • weather-related
  • some leagues and special matches may limit the number of extra innings

When counting home-led or away-led innings:

  • You look at who was leading at the end of that inning (not just during).
  • If the score is tied at the end of the inning, it is usually counted as neither home-led nor away-led — it’s just recorded as a tie for that frame.
  • Some analyses will include a “tied” category so you can see how many innings were tied, others just omit those from the count entirely.

Implications for the Implementation

  • winner is determined by comparing homeTotal and awayTotal
  • home-led or away-led innings are determined by comparing homeTotal and awayTotal at the end of the innings
  • there’s no constraint mentioned, so I’ll assume draws are possible
  • I’ll also assume we ignore tied innings when calculating home-led/away-led

Initial Implementation

Created a new app cargo new bba, structured it as follows:

  • main.rs
    • main controller
    • accepts innings stats, calls the implementation, prints the result
  • lib.rs
    • implements the analysis function
    • I decided to go with conventional rust linting practices rather than follow the specification to the letter, hence snake-case analyze_baseball_game() instead of analyzeBaseballGame().

In action, here’s the result for the example provided:

$ cd bba
$ echo "[[1, 0], [2, 2], [0, 3], [4, 1]]" | cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/bba`
Enter the array of innings result arrays i.e. [[home, away]]:
Given Innings: [[1, 0], [2, 2], [0, 3], [4, 1]]
Result:
{
  "awayLedInnings": [
    3
  ],
  "awayTotal": 6,
  "homeLedInnings": [
    1,
    2,
    4
  ],
  "homeTotal": 7,
  "winner": "home"
}

I’m using serde_json to parse the input and pretty-print the results, however this means that the order and styling of the output values are determined by the internal implementation. Since sequence and formatting should not be significant for JSON, I’m going to just go with the flow and accept this variation from the example.

Here’s another example using a full 9-innings set of results:

$ echo "[[1, 0], [2, 5], [0, 3], [4, 1], [1, 0], [2, 5], [0, 3], [4, 1], [0, 0]]" | cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/bba`
Enter the array of innings result arrays i.e. [[home, away]]:
Given Innings: [[1, 0], [2, 5], [0, 3], [4, 1], [1, 0], [2, 5], [0, 3], [4, 1], [0, 0]]
Result:
{
  "awayLedInnings": [
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9
  ],
  "awayTotal": 18,
  "homeLedInnings": [
    1
  ],
  "homeTotal": 14,
  "winner": "away"
}

Tests

I’ve added some basic unit tests for the main algorithm:

$ cargo test
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running unittests src/lib.rs (target/debug/deps/bba-ba471bff2c56bc4c)

running 8 tests
test tests::test_all_zero_scores ... ok
test tests::test_away_wins ... ok
test tests::test_empty_innings ... ok
test tests::test_home_wins ... ok
test tests::test_draw ... ok
test tests::test_panic_on_inning_with_less_than_2_elements ... ok
test tests::test_example ... ok
test tests::test_panic_on_inning_with_more_than_2_elements ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/bba-698f36f01158bdfe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests bba

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Example Code

bba/src/main.rs:

use exitfailure::ExitFailure;
use failure::ResultExt;
use std::io;

use bba::analyze_baseball_game;

fn main() -> Result<(), ExitFailure> {
    println!("Enter the array of innings result arrays i.e. [[home, away]]:");
    let mut input = String::new();
    io::stdin().read_line(&mut input)
        .with_context(|_| "Failed to read from stdin")?;
    let innings: Vec<Vec<i32>> = serde_json::from_str(&input.trim())
        .with_context(|_| "Failed to parse innings as JSON array of integer pairs")?;
    println!("Given Innings: {:?}", innings);

    let result = analyze_baseball_game(&innings);
    println!("Result:\n{}", serde_json::to_string_pretty(&result).unwrap());

    Ok(())
}

bba/src/lib.rs:

//! algorithms for analyzing a baseball game
//!
use serde_json::json;

/// This function implements a basic analysis of a baseball game given the innings results
pub fn analyze_baseball_game(innings: &Vec<Vec<i32>>) -> serde_json::Value {
    let mut home_total = 0;
    let mut away_total = 0;
    let mut home_led_innings = Vec::new();
    let mut away_led_innings = Vec::new();

    for (i, inning) in innings.iter().enumerate() {
        if inning.len() != 2 {
            panic!("Each inning must have exactly 2 elements (home and away scores), but found {}", inning.len());
        }
        home_total += inning[0];
        away_total += inning[1];
        if away_total > home_total {
            away_led_innings.push(i + 1);
        } else if home_total > away_total {
            home_led_innings.push(i + 1);
        }
    }

    let winner = if home_total > away_total {
        "home"
    } else if away_total > home_total {
        "away"
    } else {
        "draw"
    };

    json!({
        "homeTotal": home_total,
        "awayTotal": away_total,
        "homeLedInnings": home_led_innings,
        "awayLedInnings": away_led_innings,
        "winner": winner
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_example() {
        assert_eq!(
            analyze_baseball_game(&vec![vec![1, 0], vec![2, 2], vec![0, 3], vec![4, 1]]),
            json!({
                "homeTotal": 7,
                "awayTotal": 6,
                "homeLedInnings": [1, 2, 4],
                "awayLedInnings": [3],
                "winner": "home"
            })
        );
    }

    #[test]
    fn test_home_wins() {
        assert_eq!(
            analyze_baseball_game(
                &vec![
                    vec![0, 1], vec![7, 5], vec![4, 3], vec![1, 2],
                    vec![0, 1], vec![7, 5], vec![4, 3], vec![1, 2], vec![0, 0]
                ]
            ),
            json!({
                "homeTotal": 24,
                "awayTotal": 22,
                "homeLedInnings": [2, 3, 4, 6, 7, 8, 9],
                "awayLedInnings": [1],
                "winner": "home"
            })
        );
    }

    #[test]
    fn test_away_wins() {
        assert_eq!(
            analyze_baseball_game(
                &vec![
                    vec![1, 0], vec![2, 5], vec![0, 3], vec![4, 1],
                    vec![1, 0], vec![2, 5], vec![0, 3], vec![4, 1], vec![0, 0]
                ]
            ),
            json!({
                "homeTotal": 14,
                "awayTotal": 18,
                "homeLedInnings": [1],
                "awayLedInnings": [2, 3, 4, 5, 6, 7, 8, 9],
                "winner": "away"
            })
        );
    }

    #[test]
    fn test_draw() {
        assert_eq!(
            analyze_baseball_game(
                &vec![
                    vec![1, 0], vec![2, 0], vec![2, 3], vec![0, 2],
                    vec![1, 0], vec![2, 0], vec![2, 3], vec![0, 2], vec![2, 2]
                ]
            ),
            json!({
                "homeTotal": 12,
                "awayTotal": 12,
                "homeLedInnings": [1, 2, 3, 5, 6, 7],
                "awayLedInnings": [],
                "winner": "draw"
            })
        );
    }

    #[test]
    fn test_panic_on_inning_with_less_than_2_elements() {
        let result = std::panic::catch_unwind(|| {
            analyze_baseball_game(&vec![vec![1]]);
        });
        assert!(result.is_err());
    }

    #[test]
    fn test_panic_on_inning_with_more_than_2_elements() {
        let result = std::panic::catch_unwind(|| {
            analyze_baseball_game(&vec![vec![1, 2, 3]]);
        });
        assert!(result.is_err());
    }

    #[test]
    fn test_empty_innings() {
        let result = analyze_baseball_game(&vec![]);
        assert_eq!(
            result,
            json!({
                "homeTotal": 0,
                "awayTotal": 0,
                "homeLedInnings": [],
                "awayLedInnings": [],
                "winner": "draw"
            })
        );
    }

    #[test]
    fn test_all_zero_scores() {
        assert_eq!(
            analyze_baseball_game(&vec![vec![0, 0], vec![0, 0], vec![0, 0]]),
            json!({
                "homeTotal": 0,
                "awayTotal": 0,
                "homeLedInnings": [],
                "awayLedInnings": [],
                "winner": "draw"
            })
        );
    }
}

Credits and References

About LCK#357 rustcassidoo

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader