69 lines
		
	
	
		
			1.4 KiB
		
	
	
	
		
			Crystal
		
	
	
	
	
	
		
		
			
		
	
	
			69 lines
		
	
	
		
			1.4 KiB
		
	
	
	
		
			Crystal
		
	
	
	
	
	
|  | require "advent" | ||
|  | 
 | ||
|  | rlines, _, strings = input(2020, 19).partition("\n\n") | ||
|  | strings = strings.lines | ||
|  | rules = {} of String => String | ||
|  | rlines.lines.map do |l| | ||
|  |   rule, _, text = l.partition(": ") | ||
|  |   rules[rule] = text | ||
|  | end | ||
|  | 
 | ||
|  | alias Matcher = Proc(String,Int32,Array(Int32)) | ||
|  | 
 | ||
|  | def char(c) | ||
|  |   ->(str : String, i : Int32) { str[i]? == c ? [i+1] : [] of Int32 } | ||
|  | end | ||
|  | 
 | ||
|  | def any(ps) | ||
|  |   ->(str : String, i : Int32) { ps.flat_map { |p| p.call(str, i) } } | ||
|  | end | ||
|  | 
 | ||
|  | def seq(ps) | ||
|  |   ->(str : String, i : Int32) { | ||
|  |     base = [i] | ||
|  |     ps.each do |p| | ||
|  |       base = base.flat_map { |i| p.call(str, i) } | ||
|  |     end | ||
|  |     base | ||
|  |   } | ||
|  | end | ||
|  | 
 | ||
|  | def to_regex(rules, rule) | ||
|  | end | ||
|  | 
 | ||
|  | def build_rule(rules, built, rule) : Matcher | ||
|  |   if exists = built[rule]? | ||
|  |     return exists  | ||
|  |   end | ||
|  | 
 | ||
|  |   body = rules[rule] | ||
|  |   return built[rule] = char body[1] if body.matches? /"."/ | ||
|  |    | ||
|  |   branches = [] of Matcher | ||
|  |   top = any(branches) | ||
|  |   built[rule] = top | ||
|  |   branches.concat(body.split(" | ").map { |b| seq(b.split(" ").map { |subrule| build_rule(rules, built, subrule) }) }) | ||
|  |   top | ||
|  | end | ||
|  | 
 | ||
|  | def part1(input) | ||
|  |   rules, lines = input | ||
|  |   matcher = build_rule(rules, {} of String => Matcher, "0") | ||
|  |   lines.count do |l| | ||
|  |     matcher.call(l, 0).includes? l.size | ||
|  |   end | ||
|  | end | ||
|  | 
 | ||
|  | def part2(input) | ||
|  |   rules, lines = input | ||
|  |   rules["8"] = "42 | 42 8" | ||
|  |   rules["11"] = "42 31 | 42 11 31" | ||
|  |   matcher = build_rule(rules, {} of String => Matcher, "0") | ||
|  |   lines.count do |l| | ||
|  |     matcher.call(l, 0).includes? l.size | ||
|  |   end | ||
|  | end | ||
|  | 
 | ||
|  | puts part1({rules,strings}) | ||
|  | puts part2({rules,strings}) |