Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Last active March 28, 2025 02:16
Show Gist options
  • Save amirrajan/747afca16b1096853cc783b7a3b50182 to your computer and use it in GitHub Desktop.
Save amirrajan/747afca16b1096853cc783b7a3b50182 to your computer and use it in GitHub Desktop.
Advent of Code 2024
def parse_input path
content = File.read path
list_1 = []
list_2 = []
content.each_line do |line|
l = line.strip.gsub(/\s+/, ' ').split(' ')
next if l.length != 2
list_1 << l[0].to_i
list_2 << l[1].to_i
end
{
list_1: list_1,
list_2: list_2
}
end
def part_one
input = parse_input 'input'
list_1 = input[:list_1]
list_2 = input[:list_2]
list_1.sort!
list_2.sort!
differences = []
list_1.each_with_index do |n, i|
differences << (n - list_2[i]).abs
end
puts differences.sum
end
def occurrence_count list
result = {}
list.each do |n|
result[n] ||= 0
result[n] += 1
end
result
end
def part_two
input = parse_input 'input'
list_1 = input[:list_1]
list_2 = input[:list_2]
list_2_occurrence_count = occurrence_count list_2
result = list_1.map.with_index do |n, i|
{
id: i,
number: n,
occurrence_count: list_2_occurrence_count[n] || 0
}
end
result = result.map do |h|
{ **h, line_sum: h[:number] * h[:occurrence_count] }
end
total = result.map { |h| h[:line_sum] }.sum
puts total
end
part_two
# this function parses the input file
# each line is split on spaces, and then
# each token is converted to an int via .to_i
# eg, the following txt input:
# 65 67 70 72 74 73
# 32 35 37 39 39
# 28 31 34 35 38 39 43
# returns an List<List<int>>:
# [
# [65, 67, 70, 72, 74, 73],
# [32, 35, 37, 39, 39],
# [28, 31, 34, 35, 38, 39 43],
# ]
def parse_input path
File.read(path).each_line.map do |l|
l.strip.split(" ").map(&:to_i)
end
end
# given an array of numbers, this function returns
# true if all numbers are strictly increasing
# this leverages Ruby's Enumberable#each_cons method.
# each_cons(2) means "each consecutive 2 entries"
# eg:
# - if you gave the array [3, 2, 4, 5] and used each_cons(2),
# you would get an enumberable with the following pairs:
# - 3, 2
# - 2, 4
# - 4, 5
# - this is then sent to map, which takes those pairs and
# performs "a > b" (returns an array of booleans)
# - after that, all? is called on the array of booleans (all values must be true)
def all_increasing? numbers
numbers.each_cons(2)
.map { |a, b| a > b }
.all?
end
# same thing as all_increasing, except instead of a > b, we
# test on a < b
def all_decreasing? numbers
numbers.each_cons(2).map { |a, b| a < b }.all?
end
# for part two of this advent of code, if the difference
# between the two values are within 1 and 3, then it's
# considered a valid input (even if it isn't strictly increasing or decreasing)
# we leverage each_cons(2) again to compare the differences
def all_within_diff_range? numbers, min, max
numbers.each_cons(2)
.map { |a, b| (a - b).abs }
.map { |d| d >= min && d <= max }
.all?
end
# a series is considered safe if:
# - all numbers are increasing
# - all numbers are decreasing
# - if the difference between to numbers is within the acceptable range of 1 to 3
def safe? numbers
# if numbers aren't strictly increasing or strictly decreasing,
# return false immediately
return false if !all_increasing?(numbers) && !all_decreasing?(numbers)
# if the check above is passed, then check if
# the differences are within range
all_within_diff_range?(numbers, 1, 3)
end
# from the problem definition in part 2:
# > Now, the same rules apply as before, except if
# > removing a single level from an unsafe report would
# > make it safe, the report instead counts as safe.
#
# this function returns all number combinations, with each index value removed
# for example, if you gave it [1, 2, 3, 4, 5]
# this function would return:
# [
# [2, 3, 4, 5],
# [1, 3, 4, 5],
# [1, 2, 4, 5],
# [1, 2, 3, 5],
# [1, 2, 3, 4, 5],
# ]
def combinations numbers
# get the length of the array and do a "map with index"
# if the array length is five, then "i" would be 0, 1, 2, 3, 4
numbers.length.times.map do |i|
# now that we have the index, look at the
# numbers array (with both the number and index "j")
# and reject the index "j" if it matches "i"
# return the number value
# breakdown:
# - given numbers: [8, 9, 10, 11]
# - map with index would give you
# [[8, 0], [9, 1], [10, 2], [11, 3]]
# where the first value is the number and the second value is index "j"
# - if "i" is 2, then the reject would return:
# [[8, 0], [9, 1], [11, 3]]
# - the final map plucks out the numbers and drop "j"
# [8, 9, 11]
# the final step for combinations, is to add the original numbers
# back into the list
numbers.map # [8, 9, 10, 11]
.with_index # [[8, 0], [9, 1], [10, 2], [11, 3]]
.reject { |n, j| i == j } # [[8, 0], [9, 1], [11, 3]] (assuming "i" is 2)
.map { |n, j| n } # [8, 9, 11, 3]
end + [numbers]
# if you wrote this using for loops in C#
#
# public static List<List<int>> Combinations(List<int> numbers)
# {
# List<List<int>> results = new List<List<int>>();
# for(int i = 0; i < numbers.Count; i++)
# {
# List<int> combination = new List<int>();
# for(int j = 0; j < numbers.Count; j++)
# {
# if(i == j) continue;
# combination.Add(numbers[j]);
# }
# results.Add(combination);
# }
# results.Add(new List<int>(numbers));
# return results;
# }
#
# vs Linq variant
#
# public static List<List<int>> Combinations(List<int> numbers)
# {
# List<List<int>> results = Enumerable.Range(0, numbers.Count)
# .Select(i =>
# numbers.Where((num, j) => i != j)
# .ToList()
# )
# .ToList();
# results.Add(new List<int>(numbers)); // append numbers to the final result
# return results;
# }
#
# vs
#
# def combinations numbers
# numbers.length.times.map do |i|
# numbers.map
# .with_index
# .reject { |n, j| i == j }
# .map { |n, j| n }
# end + [numbers]
# end
#
end
# the line is considered "dampner safe" if any of the combinations
# of numbers is safe
def dampener_safe? numbers
combinations(numbers).any? { |ns| safe? ns }
end
def part_1
parsed = parse_input "input"
safe_result = parsed.map { |numbers| safe? numbers }
puts safe_result.find_all { |s| s }.length
end
def part_2
parsed = parse_input "input"
safe_result = parsed.map { |numbers| dampener_safe? numbers }
puts safe_result.find_all { |s| s }.length
end
part_2
def part_1 path
content = File.read path
index = 0
tokens = []
while index < content.length
char = content[index]
if char.to_i.to_s == char
tokens << { char: char, type: :digit }
elsif char == "-"
tokens << { char: char, type: :dash }
elsif char == "("
tokens << { char: char, type: :open_paren }
elsif char == ")"
tokens << { char: char, type: :close_paren }
elsif char == ","
tokens << { char: char, type: :comma }
else
tokens << { char: char, type: :string }
end
index += 1
end
collapsed_mul = []
index = 0
while index < tokens.length
t1 = tokens[index]
t2 = tokens[index + 1]
t3 = tokens[index + 2]
if ((t1 && t2 && t3) &&
(t1[:type] == :string && t1[:char] == "m" &&
t2[:type] == :string && t2[:char] == "u" &&
t3[:type] == :string && t3[:char] == "l"))
collapsed_mul << { char: "mul", type: :mul_string }
index += 3
else
collapsed_mul << t1
index += 1
end
end
collapsed_digits = []
index = 0
while index < collapsed_mul.length
t = collapsed_mul[index]
if t[:type] == :digit
working = []
digit_index = index
while collapsed_mul[digit_index] && collapsed_mul[digit_index][:type] == :digit
working << collapsed_mul[digit_index][:char]
digit_index += 1
end
collapsed_digits << { type: :digit, char: working.join }
index += working.length
else
collapsed_digits << t
index += 1
end
end
valid_muls = []
index = 0
while index < collapsed_digits.length
t = collapsed_digits[index]
t1 = collapsed_digits[index + 1]
t2 = collapsed_digits[index + 2]
t3 = collapsed_digits[index + 3]
t4 = collapsed_digits[index + 4]
t5 = collapsed_digits[index + 5]
t_is_mul = t && t[:type] == :mul_string
t1_is_open = t1 && t1[:type] == :open_paren
t2_is_digit = t2 && t2[:type] == :digit
t3_is_comma = t3 && t3[:type] == :comma
t4_is_digit = t4 && t4[:type] == :digit
t5_is_close = t5 && t5[:type] == :close_paren
if t_is_mul && t1_is_open && t2_is_digit && t3_is_comma && t4_is_digit && t5_is_close
valid_muls << { digit_1: t2[:char], digit_2: t4[:char] }
end
index += 1
end
s = valid_muls.map do |t|
t[:digit_1].to_i * t[:digit_2].to_i
end.sum
puts s
end
def part_2 path
content = File.read path
index = 0
tokens = []
while index < content.length
char = content[index]
if char.to_i.to_s == char
tokens << { char: char, type: :digit }
elsif char == "-"
tokens << { char: char, type: :dash }
elsif char == "("
tokens << { char: char, type: :open_paren }
elsif char == ")"
tokens << { char: char, type: :close_paren }
elsif char == ","
tokens << { char: char, type: :comma }
else
tokens << { char: char, type: :string }
end
index += 1
end
collapsed_mul = []
index = 0
while index < tokens.length
t1 = tokens[index]
t2 = tokens[index + 1]
t3 = tokens[index + 2]
t4 = tokens[index + 3]
t5 = tokens[index + 4]
t6 = tokens[index + 5]
t7 = tokens[index + 6]
if (
(t1 && t2 && t3) &&
(
t1[:type] == :string && t1[:char] == "m" &&
t2[:type] == :string && t2[:char] == "u" &&
t3[:type] == :string && t3[:char] == "l"
)
)
collapsed_mul << { char: "mul", type: :mul_string }
index += 3
elsif (
(t1 && t2 && t3 && t4 && t5 && t6 && t7) &&
(
t1[:type] == :string && t1[:char] == "d" &&
t2[:type] == :string && t2[:char] == "o" &&
t3[:type] == :string && t3[:char] == "n" &&
t4[:type] == :string && t4[:char] == "'" &&
t5[:type] == :string && t5[:char] == "t" &&
t6[:type] == :open_paren &&
t7[:type] == :close_paren
)
)
collapsed_mul << { char: "don't()", type: :dont_string }
index += 7
elsif (
(t1 && t2 && t3 && t4) &&
(
t1[:type] == :string && t1[:char] == "d" &&
t2[:type] == :string && t2[:char] == "o" &&
t3[:type] == :open_paren &&
t4[:type] == :close_paren
)
)
collapsed_mul << { char: "do()", type: :do_string }
index += 4
else
collapsed_mul << t1
index += 1
end
end
collapsed_digits = []
index = 0
while index < collapsed_mul.length
t = collapsed_mul[index]
if t[:type] == :digit
working = []
digit_index = index
while collapsed_mul[digit_index] && collapsed_mul[digit_index][:type] == :digit
working << collapsed_mul[digit_index][:char]
digit_index += 1
end
collapsed_digits << { type: :digit, char: working.join }
index += working.length
else
collapsed_digits << t
index += 1
end
end
valid_muls = []
index = 0
mul_enabled = true
while index < collapsed_digits.length
t = collapsed_digits[index]
t1 = collapsed_digits[index + 1]
t2 = collapsed_digits[index + 2]
t3 = collapsed_digits[index + 3]
t4 = collapsed_digits[index + 4]
t5 = collapsed_digits[index + 5]
t_is_mul = t && t[:type] == :mul_string
t1_is_open = t1 && t1[:type] == :open_paren
t2_is_digit = t2 && t2[:type] == :digit
t3_is_comma = t3 && t3[:type] == :comma
t4_is_digit = t4 && t4[:type] == :digit
t5_is_close = t5 && t5[:type] == :close_paren
if t && t[:type] == :do_string
mul_enabled = true
elsif t && t[:type] == :dont_string
mul_enabled = false
elsif mul_enabled && t_is_mul && t1_is_open && t2_is_digit && t3_is_comma && t4_is_digit && t5_is_close
valid_muls << { digit_1: t2[:char], digit_2: t4[:char] }
end
index += 1
end
s = valid_muls.map do |t|
t[:digit_1].to_i * t[:digit_2].to_i
end.sum
puts s
puts "DONE"
end
part_1 "input"
part_2 "input"
@JustinJohnWilliams
Copy link

