Last active
March 28, 2025 02:16
-
-
Save amirrajan/747afca16b1096853cc783b7a3b50182 to your computer and use it in GitHub Desktop.
Advent of Code 2024
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
C# 4 lyfe, bro!