Uncategorized

Google Code Jam: Text Messaging Outrage

Google Code Jam Round 1C began with the "Text Messaging Outrage" problem which basically asks that we identify the most efficient letter-to-key assignment on a phone to minimize the number of key presses needed by users. Google quite helpfully provide a frequency distribution for each letter. Given the frequency distribution it's obvious what the optimal assignment is - for the letters that are most frequently used assign them key press combinations that require the fewest key presses. Technically speaking this is a greedy approach and it should be intuitively obvious why it produces the optimal assignment.

This was one of the easiest problems encountered so far! Here's the code in C#:


using System;
using System.IO;

namespace GoogleCodeJam.TextMessage
{
    class Program
    {
        static void Main()
        {
            string basePath = "";
            StreamReader infile = new System.IO.StreamReader(basePath + "large.txt");
            StreamWriter outfile = new System.IO.StreamWriter(basePath + "output.txt");

            int testCases = Int32.Parse(infile.ReadLine());
            for (int caseNo = 1; caseNo <= testCases; caseNo++) // note this is 1-based
            {
                long minKeyPressCount = 0;

                // read in the input data
                string[] data = (infile.ReadLine()).Split(new char[] { ' ' }, StringSplitOptions.None);
                int P = Int32.Parse(data[0]); // max number of letters to place on a key
                int K = Int32.Parse(data[1]); // number of keys available
                int L = Int32.Parse(data[2]); // number of letters in the alphabet

                // read in the letter frequencies
                int[] frequencies = new int[L];
                int[] indexes = new int[L];

                string[] freqData = (infile.ReadLine()).Split(new char[] { ' ' }, StringSplitOptions.None);
                int i = 0;
                foreach (string s in freqData)
                {
                    indexes[i] = i;
                    frequencies[i] = Int32.Parse(s);
                    i++;
                }

                int nextKfree = 0;      // the one-based index of the key with the next lowest key press combo
                int nextPfree = 1;      // the one-based index of the letter on the "low key" with the next lowest key press combo

                // map the most used letters to the shortest key press routines available
                Array.Sort(frequencies,indexes);    // ~ O(n log n) on average since it uses an unstable QuickSort internally
                Array.Reverse(frequencies);         // ~ O(n)
                Array.Reverse(indexes);             // ~ O(n)
                int ii = 0;
                foreach (int x in indexes)  // from greatest- to least-used index
                {
                    // find the next available key press combo walking through from cheapest to dearest
                    nextKfree++;
                    if (nextKfree > K)
                    {
                        nextKfree = 1;
                        nextPfree++;
                    }
                    checked
                    {
                        minKeyPressCount += (frequencies[ii] * nextPfree);
                    }
                    ii++;
                }

                // write out the results
                outfile.WriteLine(String.Format("Case #{0}: {1}", caseNo, minKeyPressCount));
            }
            infile.Close();
            outfile.Flush();
            outfile.Close();
        }
    }
}

Programming, Uncategorized

Google Code Jam: Milkshakes

Google Code JamFollowing on from the Minimum Scalar Product problem in Round 1A, I next attempted the Milkshakes problem. This problem initially had me perplexed. You could solve the problem using brute force by generating a list of all possible permutations of the N flavours (malted or unmalted) and then assessing if any of these candidate solutions from the solution space was feasible, and from all the feasible solutions pick the one that had the least number of malted batches made. That would require evaluating 2N candidate solutions which is NOT computationally practical given the range for N (1 < N < 2000).

I must admit I did check through a few reference books to get ideas on this one. From my studies in Operations Research I had a suspicion that this was similar to a boolean satisfiability problem and found reference to it in  this book on page 38 and 48. That told me that Cooke's Theorem had proved this to be NP-Complete. I know Google gives out challenging problems but I very much doubt they'd be evil enough to give out an NP-Complete problem to solve! Hence I kept looking for a solution. The problem was subtlety different from that problem in that each customer could have at most 1 malted flavour preference. That's the opening we need...