C# 4 lyfe, bro!

using System;
using System.Collections.Generic;
using System.IO;

class Program
{
    static Dictionary<string, List<int>> ParseInput(string path)
    {
        string content = File.ReadAllText(path);
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();

        StringReader reader = new StringReader(content);
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            line = System.Text.RegularExpressions.Regex.Replace(line.Trim(), @"\s+", " ");
            string[] parts = line.Split(' ');
            if (parts.Length != 2)
                continue;

            int val1, val2;
            if (int.TryParse(parts[0], out val1) && int.TryParse(parts[1], out val2))
            {
                list1.Add(val1);
                list2.Add(val2);
            }
        }

        Dictionary<string, List<int>> result = new Dictionary<string, List<int>>();
        result["list_1"] = list1;
        result["list_2"] = list2;
        return result;
    }

    static void PartOne()
    {
        Dictionary<string, List<int>> input = ParseInput("input");
        List<int> list1 = input["list_1"];
        List<int> list2 = input["list_2"];

        list1.Sort();
        list2.Sort();

        List<int> differences = new List<int>();
        for (int i = 0; i < list1.Count; i++)
        {
            differences.Add(Math.Abs(list1[i] - list2[i]));
        }

        int sum = 0;
        foreach (int diff in differences)
        {
            sum += diff;
        }

        Console.WriteLine(sum);
    }

    static Dictionary<int, int> OccurrenceCount(List<int> list)
    {
        Dictionary<int, int> result = new Dictionary<int, int>();
        foreach (int n in list)
        {
            if (!result.ContainsKey(n))
                result[n] = 0;
            result[n]++;
        }
        return result;
    }

    static void PartTwo()
    {
        Dictionary<string, List<int>> input = ParseInput("input");
        List<int> list1 = input["list_1"];
        List<int> list2 = input["list_2"];

        Dictionary<int, int> list2OccurrenceCount = OccurrenceCount(list2);

        List<int> lineSums = new List<int>();
        for (int i = 0; i < list1.Count; i++)
        {
            int number = list1[i];
            int count = list2OccurrenceCount.ContainsKey(number) ? list2OccurrenceCount[number] : 0;
            int lineSum = number * count;
            lineSums.Add(lineSum);
        }

        int total = 0;
        foreach (int sum in lineSums)
        {
            total += sum;
        }

        Console.WriteLine(total);
    }

    static void Main(string[] args)
    {
        PartTwo();
        // To run part one instead, call PartOne();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment