פיתרון Advent Of Code 2024 יום 7 - לא להיבהל, אני יודע לשנות את Integer
יום 7 של Advent Of Code לא היה מסובך והזכיר לי כמה כיף שאפשר ברובי לשנות את המחלקות המובנות בשפה. בואו נראה מה היה שם.
1. האתגר
נתון קלט שמורכב מתוצאה ואופרנדים:
190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20
אנחנו צריכים למצוא אם אפשר להוסיף את האופרטורים פלוס וכפול בין האופרנדים כדי להגיע לתוצאה. בחלק השני של האתגר מוסיפים לנו אופרטור בשם ||
שמשמעותו "שרשור" שני אופרנדים כך ש:
10 || 19 = 1019
ורוצים לראות אם אפשר להגיע לתוצאה עם האופרטור החדש.
2. פיתרון
החלק המרכזי של הפיתרון לקח בסך הכל מספר שורות ברובי:
def count_options(target, values)
results = [:+, :*].repeated_permutation(values.size - 1).to_a.map do |seq|
values[1..].zip(seq).reduce(values[0]) { |a, (v, op)| a.send(op, v) } == target
end
results.count(true)
end
הפונקציה repeated_permutation
מופעלת על מערך ומחזירה את כל הפרמוטציות עם חזרות על האיברים שלו. הנה דוגמה:
3.3.5 :003 > [1, 2].repeated_permutation(3).to_a
=> [[1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2], [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2]]
מה עושים? לוקחים את כל הפרמוטציות של האופרטורים ושותלים אותן ברשימת האופרנדים עם zip. אחרי זה מפעילים reduce כדי לרוץ על הרשימה ולבצע את הפעולות שהגרלנו ובסוף בודקים אם הגענו לתוצאה.
בשביל לקרוא את הקלט היה צריך רק עוד ביטוי רגולארי:
puts (File.readlines('input.txt').map do |line|
target, values = line.match(/(\d+): (.*)/).captures
count_options(target.to_i, values.split.map(&:to_i)).positive? ? target.to_i : 0
end.sum)
ומה עם החלק השני? מאוד פשוט, צריך רק להוסיף מתודה ל Integer:
class Integer
def op_concat(other)
(to_s + other.to_s).to_i
end
end
ואז אפשר להישאר עם אותה פונקציה קצרה ורק לשנות את מערך האופרטורים:
def count_options(target, values)
results = [:+, :*, :op_concat].repeated_permutation(values.size - 1).to_a.map do |seq|
values[1..].zip(seq).reduce(values[0]) { |a, (v, op)| a.send(op, v) } == target
end
results.count(true)
end
סך הכל הפיתרון המלא ברובי עם פחות מ 20 שורות היה:
class Integer
def op_concat(other)
(to_s + other.to_s).to_i
end
end
def count_options(target, values)
results = [:+, :*, :op_concat].repeated_permutation(values.size - 1).to_a.map do |seq|
values[1..].zip(seq).reduce(values[0]) { |a, (v, op)| a.send(op, v) } == target
end
results.count(true)
end
puts (File.readlines('input.txt').map do |line|
target, values = line.match(/(\d+): (.*)/).captures
count_options(target.to_i, values.split.map(&:to_i)).positive? ? target.to_i : 0
end.sum)
ואחרי שנתתי לקלוד לשפר קיבלתי את זה:
class Integer
# Method to concatenate two numbers as strings and convert back to integer
def op_concat(other)
(to_s + other.to_s).to_i
end
end
def count_options(target, values)
# Early return if only one value
return (values[0] == target ? 1 : 0) if values.size == 1
# Define available operations
operations = [:+, :*, :op_concat]
# Count combinations that equal the target
operations.repeated_permutation(values.size - 1).count do |ops|
result = values[0]
values[1..].zip(ops).each do |value, op|
result = result.send(op, value)
end
result == target
end
end
def solve_file(filename)
# Read the file and process each line
File.readlines(filename).sum do |line|
if line.strip.empty?
0 # Handle empty lines
else
# Parse the target and values
match = line.match(/(\d+):(.*)/)
if match
target = match[1].to_i
values = match[2].strip.split.map(&:to_i)
# If there's at least one way to reach the target, count that target
count_options(target, values).positive? ? target : 0
else
0 # Handle invalid lines
end
end
end
end
# Execute the solution if run as a script
if __FILE__ == $PROGRAM_NAME
puts solve_file('input.txt')
end
שיפור? אני לא בטוח.