Zytkowski's Thought Dumpster

Advent of Code 2023 - Day 07

I'ts that time of year again, the Advent of Code! I'm completing the challenges this year using Rust 🦀 I wish I could have more time to cleanup day 7's condition hell 😅!

I'll be posting them here, just to document how I'm solving each challenge, and I highly encourage you to check them out before reading my solutions!

Here's the Day 7 Challenge

BTW: Complete Part 1 to have access to Part 2 😉

Solution for Part 1

use std::cmp::Ordering;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines, Result};
use std::path::Path;
use std::str::FromStr;
use crate::HandType::{FiveOfAKind, FourOfAKind, FullHouse, ThreeOfAKind, TwoPair};
use itertools::{Itertools, rev};

#[derive(Eq, Hash, PartialEq, Debug)]
enum HandType {
    FiveOfAKind,
    FourOfAKind,
    FullHouse,
    ThreeOfAKind,
    TwoPair,
    OnePair,
    HighCard,
}

const HAND_STRENGTH: [HandType; 7] = [
    FiveOfAKind,
    FourOfAKind,
    FullHouse,
    ThreeOfAKind,
    TwoPair,
    HandType::OnePair,
    HandType::HighCard,
];

#[derive(Eq, Hash, PartialEq, Debug)]
struct Hand {
    original_hand: String,
    hand_type: HandType,
    bid: i32,
}

const LABELS: [char; 13] = [
    'A',
    'K',
    'Q',
    'J',
    'T',
    '9',
    '8',
    '7',
    '6',
    '5',
    '4',
    '3',
    '2',
];


fn main() {
    let input_file_path = "path_to_input";
    let mut curr_sum = 0;
    let iterator = for_each_line(input_file_path)
        .unwrap_or_else(|_| panic!("Could not find input file {}", input_file_path));
    let mut hands: Vec<Hand> = vec![];
    for line in iterator.flatten() {
        let (hand, bid) = line.split_once(' ').unwrap();
        let hand: Hand = build_hand(hand, bid);
        hands.push(hand);
    }
    hands.sort_by(hand_order);
    for (idx, hand) in rev(hands).enumerate() {
        curr_sum += (i32::try_from(idx).unwrap() + 1) * hand.bid;
    }
    println!("Sum of all winning cards: {}", curr_sum);
}

fn hand_order(a: &Hand, b: &Hand) -> Ordering {
    let a_hand_pos = HAND_STRENGTH.iter().position(|i| i == &a.hand_type).unwrap();
    let b_hand_pos = HAND_STRENGTH.iter().position(|i| i == &b.hand_type).unwrap();
    match a_hand_pos.cmp(&b_hand_pos) {
        Ordering::Less => { Ordering::Less }
        Ordering::Greater => { Ordering::Greater}
        Ordering::Equal => {
            for idx in 0..5 {
                let idx = idx as usize;
                let a_char = a.original_hand.chars().nth(idx).unwrap();
                let b_char = b.original_hand.chars().nth(idx).unwrap();
                let a_position = LABELS.iter().position(|i| i == &a_char).unwrap();
                let b_position = LABELS.iter().position(|i| i == &b_char).unwrap();
                match a_position.cmp(&b_position) {
                    Ordering::Less => { return Ordering::Less }
                    Ordering::Greater => { return Ordering::Greater}
                    Ordering::Equal => { continue }
                }
            }
            Ordering::Equal
        }
    }
}

fn build_hand(original_hand: &str, bid: &str) -> Hand {
    let bid = i32::from_str(bid).unwrap();
    let mut ordered_hand = original_hand.chars()
        .collect::<Vec<char>>();
    ordered_hand.sort_by(|a, b| {
        let a_position = LABELS.iter().position(|i| i == a).unwrap();
        let b_position = LABELS.iter().position(|i| i == b).unwrap();
        a_position.cmp(&b_position)
    });

    if ordered_hand.clone().iter().all(|i| i == &ordered_hand[0]) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FiveOfAKind,
            bid,
        };
    }
    if ordered_hand.clone().windows(4).any(|chunk| {
        chunk.iter().all(|i| {
            i == &chunk[0]
        })
    }) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FourOfAKind,
            bid,
        };
    }
    if is_full_house(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FullHouse,
            bid,
        };
    }
    if ordered_hand.clone().windows(3)
        .any(|chunk| chunk.iter().all(|i| i == &chunk[0])) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: ThreeOfAKind,
            bid,
        };
    }
    if is_two_pair(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: TwoPair,
            bid,
        };
    }
    if ordered_hand.clone().windows(2)
        .any(|chunk| chunk.iter().all(|i| i == &chunk[0])) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: HandType::OnePair,
            bid,
        };
    }
    Hand {
        original_hand: original_hand.into(),
        hand_type: HandType::HighCard,
        bid,
    }
}