Intuition should tell us that we can improve drastically on the brute-force approach by adopting a greedy-like algorithm whereby we start with the best case scenario - no malted flavour batches made - and assess if this candidate solution is feasible (in that it satisfies all of the customers flavour preferences). If it is feasible then we know it will be optimal. If it is not feasible we need to amend our candidate solution such that it is closer to being feasible, and then start the process over.

In summary, our approach to solving the milkshakes problem is as follows:

  1. Set first candidate solution to be all flavours made as unmalted
  2. Check if current candidate solution is feasible. If it is then exit. The current solution will be optimal.
  3. If current candidate solution is not feasible, and the unsatisfied customer has a malted flavour preference then change the candidate solution to make that flavour as malted rather than unmalted, and return to Step 2. NB: it should be noted that the customer who caused us to amend our candidate solution is now satisfied and can be removed from the list of customers to check on subsequent passes.
  4. If current candidate solution is not feasible, and the unsatisfied customer has no malted flavour preference then it means that they had unmalted preferences that we were forced to make as malted to satisfy other customers. This implies that we cannot change our current candidate solution in such a way so as to satisfy the customer and hence no feasible solution exists. In this case exit and mark the problem as "Impossible".

Here's my C# code to do that:

using System;
using System.Collections.Generic;
using System.IO;

namespace GoogleCodeJam.MilkShakes
{
    class Program
    {
        public class Customer
        {
            private List<int> _likesUnmalted;   // a list of the customers preferences
            private int? _likesMalted = null;   // the index of the malted flavour they list

            public Customer(List<int> likesIndices, int? maltedIndex)
            {
                _likesUnmalted = likesIndices;
                _likesMalted = maltedIndex;
            }

            public int? LikesMaltedIndex
            {
                get {return _likesMalted;}

            }

