changeset 23:f34254b67082

Add failed and almost complete Day 12 part 1 Read some discussion around solutions in other languages, realised that recursion may work. And we can do caching. Also includes failed map/reduce attempts.
author IBBoard <dev@ibboard.co.uk>
date Sat, 16 Dec 2023 20:04:27 +0000
parents ad73a2ff3d06
children 19481b061461
files day12-failed.rb day12.rb day12.txt
diffstat 3 files changed, 233 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/day12-failed.rb	Sat Dec 16 20:04:27 2023 +0000
@@ -0,0 +1,96 @@
+#! /usr/bin/env ruby
+
+if ARGV.length != 1
+        abort("Incorrect arguments - needs input file")
+elsif not File.exist? (ARGV[0])
+	abort("File #{ARGV[0]} did not exist")
+end
+
+file = ARGV[0]
+
+RowData = Struct.new(:symbols, :counts)
+CandidateCollector = Struct.new(:completed, :pending)
+
+def find_earliest_positions(symbols, counts)
+        # We need to skip items (to separate each object) but I don't know enough Ruby to
+        # do that in a map/reduce/etc (if it's even possible), so just use classic iteration
+        char_pos = 0
+        count_pos = 0
+        match_start = 0
+        earliest_matches = []
+#        puts "Counts: #{counts}"
+        while char_pos < symbols.length
+                # FIXME: Doesn't take account of pre-existing fixed blocks if there are enough question marks
+                if symbols[char_pos] != "."
+                        if (char_pos - match_start + 1) == counts[count_pos]
+                                if symbols[char_pos+1] != "#"
+                                        earliest_matches << match_start
+#                                        puts "At #{char_pos} found #{counts[count_pos]} chars from #{match_start} -- #{earliest_matches}"
+                                        # We have to allow one space as well as moving on
+                                        match_start = char_pos + 2
+                                        count_pos += 1
+                                else
+                                        # We can't have found it because there's not a space! Shuffle along one.
+                                        match_start += 1
+                                end
+                        # Else we're still in the middle of it
+                        end
+                elsif match_start < char_pos
+                        match_start = char_pos + 1
+                end
+                char_pos += 1
+        end
+        earliest_matches
+end
+
+data = File.open(file, "r").each_line(chomp: true).map do |line|
+        symbols, counts_str = line.split(" ")
+        counts = counts_str.split(",").map(&:to_i)
+
+        # There's nothing like Python's strip to strip all matching characters
+        # So use a regex to just get the characters that could be items
+        candidate_symbols = /\.*?([^.].+[^.]).*?/.match(symbols)[1]
+        puts candidate_symbols
+        earliest_matches = find_earliest_positions(candidate_symbols, counts)
+        latest_matches = find_earliest_positions(candidate_symbols.reverse, counts.reverse).reverse.zip(counts).map {|pos, count| candidate_symbols.length - pos - count}
+        puts "#{earliest_matches} to #{latest_matches}"
+        # Assume lines are less than 100 chars and put a fake end in so we reduce to the end
+        earliest_matches << 100
+        latest_matches << 100
+        ranges = earliest_matches.zip(latest_matches).map {|vals| Range.new(*vals)}
+	sum = ranges.zip(counts).reduce([1, [(-1..-1), 0]]) do |accum, next_range_parts|
+		size, cur_range_parts = accum
+		cur_range, cur_count = cur_range_parts
+		next_range, next_count = next_range_parts
+        	
+        	if cur_range.max >= next_range.min
+        		# Overlap - do combinations
+        		remaining_next_range = next_range.max - cur_range.max - cur_count
+        		triangular = ((next_range.size * (next_range.size + 1)) / 2)
+        		puts "#{remaining_next_range} => #{triangular}"
+        		new_range = (cur_range.max + cur_count + 1)..next_range.end
+        		[size * triangular, [new_range, next_count]]
+        	else
+        		puts "#{cur_range} => #{cur_range.size}"
+        		[size * cur_range.size, next_range_parts]
+        	end
+        end
+	puts sum[0]
+        puts
+
+=begin
+        # Aborted attempt trying to use regex to start to find positions for us
+        count_parts = counts.split(",").map {|count| "([#\?]{#{count.to_i}})"}
+        counts_re = /[.?]*#{count_parts.join("[.?]+")}[.?]*/
+        puts symbols
+        match = symbols.match(counts_re)
+        captures = match.captures
+        puts "#{captures}"
+        found = captures.map {|v| v.each_char.all?{|c| c == "#"}}
+        puts "#{found}"
+        puts captures.each.with_index(1).map {|v, i| "#{v} at #{match.offset(i)}"}
+=end
+        RowData.new(symbols, counts)
+end
+puts data
+# Rest of algorithm here
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/day12.rb	Sat Dec 16 20:04:27 2023 +0000
@@ -0,0 +1,83 @@
+#! /usr/bin/env ruby
+
+if ARGV.length != 1
+        abort("Incorrect arguments - needs input file")
+elsif not File.exist? (ARGV[0])
+	abort("File #{ARGV[0]} did not exist")
+end
+
+file = ARGV[0]
+
+def truthy?(val)
+	!val.nil? and val != [] and val != ""
+end
+
+def arrangements(candidate, other_candidates, run_lengths, cache)
+#	puts "Looking for #{run_lengths} (#{truthy?(run_lengths)}) in #{candidate} (#{truthy?(candidate)}), #{other_candidates.nil? '' : other_candidates.join(", ")}"
+	puts "Looking for #{run_lengths} in #{candidate}, #{other_candidates.nil? ? '' : other_candidates.join(",")}"
+	if !truthy?(candidate) and !truthy?(run_lengths)
+		puts "Combos: 1 (both empty)"
+		return 1
+	elsif !truthy?(run_lengths) and !candidate.include? "#" and (!truthy?(other_candidates) or other_candidates.all? {|candidate| truthy?(candidate)})
+		return 1
+	elsif (!truthy?(candidate) and truthy?(run_lengths)) or (!truthy?(run_lengths) and candidate.include? "#")
+		puts "Combos: 0 (fail)"
+		return 0
+	end
+	find_length = run_lengths.first
+
+	cached = cache[[candidate, other_candidates, run_lengths]]
+	if !cached.nil?
+		puts "Combos: #{cached} (cached)"
+		return cached
+	end
+		
+	
+	combo_count = 0
+
+	if find_length == candidate.length
+		# We found a perfect match!
+		new_candidate = other_candidates[0]
+		new_other_candidates = other_candidates[1..]
+		combo_count += arrangements(new_candidate, new_other_candidates, run_lengths[1..], cache)
+		if truthy?(candidate) and !candidate.include? "#"
+			# What if we skip this all all question mark candidate and try later?
+			combo_count += arrangements(new_candidate, new_other_candidates, run_lengths, cache)
+		end
+	elsif find_length < candidate.length
+		if candidate[find_length] != "#"
+			if find_length == candidate.length - 1
+				new_candidate = other_candidates[0]
+				new_other_candidates = other_candidates[1..]
+			else
+				new_candidate = candidate[(find_length+1)..]
+				new_other_candidates = other_candidates
+			end
+			# We found a substring match that ends in a valid place
+			combo_count += arrangements(new_candidate, new_other_candidates, run_lengths[1..], cache)
+		end
+		if candidate[0] == "?"
+			# We've got space, so try the other combo
+			combo_count += arrangements(candidate[1..], other_candidates, run_lengths, cache)
+		end
+	else
+		# This candidate must all be good parts
+		combo_count += arrangements(other_candidates[0], other_candidates[1..], run_lengths, cache)
+	end
+	puts "Combos: #{combo_count}\n"
+	cache[[candidate, other_candidates, run_lengths]] = combo_count
+	combo_count
+end
+
+cache = Hash.new
+
+combos = File.open(file, "r").each_line(chomp: true).map do |line|
+        symbols, run_lengths_str = line.split(" ")
+        run_lengths = run_lengths_str.split(",").map(&:to_i)
+        candidates = symbols.split(".").filter {|candidate| candidate != ""}
+	candidate = candidates.shift
+	line_arrangements = arrangements(candidate, candidates, run_lengths, cache)
+	puts "Line arrangements: #{line_arrangements}\n\n"
+	line_arrangements
+end
+puts "#{combos} = #{combos.sum}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/day12.txt	Sat Dec 16 20:04:27 2023 +0000
@@ -0,0 +1,54 @@
+For each row, the condition records show whether each item is operational (.) or damaged (#). This is the part of the condition records that is itself damaged; for some items, it is simply unknown (?) whether the item is operational or damaged.
+
+However, after each row, the size of each contiguous group of damaged items is listed in the order those groups appear in the row. This list always accounts for every damaged item, and each number is the entire size of its contiguous group (that is, groups are always separated by at least one operational item: #### would always be 4, never 2,2).
+
+So, condition records with no unknown item conditions might look like this:
+
+#.#.### 1,1,3
+.#...#....###. 1,1,3
+.#.###.#.###### 1,3,1,6
+####.#...#... 4,1,1
+#....######..#####. 1,6,5
+.###.##....# 3,2,1
+
+However, the condition records are partially damaged; some of the items' conditions are actually unknown (?). For example:
+
+???.### 1,1,3
+.??..??...?##. 1,1,3
+?#?#?#?#?#?#?#? 1,3,1,6
+????.#...#... 4,1,1
+????.######..#####. 1,6,5
+?###???????? 3,2,1
+
+Equipped with this information, it is your job to figure out how many different arrangements of operational and broken items fit the given criteria in each row.
+
+In the first line (???.### 1,1,3), there is exactly one way separate groups of one, one, and three broken items (in that order) can appear in that row: the first three unknown items must be broken, then operational, then broken (#.#), making the whole row #.#.###.
+
+The second line is more interesting: .??..??...?##. 1,1,3 could be a total of four different arrangements. The last ? must always be broken (to satisfy the final contiguous group of three broken items), and each ?? must hide exactly one of the two broken items. (Neither ?? could be both broken items or they would form a single contiguous group of two; if that were true, the numbers afterward would have been 2,3 instead.) Since each ?? can either be #. or .#, there are four possible arrangements of items.
+
+The last line is actually consistent with ten different arrangements! Because the first number is 3, the first and second ? must both be . (if either were #, the first number would have to be 4 or higher). However, the remaining run of unknown item conditions have many different ways they could hold groups of two and one broken items:
+
+?###???????? 3,2,1
+.###.##.#...
+.###.##..#..
+.###.##...#.
+.###.##....#
+.###..##.#..
+.###..##..#.
+.###..##...#
+.###...##.#.
+.###...##..#
+.###....##.#
+
+In this example, the number of possible arrangements for each row is:
+
+    ???.### 1,1,3 - 1 arrangement
+    .??..??...?##. 1,1,3 - 4 arrangements
+    ?#?#?#?#?#?#?#? 1,3,1,6 - 1 arrangement
+    ????.#...#... 4,1,1 - 1 arrangement
+    ????.######..#####. 1,6,5 - 4 arrangements
+    ?###???????? 3,2,1 - 10 arrangements
+
+Adding all of the possible arrangement counts together produces a total of 21 arrangements.
+
+For each row, count all of the different arrangements of operational and broken items that meet the given criteria. What is the sum of those counts?
\ No newline at end of file