fn is_two_pair(hand: &[char]) -> bool {
    let deduplicated_hand = hand.iter().unique().collect_vec();
    deduplicated_hand.len() == 3
}

fn is_full_house(hand: &[char]) -> bool {
    return (hand[0..=1].iter().all(|i| i == &hand[0]) && hand[2..=4].iter().all(|i| i == &hand[2])) ||
        (hand[0..=2].iter().all(|i| i == &hand[0]) && hand[3..=4].iter().all(|i| i == &hand[3]));
}

fn for_each_line<P: AsRef<Path>>(filename: P) -> Result<Lines<BufReader<File>>> {
    let file = File::open(filename)?;
    Ok(BufReader::new(file).lines())
}

Solution for Part 2

use std::cmp::Ordering;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines, Result};
use std::path::Path;
use std::str::FromStr;
use crate::HandType::{FiveOfAKind, FourOfAKind, FullHouse, ThreeOfAKind, TwoPair};
use itertools::{Itertools, rev};

#[derive(Eq, Hash, PartialEq, Debug)]
enum HandType {
    FiveOfAKind,
    FourOfAKind,
    FullHouse,
    ThreeOfAKind,
    TwoPair,
    OnePair,
    HighCard,
}

const HAND_STRENGTH: [HandType; 7] = [
    FiveOfAKind,
    FourOfAKind,
    FullHouse,
    ThreeOfAKind,
    TwoPair,
    HandType::OnePair,
    HandType::HighCard,
];

#[derive(Eq, Hash, PartialEq, Debug)]
struct Hand {
    original_hand: String,
    hand_type: HandType,
    bid: i32,
}

const LABELS: [char; 13] = [
    'A',
    'K',
    'Q',
    'T',
    '9',
    '8',
    '7',
    '6',
    '5',
    '4',
    '3',
    '2',
    'J',
];


fn main() {
    let input_file_path = "path_to_input_file";
    let mut curr_sum = 0;
    let iterator = for_each_line(input_file_path)
        .unwrap_or_else(|_| panic!("Could not find input file {}", input_file_path));
    let mut hands: Vec<Hand> = vec![];
    for line in iterator.flatten() {
        let (hand, bid) = line.split_once(' ').unwrap();
        let hand: Hand = build_hand(hand, bid);
        hands.push(hand);
    }
    hands.sort_by(hand_order);
    for (idx, hand) in rev(hands).enumerate() {
        curr_sum += (i32::try_from(idx).unwrap() + 1) * hand.bid;
    }
    println!("Sum of all winning cards: {}", curr_sum);
}

fn hand_order(a: &Hand, b: &Hand) -> Ordering {
    let a_hand_pos = HAND_STRENGTH.iter().position(|i| i == &a.hand_type).unwrap();
    let b_hand_pos = HAND_STRENGTH.iter().position(|i| i == &b.hand_type).unwrap();
    match a_hand_pos.cmp(&b_hand_pos) {
        Ordering::Less => { Ordering::Less }
        Ordering::Greater => { Ordering::Greater}
        Ordering::Equal => {
            for idx in 0..5 {
                let idx = idx as usize;
                let a_char = a.original_hand.chars().nth(idx).unwrap();
                let b_char = b.original_hand.chars().nth(idx).unwrap();
                let a_position = LABELS.iter().position(|i| i == &a_char).unwrap();
                let b_position = LABELS.iter().position(|i| i == &b_char).unwrap();
                match a_position.cmp(&b_position) {
                    Ordering::Less => { return Ordering::Less }
                    Ordering::Greater => { return Ordering::Greater}
                    Ordering::Equal => { continue }
                }
            }
            Ordering::Equal
        }
    }
}

