главная новое лучшее написать
3

100 задач с LeetCode мы уже обозрели. Пора переходить к более тяжёлым наркотикам.
Дальше будем смотреть на задачи из последнего Advent of Code.
Задача первого дня — Historian Hysteria несложная и затравочная 😻.

📘 Условие части I

Даны два списка целых чисел одинаковой длины. Необходимо оценить, насколько они различаются.
Для этого:

Пример:

После сопоставления по возрастанию:

💡 Идея

Нам нужно просто отсортировать оба списка и сопоставить соответствующие числа в них.

📘 Условие части II

Теперь требуется вычислить оценку схожести между двумя списками.
Для этого:

Пример:

💡 Идея

Если списки отсортировать, то одинаковые элементы будут расположены рядом. Это позволяет эффективно посчитать количество вхождений каждого значения, не используя хеш-таблицы или сложные структуры. А сопоставить элементы из разных списков можно будет процедурой близкой по смыслу с сортировкой слиянием.

🛠 Детали реализации 🛠

🔸 Общий шаг

  1. Списки сортируются заранее — это необходимо и для минимизации расхождений, и для эффективной группировки значений.

🔹 solve_simple

  1. Используется метод .zip() для попарного объединения элементов двух отсортированных списков.
  2. Для каждой пары чисел вычисляется модуль разности.
  3. Итоговая сумма расхождений находится с помощью .sum().

🔹 solve_hard

  1. Используются сгруппированные участки (chunk_by) каждого списка.
  2. Для каждого значения из левого списка ищется совпадающая группа в правом.
  3. Если значение совпадает, его вклад в итоговую сумму рассчитывается как
    значение × число повторов слева × число повторов справа.

⏱ Асимптотика

💻 Исходный код

use std::cmp::Ordering;
use super::TaskData;

/*
 * pub struct TaskData {
 *    pub nums_left: Vec<i32>,
 *     pub nums_right: Vec<i32>,
 * }
 */

/// Holds two sorted number lists and computes difference and similarity scores.
pub struct Solution {
    nums_left: Vec<i32>,
    nums_right: Vec<i32>,
}

impl Solution {
    /// Constructs a new Solution, sorting both input lists.
    pub fn new(mut data: TaskData) -> Self {
        data.nums_left.sort_unstable();
        data.nums_right.sort_unstable();
        Self {
            nums_left: data.nums_left,
            nums_right: data.nums_right,
        }
    }

    /// Part 1:
    /// Pairs sorted values and sums their absolute differences.
    pub fn solve_simple(&self) -> i32 {
        self.nums_left.iter()
            .zip(&self.nums_right)
            .map(|(&l, &r)| (l - r).abs())
            .sum()
    }

    /// Part 2:
    /// For each number in the left list, multiplies it by the number of times
    /// it appears in the right list, and sums the result.
    pub fn solve_hard(&self) -> i32 {
        // Group equal elements together in both lists
        let mut left_chunks = self.nums_left.chunk_by(|a, b| a == b);
        let mut right_chunks = self.nums_right.chunk_by(|a, b| a == b).peekable();

        let mut sim_score = 0;

        // Iterate over left groups
        for left in &mut left_chunks {
            let val_left = left[0];
            let left_count = left.len() as i32;

            // Advance right_chunks until val_right >= val_left
            while let Some(&right) = right_chunks.peek() {
                match val_left.cmp(&right[0]) {
                    Ordering::Less => break, // No more matches
                    Ordering::Equal => {
                        let right_count = right.len() as i32;
                        sim_score += val_left * left_count * right_count;
                    }
                    Ordering::Greater => {} // Skip unmatched right group
                }
                right_chunks.next();
            }
        }

        sim_score
    }
}

Также приведу и простой код для чтения входных данных
(чего не буду делать в обзорах следующих задач).

use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};

/// Holds two lists of numbers read from the input file
pub struct TaskData {
    pub nums_left: Vec<i32>,
    pub nums_right: Vec<i32>,
}

/// Responsible for parsing the input file into TaskData
pub struct Reader {}

impl Reader {
    /// Parses a file where each non-empty line contains a pair of integers
    pub fn parse_input(path: &str) -> Result<TaskData, Box<dyn Error>> {
        let reader = BufReader::new(File::open(path)?);
        let mut nums_left = Vec::new();
        let mut nums_right = Vec::new();

        for line in reader.lines() {
            let input = line?;
            if !input.is_empty() {
                // Parse line like "3 4" into (3, 4)
                let (n0, n1) = parse_line!(input; i32, i32);
                nums_left.push(n0);
                nums_right.push(n1);
            }
        }

        Ok(TaskData { nums_left, nums_right })
    }
}

/// Parses a line into a tuple of typed values with item count validation
macro_rules! parse_line {
    ( $ln:expr; $( $x:ty ), *) =>  {{
        let mut s = $ln.split_whitespace();
        let ans = (
            $(s.next()
                .ok_or_else(|| trace_me!("too few items in line: {}", $ln))?
                .parse::<$x>()?
            ), *
        );
        // Ensure there are no extra items
        s.next().xor(Some("")).ok_or_else(|| trace_me!("too many items in line: {}", $ln))?;
        ans
    }};
}

/// Formats error messages with file and line context
macro_rules! trace_me {
    ( $msg_fmt: expr, $($arg: expr),* ) => {
        trace_me!(format!($msg_fmt, $($arg), *))
    };
    ( $msg_fmt: expr) => {
        format!("Error '{}' at {}:{}", $msg_fmt, file!(), line!())
    }
}

Tags: #rust #adventofcode #algorithms #sorting