            public bool IsSatisfied(bool[] batchMaltedFlags)
            {
                if (this.LikesMaltedIndex != null && (batchMaltedFlags[(this.LikesMaltedIndex ?? 0) - 1]))
                {
                    return true;
                }

                foreach (int i in _likesUnmalted)
                {
                    if (!batchMaltedFlags[i-1])
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        static void Main()
        {
            StreamReader infile = new System.IO.StreamReader("input.txt");
            StreamWriter outfile = new System.IO.StreamWriter("output.txt");

            int testCases = Int32.Parse(infile.ReadLine());
            for (int caseNo = 1; caseNo <= testCases; caseNo++) // note this is 1-based
            {
                bool IsImpossible = false;

                int flavours = Int32.Parse(infile.ReadLine());
                bool[] isBatchMalted = new bool[flavours];

                int custCount = Int32.Parse(infile.ReadLine());
                List<Customer> customers = new List<Customer>();
                for (int i = 0; i < custCount; i++ )
                {
                    string[] data=(infile.ReadLine()).Split(new char[] { ' ' }, StringSplitOptions.None);
                    int flavoursLiked = Int32.Parse(data[0]); // number of flavours liked

                    List<int> likes = new List<int>();
                    int? likesMaltedIndex = null;

                    for (int j = 0; j < flavoursLiked; j++)
                    {
                        if (Int32.Parse(data[j * 2 + 2]) == 1)
                        {
                            likesMaltedIndex = Int32.Parse(data[j * 2 + 1]);
                        }
                        else
                        {
                            likes.Add(Int32.Parse(data[j * 2 + 1]));
                        }
                    }
                    customers.Add(new Customer(likes,likesMaltedIndex));
                }

                // for initial solution assume we only make unmalted flavours
                // and check for feasibility
                for (int i = 0; i < isBatchMalted.Length; i++ )
                {
                    isBatchMalted[i] = false;
                }

                bool madeMalted;

                do
                {
                    madeMalted = false;
                    foreach (Customer c in customers.ToArray())
                    {
                        if (!c.IsSatisfied(isBatchMalted))   // this customer isn't happy
                        {
                            if (c.LikesMaltedIndex == null)
                            {
                                // they must be unsatisfied because none of their prefered
                                // MALTED flavours have been made
                                IsImpossible = true;
                                break;
                            }
                            else
                            {
                                // they have a malted-preference so we MUST make that flavour
                                // as a malt rather than an unmalted 
                                madeMalted=madeMalted || !isBatchMalted[(c.LikesMaltedIndex ?? 0) - 1];

                                isBatchMalted[(c.LikesMaltedIndex ?? 0) - 1] = true;

                                // since this customer is now satisfied we can remove them
                                customers.Remove(c);
                            }
                        }
                    }
                } while (madeMalted);

                // write out the results
                if (IsImpossible)
                {
                    outfile.WriteLine(String.Format("Case #{0}: IMPOSSIBLE", caseNo));
                    //outfile.WriteLine();
                }
                else
                {
                    outfile.Write(String.Format("Case #{0}:", caseNo));
                    foreach (bool b in isBatchMalted)
                    {
                        outfile.Write(" " + (b ? 1 : 0));
                    }
                    outfile.WriteLine();
                }
            }

            infile.Close();
            outfile.Flush();
            outfile.Close();
        }
    }
}

Uncategorized

Google Code Jam: Minimum Scalar Product

Google Code JamRound 1A of Google Code Jam 2008 has a problem called Minimum Scalar Product which, as the name suggests, asks that you find the minimum scalar product of 2 equally-sized vectors. When I read this problem my thoughts immediately went to the golf course. Why? Because if you're a member of a golf club you'll invariable come upon a competition format called 4-Ball Stableford multipler. Simply put, 2 players play as a team and each get a stableford (points) score for each hole, but the team score for each hole is recorded as the product of each players score on that hole with the team with the most point winning. i.e if PlayerA gets 4 points and PlayerB gets 2 points on a given hole, the team score for that hole is 8 points (= 4 x 2). Why am I telling you this? Well the secret to getting the best team score in a 4-Ball Stableford multiplier is to co-ordinate your good holes and your poor holes with your partners' good and bad scoring holes. In order words, to maximise the team score, each player should not only play well but play well on the same holes that their team-mate plays well on. The Google challenge posed here is effectively the same problem with the difference that they ask for the minimum product, rather than the maximum product that you seek on the golf course in the competition format described above.

Once you figure this out it's a simple problem to code. Get 2 vectors/lists and order one in ascending order, and order the other in descending order. There's no great need to procrastinate over which sort algorithm to use, and whether or not to replace the one provided by the .Net framework with your own - you'll be able to solve both the small and large problem with the code shown below:

using System;
using System.IO;
using System.Collections.Generic;

namespace GoogleCodeJam.ScalarProduct
{
    class Program
    {
        static void Main()
        {
            StreamReader infile = new System.IO.StreamReader(basePath + "large.txt");
            StreamWriter outfile = new System.IO.StreamWriter(basePath + "output.txt");

            int testCases = Int32.Parse(infile.ReadLine());
            for (int caseNo = 1; caseNo <= testCases; caseNo++) // note this is 1-based
            {
                // read in the input data
                int vectorSize = Int32.Parse(infile.ReadLine());

                List<long> vector1 = new List<long>();
                List<long> vector2 = new List<long>();

                string[] data = (infile.ReadLine()).Split(new char[] { ' ' }, StringSplitOptions.None);
                foreach (string s in data) { vector1.Add(Int64.Parse(s)); }

                data = (infile.ReadLine()).Split(new char[] { ' ' }, StringSplitOptions.None);
                foreach (string s in data) { vector2.Add(Int64.Parse(s)); }

                vector1.Sort();
                vector2.Sort();
                vector2.Reverse();

                long minProduct=0;
                for(int i=0; i< vector1.Count; i++ )
                {
                    minProduct += vector1[i] * vector2[i];
                }

                // write out the results
                outfile.WriteLine(String.Format("Case #{0}: {1}", caseNo, minProduct));
            }

            infile.Close();
            outfile.Flush();
            outfile.Close();
        }
    }
}

« Prev - Next »