fn build_hand(original_hand: &str, bid: &str) -> Hand {
    let bid = i32::from_str(bid).unwrap();
    let mut ordered_hand = original_hand.chars()
        .collect::<Vec<char>>();
    ordered_hand.sort_by(|a, b| {
        let a_position = LABELS.iter().position(|i| i == a).unwrap();
        let b_position = LABELS.iter().position(|i| i == b).unwrap();
        a_position.cmp(&b_position)
    });

    if is_five_of_a_kind(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FiveOfAKind,
            bid,
        };
    }
    if ordered_hand.as_slice().iter().any(|c| c == &'J') {
        return build_joker_based_hand(ordered_hand.as_slice(), original_hand, bid)
    }
    if is_four_of_a_kind(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FourOfAKind,
            bid,
        };
    }
    if is_full_house(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FullHouse,
            bid,
        };
    }
    if is_three_of_a_kind(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: ThreeOfAKind,
            bid,
        };
    }
    if is_two_pair(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: TwoPair,
            bid,
        };
    }
    if is_one_pair(&ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: HandType::OnePair,
            bid,
        };
    }
    Hand {
        original_hand: original_hand.into(),
        hand_type: HandType::HighCard,
        bid,
    }
}

fn build_joker_based_hand(ordered_hand: &[char], original_hand: &str, bid: i32) -> Hand {
    let joker_count : Vec<&char> = ordered_hand.iter().filter(|c| c == &&'J').collect();
    let joker_count = joker_count.len();
    if is_four_of_a_kind(ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FiveOfAKind,
            bid,
        };
    }
    if is_full_house(ordered_hand) && joker_count >= 2 {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FiveOfAKind,
            bid,
        };
    }
    if is_three_of_a_kind(ordered_hand) && [1, 3].contains(&joker_count) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FourOfAKind,
            bid,
        };
    }
    if is_two_pair(ordered_hand) && joker_count == 2 {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FourOfAKind,
            bid,
        };
    }
    if is_two_pair(ordered_hand) && joker_count == 1 {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: FullHouse,
            bid,
        };
    }
    if is_one_pair(ordered_hand) {
        return Hand {
            original_hand: original_hand.into(),
            hand_type: ThreeOfAKind,
            bid,
        };
    }
    Hand {
        original_hand: original_hand.into(),
        hand_type: HandType::OnePair,
        bid,
    }
}

fn is_five_of_a_kind(ordered_hand: &[char]) -> bool {
    ordered_hand.clone().iter().all(|i| i == &ordered_hand[0])
}

fn is_one_pair(ordered_hand: &[char]) -> bool {
    ordered_hand.clone().windows(2)
        .any(|chunk| chunk.iter().all(|i| i == &chunk[0]))
}

fn is_three_of_a_kind(ordered_hand: &[char]) -> bool {
    ordered_hand.clone().windows(3)
        .any(|chunk| chunk.iter().all(|i| i == &chunk[0]))
}

fn is_four_of_a_kind(ordered_hand: &[char]) -> bool {
    ordered_hand.clone().windows(4).any(|chunk| {
        chunk.iter().all(|i| {
            i == &chunk[0]
        })
    })
}

fn is_two_pair(hand: &[char]) -> bool {
    let deduplicated_hand = hand.iter().unique().collect_vec();
    deduplicated_hand.len() == 3
}

fn is_full_house(hand: &[char]) -> bool {
    return (hand[0..=1].iter().all(|i| i == &hand[0]) && hand[2..=4].iter().all(|i| i == &hand[2])) ||
        (hand[0..=2].iter().all(|i| i == &hand[0]) && hand[3..=4].iter().all(|i| i == &hand[3]));
}

fn for_each_line<P: AsRef<Path>>(filename: P) -> Result<Lines<BufReader<File>>> {
    let file = File::open(filename)?;
    Ok(BufReader::new(file).lines())
}