Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //Advent of Code 2020 Day 16 Parts 1 and 2 solution by Mike LeSauvage
- using System.Collections.Generic;
- using UnityEngine;
- using System.Text.RegularExpressions;
- public class TicketAnalyzer : MonoBehaviour
- {
- [SerializeField] TextAsset ticketRulesTextfile = null; //Hooked up in the input text in the Unity editor.
- string[] ticketRulesAndTicketStrings; //Holds all the individual lines from the text file.
- int ticketRuleStringIndex = 0; //Indexes into the ticketRulesAndStrings array as the pointer of the next line to analyze.
- string fieldRuleRegexString = @"^([\w ]+): (\d+)-(\d+) or (\d+)-(\d+)$"; //Grabs field name, range1start, range1end, range2start, range2end
- string ticketRegexString = @"^(?:\d+,){19}\d+$"; //Checks for a string of 20 comma-delimited numbers.
- Regex ticketRgx;
- // TicketRule has the name of a ticket field and the two ranges that are valid for that field.
- // It also contains data and logic for matching the rule to a field on the ticket.
- class TicketRule
- {
- public string name;
- public int range1Start;
- public int range1End;
- public int range2Start;
- public int range2End;
- public List<int> possibleMatchingFields; //All possible ticket fields that match the allowable ranges.
- public int matchingField; //The specific and only actual matching field.
- // Pretty print for debugging.
- public override string ToString()
- {
- return $"Field {name} valid ranges: {range1Start}-{range1End} | {range2Start}-{range2End}";
- }
- // Method to check if a number falls within the allowable ranges for this rule.
- public bool IsNumberInAnyRuleRange(int num)
- {
- if( (num >= range1Start && num <=range1End) || (num >= range2Start && num <= range2End) )
- {
- return true;
- }
- return false;
- }
- }
- List<TicketRule> ticketRules = new List<TicketRule>(); //Holds all ticket rules.
- const int NUM_TICKET_FIELDS = 20;
- int[] myTicket = new int[NUM_TICKET_FIELDS]; //Holds "my" ticket from the puzzle input.
- List<int[]> tickets = new List<int[]>(); //Holds all other provided tickets.
- // Start is a method in Unity; would be Main() in other environments etc
- void Start()
- {
- ticketRgx = new Regex(ticketRegexString); //Regular expression to check if a string is 20 comma-delimited numbers.
- string[] stringSeparator = { "\r\n" }; //Find two new lines for breaks. Windows encoding has both carriage return and line feed.
- ticketRulesAndTicketStrings = ticketRulesTextfile.text.Split(stringSeparator, System.StringSplitOptions.RemoveEmptyEntries);
- //Read in the rules.
- while(ReadRule(ticketRulesAndTicketStrings[ticketRuleStringIndex]))
- {
- ticketRuleStringIndex++;
- }
- //Read in my ticket.
- AdvanceTicketIndexToNextTicket();
- myTicket = ReadTicket(ticketRulesAndTicketStrings[ticketRuleStringIndex]);
- //Read in all the "nearby" tickets.
- ticketRuleStringIndex++;
- AdvanceTicketIndexToNextTicket();
- while(ticketRuleStringIndex < ticketRulesAndTicketStrings.Length)
- {
- tickets.Add(ReadTicket(ticketRulesAndTicketStrings[ticketRuleStringIndex]));
- ticketRuleStringIndex++;
- }
- //Solve Part One
- //--------------
- int errors = RemoveInvalidTickets();
- Debug.Log($"Part 1 - Error Scanning Rate: {errors}. {tickets.Count} tickets remaining.");
- //Add my ticket to those being used to match rules to fields. (Part 1 told us to ignore it!)
- tickets.Add(myTicket);
- //Solve Part Two
- //--------------
- // Start by finding which fields are possible matches for which rules.
- // A test of this found that the puzzle has been built such that:
- // Rule A - 20 matches
- // Rule B - 19 matches
- // Rule C - 18 matches
- // etc... so that once the possible matches are found, the specific matches can be identified by
- // taking the "matches 1 only" match, assigning it, and removing it from all the other possible
- // matches, then repeating for the new "matches 1 only" match.
- //For every rule rule
- // Iterate through each field, one at a time
- // Across every ticket.
- // If all fields pass the rule, then it's a match (that will have to get winnowed down later).
- foreach(TicketRule rule in ticketRules)
- {
- for(int i=0; i<NUM_TICKET_FIELDS; i++)
- {
- bool passedAllTickets = true;
- foreach(int[] t in tickets)
- {
- if(!rule.IsNumberInAnyRuleRange(t[i]))
- {
- passedAllTickets = false;
- break;
- }
- }
- if(passedAllTickets)
- {
- rule.possibleMatchingFields.Add(i);
- }
- }
- }
- //Match rules to fields by working through all the rules.
- // In two loops, swapping the found rule to the front so each rule with one match is only encountered once.
- // Find the rule that matches only one field.
- // Mark the final matching field on the rule for convenenience (it could have stayed just in the list).
- // Remove it from all the other rules
- // Swap it to the front so that it isn't checked again.
- //
- // Repeat until they are all matched.
- for (int i=0; i<ticketRules.Count-1; i++)
- {
- TicketRule curRule = ticketRules[i];
- for (int j = i; j < ticketRules.Count; j++)
- {
- if (ticketRules[j].possibleMatchingFields.Count == 1)
- {
- ticketRules[j].matchingField = ticketRules[j].possibleMatchingFields[0];
- RemovePossibleMatchFromAllOtherRules(ticketRules[j]);
- ticketRules[i] = ticketRules[j];
- ticketRules[j] = curRule;
- break;
- }
- }
- }
- // At this point every rule has a matching field.
- // Loop through all the rules
- // For each rule matched to a field with the word "departure"
- // Get the value from MY ticket
- // Multiply them together.
- long partTwoSolution = 1;
- foreach(TicketRule tr in ticketRules)
- {
- if(tr.name.Contains("departure"))
- {
- partTwoSolution *= myTicket[tr.matchingField];
- }
- }
- Debug.Log($"Part Two Solution: {partTwoSolution}");
- }
- // Looks at a rule's matching field and removes that from the list held by all other rules.
- void RemovePossibleMatchFromAllOtherRules(TicketRule targetRule)
- {
- int count = 0;
- foreach(TicketRule tr in ticketRules)
- {
- if(tr != targetRule)
- {
- if(tr.possibleMatchingFields.Remove(targetRule.matchingField))
- {
- count++;
- }
- }
- }
- }
- //Check all ticket values. Iterate backwards so tickets with errors can be removed.
- int RemoveInvalidTickets()
- {
- int errorScanningRate = 0;
- for (int i = tickets.Count - 1; i >= 0; i--)
- {
- int[] ticket = tickets[i];
- foreach (int num in ticket)
- {
- bool ruleFound = false;
- foreach (TicketRule tr in ticketRules)
- {
- if (tr.IsNumberInAnyRuleRange(num))
- {
- ruleFound = true;
- }
- }
- if (!ruleFound)
- {
- errorScanningRate += num;
- tickets.RemoveAt(i);
- }
- }
- }
- return errorScanningRate;
- }
- // Used for advancing through the text file to find the next ticket.
- // Moves the ticketRuleStringIndex along until it is pointing at a string of
- // twenty comma-separated numbers.
- void AdvanceTicketIndexToNextTicket()
- {
- while (!ticketRgx.IsMatch(ticketRulesAndTicketStrings[ticketRuleStringIndex]))
- {
- ticketRuleStringIndex++;
- }
- }
- // Takes in a single rule in the format of: field name: r1s-r1e or r2s-r2e
- // eg: departure location: 29-766 or 786-950
- // Breaks it apart and stores it into a new TicketRule, which is added to the
- // ticketRules list.
- bool ReadRule(string ticketString)
- {
- MatchCollection allMatches = Regex.Matches(ticketString, fieldRuleRegexString);
- if (allMatches.Count == 0)
- {
- return false;
- }
- GroupCollection groups = allMatches[0].Groups;
- TicketRule rule = new TicketRule();
- rule.name = groups[1].Value;
- rule.range1Start = int.Parse(groups[2].Value);
- rule.range1End = int.Parse(groups[3].Value);
- rule.range2Start = int.Parse(groups[4].Value);
- rule.range2End = int.Parse(groups[5].Value);
- rule.possibleMatchingFields = new List<int>();
- rule.matchingField = -1;
- ticketRules.Add(rule);
- return true;
- }
- // Separates a comma-delimited list of numbers and returns them as an array of integers.
- int[] ReadTicket(string ticket)
- {
- int[] ticketAsNumbers = new int[NUM_TICKET_FIELDS];
- string[] allTicketNumbers = ticket.Split(',');
- for(int i=0; i<NUM_TICKET_FIELDS; i++)
- {
- ticketAsNumbers[i] = int.Parse(allTicketNumbers[i]);
- }
- return ticketAsNumbers;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement