Google Code Jam 2009: All Your Bases
The first puzzle in Round 1C was the All Your Bases puzzle. This puzzle requires you to examine a given string literal and return the lowest possible numeric value that can be made from it. The string is a character pattern that represents a number but we don't know what base it is in. The obvious way to solve this puzzle is to:
-
Count the number of distinct characters in the string. This must be the base to use that produces the lowest possible number. To see this consider a 4 digit number "wxyz". If this is in base b, then the value of wxyz is: w.b3+x.b2+y.b1+z.b0. Since we have 4 distinct digits the base, b >= 4. If we go with b=4 then the value resulting from the above expression is clearly going to be less than the value resulting from any higher value of b. One gotcha: if the count=1 then you need to bump it up to 2 since we are explicitly told that unary (base-1) is not used.
-
For the baseToUse found above, create a sequence of digits from that base in increasing order. Since all bases have a 0, but we are told no numbers in the test data include leading zeros, we must start this sequence with 1, then 0, then 2, 3, 4... up to (baseToUse-1).
-
Starting at the left of the string (the problem doesn't specify if the aliens use left-right or right-left notation but the sample problem implies left-right is used. Given the nature of the problem - finding out the earliest possible start time for the war - this is a rather dangerous assumption and so I pointed it out to Google. See the comment at the bottom) we assign the first unused digit from the list constructed above for each character represented by the char in the position being examined.
-
Since you know the baseToUse, and the digits in each position it is easy to calculate the base10 equivalent of the number created. One gotcha here: the numbers are going to get very large so don't get caught out using floating point functions that silently lose precision when they get too big (that issue has bitten me more often than I care to admit). Use an integer type all the way.
UPDATE: the official analysis from the first round of CodeJam is out and the problem creator, Bartholomew Furrow of Google, acknowledged my observation that perhaps this problem makes a somewhat dangerous assumption. That said, I am envious of his terse solution in Python. Make my C# code look really verbose. Note to self: go learn Python TOMORROW.
The C# code is shown below:
using System; using System.Collections.Generic; using System.IO; namespace GoogleCodeJam.AllYourBases { class Program { static void Main() { string basePath = @"D:\"; var infile = new StreamReader(basePath + "large.txt"); var outfile = new StreamWriter(basePath + "output.txt"); int testCases = Int32.Parse(infile.ReadLine()); for (int caseNo = 1; caseNo <= testCases; caseNo++) { string message = infile.ReadLine().Trim(); ulong minSeconds = UInt64.MaxValue; int startBase = CountDistinct(message); if (startBase == 1) startBase++; minSeconds = message.Length ==1 ? 1 : FindMinimum(startBase, message); outfile.WriteLine(String.Format("Case #{0}: {1}", caseNo, minSeconds)); } infile.Close(); outfile.Close(); } //returns a base10 number static ulong FindMinimum(int baseUsed, string message) { message = message.Trim(); var digits = new List<int> {1, 0}; for (int d = 2; d < baseUsed; d++) digits.Add(d); ulong ans = 0; foreach (var c in GetDistinctInOrder(message)) { int index = message.IndexOf(c); while (index >=0) { ans += (ulong)digits[0] * Power( baseUsed, message.Length - (index + 1)); index = message.IndexOf(c,index+1); } digits.RemoveAt(0); } return ans; } static ulong Power(int baseUsed, int exponent) { ulong answer = 1; for(int i=0; i<exponent; i++) answer *= (ulong)baseUsed; return answer; } static List<char> GetDistinctInOrder(string input) { input = input.Trim(); var seen = new List<char>(); for (int i = 0; i < input.Length; i++) { if (!seen.Contains(input[i])) seen.Add(input[i]); } return seen; } static int CountDistinct(string input) { return GetDistinctInOrder(input).Count; } } }
And here's my unpolished C++ code to do the same:
#include <string> #include <iostream> using namespace std; char seen[256]; char seenOrder[256]; long long Power(int baseUsed, int exponent) { long long answer = 1; for(int i=0; i<exponent; i++) answer *= baseUsed; return answer; } //returns a base10 number long long findMinimum(int baseUsed, string* msg) { int digits[36]; memset(digits,0,sizeof(digits)); digits[0]=1; digits[1]=0; for (int d = 2; d < baseUsed; d++) digits[d]=d; long long ans = 0; for(int i=0; i < baseUsed; i++) { char c = seenOrder[i]; int index = msg->find(c); while (index != string::npos) { ans += digits[i] * Power( baseUsed, msg->length() - (index + 1)); index = msg->find(c,index+1); } } return ans; } void main() { string basePath = ""; string inFile = basePath + "large.txt"; string outFile = basePath + "output.txt"; freopen(inFile.c_str() , "r", stdin); freopen(outFile.c_str(), "w", stdout); int testCases; cin >> testCases; for (int caseNo = 1; caseNo <= testCases; caseNo++) { long long minSeconds = 0; string message; cin >> message; memset(seen,0,sizeof(seen)); memset(seenOrder,0,sizeof(seenOrder)); int startBase=0; for (int i = 0; i < message.length(); i++) { char c = message[i]; if (seen[c] == 0) //not seen before { seen[c] = 1; seenOrder[startBase] = c; startBase++; } } if (startBase == 1) startBase++; minSeconds = (message.length() == 1) ? 1 : findMinimum(startBase, &message); printf("Case #%d: %lld\n", caseNo, minSeconds); } }
13 Sep 2009 Damien Wintour







