Logic Grid using Backtracking

My daughter had an assignment to solve a logic grid - recycling days. The puzzle is in the image below.


She needs to finish it the old-fashioned-raw-logic way (which she is doing), but I decided to take a brute-force approach. This is a typical backtracking problem. The approach should be as follows:

1/ Figure out how to model the problem. I decided to model yet using an array of "days" where each day will have a truck, a material and a time

2/ Start by creating the validation function. This is the boring and detailed part that you can't mess up. Just a matter of reading the problem definition and implementing each one of the rules

3/ Then comes the backtracking. DFS using hash tables to keep track of which elements have already been used

Let it run for ~1sec. It outputs the result. I have not shared the results with my daughter... Result and code are below, cheers, ACC.


using System;
using System.Collections;

namespace RecyclingDays
{
    public class Days
    {
        public string truckColor = "";
        public string material = "";
        public int time = 0;

        public Days(string truckColor, string material, int time)
        {
            this.truckColor = truckColor;
            this.material = material;
            this.time = time;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Days[] days = new Days[4];
            for (int i = 0; i < days.Length; i++) days[i] = new Days("", "", 0);

            int combinationNumber = 0;
            if (!TryAllCombinations(0, days, new Hashtable(), new Hashtable(), new Hashtable(), ref combinationNumber))
            {
                Console.WriteLine("Impossible!!!");
            }
        }

        static void PrintDays(Days[] days, int combinationNumber)
        {
            string[] weekDays = { "Tue", "Wed", "Thur", "Fri" };
            Console.Write("Combination #{0} (Day, Color, Material, Time): ", combinationNumber);
            for (int i = 0; i < days.Length; i++)
            {
                    Console.Write("({0},{1},{2},{3}) ", weekDays[i], days[i].truckColor, days[i].material, days[i].time);
            }
            Console.WriteLine();
        }

        static bool TryAllCombinations(int dayIndex,
                                       Days[] days,
                                       Hashtable usedTruckColor,
                                       Hashtable usedMaterial,
                                       Hashtable usedTime,
                                       ref int combinationNumber)
        {
            if (dayIndex >= days.Length)
            {
                combinationNumber++;
                int result = Valid(days);
                if (result == 0)
                {
                    Console.WriteLine("Found it!");
                    PrintDays(days, combinationNumber);
                    return true;
                }
                else
                {
                    Console.WriteLine("Combination violates rule #{0}", result);
                    PrintDays(days, combinationNumber);
                    return false;
                }
            }

            string[] truckColors = { "b", "g", "o", "y" };
            string[] material = { "a", "b", "g", "p" };

            foreach (string c in truckColors)
            {
                if (!usedTruckColor.ContainsKey(c))
                {
                    usedTruckColor.Add(c, true);
                    foreach (string m in material)
                    {
                        if (!usedMaterial.ContainsKey(m))
                        {
                            usedMaterial.Add(m, true);
                            for (int t = 5; t <= 8; t++)
                            {
                                if (!usedTime.ContainsKey(t))
                                {
                                    usedTime.Add(t, true);
                                    days[dayIndex].truckColor = c;
                                    days[dayIndex].material = m;
                                    days[dayIndex].time = t;
                                    if (TryAllCombinations(dayIndex + 1, days, usedTruckColor, usedMaterial, usedTime, ref combinationNumber))
                                    {
                                        return true;
                                    }
                                    usedTime.Remove(t);
                                }
                            }
                            usedMaterial.Remove(m);
                        }
                    }
                    usedTruckColor.Remove(c);
                }
            }

            return false;
        }

        static int Valid(Days[] days)
        {
            for (int i = 0; i < days.Length; i++)
            {
                //#1: truck arriving at 5 must be orange
                if (days[i].time == 5 && days[i].truckColor != "o") return 1;

                //#2 the glass' truck arrives the day after the aluminum's truck
                if (days[i].material == "g" && (i == 0 || days[i - 1].material != "a")) return 2;

                //#3: the glass truck arrives either on Thursday or at 7
                if (days[i].material == "p" && i != 2 && days[i].time != 7) return 3;

                //#4: the blue truck arries one hour after the yellow one
                if (days[i].truckColor == "b")
                {
                    bool yellowCondition = false;
                    for (int j = 0; j < days.Length; j++)
                    {
                        if (i != j)
                        {
                            if (days[j].truckColor == "y" && days[j].time == days[i].time - 1)
                            {
                                yellowCondition = true;
                                break;
                            }
                        }
                    }
                    if (!yellowCondition) return 4;
                }

                //#5: the aluminum is collected at 6 or 7
                if (days[i].material == "a" && days[i].time != 6 && days[i].time != 7) return 5;

                //#6.1: friday's truck arrives at 7 or 8
                if (i == 3 && days[i].time != 7 && days[i].time != 8) return 61;

                //#6.2: paper is not collected at 8
                if (days[i].material == "p" && days[i].time == 8) return 62;

                //#7: batteries are collected the day before the green truck arrives
                if (days[i].material == "b" && (i + 1 >= days.Length || days[i + 1].truckColor != "g")) return 7;

                //#8.1: thursday's truck does not arrive at 8
                if (i == 2 && days[i].time == 8) return 81;

                //#8.2: tuesday's truck isn't blue
                if (i == 0 && days[i].truckColor == "b") return 82;

                //#9: wednesday's truck is for aluminum or is orange
                if (i == 1 && days[i].material != "a" && days[i].truckColor != "o") return 9;
            }

            return 0;
        }
    }
}

Comments

Popular posts from this blog

Advent of Code - Day 6, 2024: BFS and FSM

Golang vs. C#: performance characteristics (simple case study)

Advent of Code - Day 7, 2024: Backtracking and Eval