Today I solved problem #49 from project euler .net which reads :
The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, is unusual in two ways: (i) each of the three terms are prime, and, (ii) each of the 4-digit numbers are permutations of one another.
There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, exhibiting this property, but there is one other 4-digit increasing sequence.
What 12-digit number do you form by concatenating the three terms in this sequence?
We can easily shorten our range from 999-9999 to 999 - 9999 - 2*3330 because if the first number is bigger than that the third one will no longer be 4-digit number. The code breaks the moment it finds the correct result but still.
The way I solved this problem is by separating my work in 3 steps :
1 . Implementing the Sieve of Eratosthenes :
private static bool[] SetPrimes(int max)
{
bool[] localPrimes = new bool[max + 1];
for (int i = 2; i <= max; i++)
{
localPrimes[i] = true;
}
for (int i = 2; i <= Math.Sqrt(max); i++)
{
if (localPrimes[i])
{
for (int j = i * i; j <= max; j += i)
{
localPrimes[j] = false;
}
}
}
return localPrimes;
}
Which was pretty easy it is something I have done before and I have experience with. I picked sieve over a function which checks if a number is prime because the time it takes for the sieve to calculate all the numbers from 1-9999 is lesser than the time the function will take.
2 . Implementing a permutation generator
private static IEnumerable<int?> GetSubset(int number)
{
int?[] localSubset = new int?[GetFactiorial(number.ToString().Length)];
char[] digits = number.ToString().ToCharArray();
int startingIndex = 0;
int[] indexesToSwap = new int[digits.Length - 1];
for (int index = 0; index < indexesToSwap.Length; index++)
{
indexesToSwap[index ] = index + 1;
}
for (int i = 0; i < localSubset.Length; i += localSubset.Length/digits.Length)
{
char[] tmpSubsetDigits = (char[])digits.Clone();
tmpSubsetDigits[0] = tmpSubsetDigits[startingIndex];
tmpSubsetDigits[startingIndex] = digits[0];
localSubset[i] = int.Parse(tmpSubsetDigits.Aggregate(string.Empty, (current, digit) => current + digit));
int n = 0;
while (n < localSubset.Length/digits.Length)
{
for (int j = 0; j < indexesToSwap.Length - 1; j++)
{
char tmp = tmpSubsetDigits[indexesToSwap[j]];
tmpSubsetDigits[indexesToSwap[j]] = tmpSubsetDigits[indexesToSwap[j + 1]];
tmpSubsetDigits[indexesToSwap[j + 1]] = tmp;
localSubset[i + n + 1] = int.Parse(tmpSubsetDigits.Aggregate(string.Empty, (current, digit) => current + digit));
if (localSubset[localSubset.Length - 1] != null)
{
return localSubset;
}
n++;
}
}
startingIndex++;
digits = number.ToString().ToCharArray();
}
return localSubset;
}
private static int GetFactiorial(int n)
{
int fact = 1;
for (int i = 1; i <= n; i++)
{
fact *= i;
}
return fact;
}
This is my first time implementing my own permutation generator and it works fine, for some reason it wont work for 2 digit numbers.
We know that the permutations of number are equal to the factorial of the number's length we use this to give our array a specified length. Next, since we are going to work with the digits of the number I decided to go with a char array.
Next up we have another int array which holds all the indexes of our number except for the first one, since my code swaps all the other numbers aside from the first one which I manually change, so we fill the array with those values and we go into the actual permutation generator.
We have a for loop which increases by
localSubset.Length/digits.Length
okay let's say we have 123 the permutations starting with the digit 1, are 2 namely - 123 & 132 so if we divide the the total length of the permutations by the length of the number we get exactly how much permutations there are starting with 1. Continuing down in the for loop we are declaring another char array which we use in our operations, just so we don't touch the digits array we Clone it, after we have done that we are swapping the first index of our number with thestartingIndex
variable this variable is increased once each time we have found all the permutations starting with the same digit,let's say we have then number 123 on the first iteration it will take 1 and replace it with 1 becausestartingIndex
value is 0, after the we have added 123 and 132 to our array variable will increase by 1 and it will replace 1 with 2 and so on.Moving on we have a while loop combined with a for loop, the second one usually iterates n/m times lesser than it should where n is the amount of permutations starting with the same digit and m is the amount of indexes to rotate i.e
indexesToSwap
, when we have 123 it will iterate 0 times lesser so it's redundant in this case but if we have 1234 it will iterate 1 time less because theindexesToSwap
are only going to be 3 while we are looking for 6 permutations in total starting with the same digit. Inside we are simply just swapping the indexes of our current subset and after this we add it to our array of permutations, there is some problem there that I couldn't figure out which was causing the program to crash due to IndexOutOfRange exception, if you run the code using breakpoint there you will see that one of the values is duplicate of the first one and it gets overwritten later on i.e 1 extra value which probably caused the bug.
3 . Combine the pieces and make it work !
private static void Main()
{
const int increament = 3330;
const int maxFirstNumber = 9999 - increament*2;
string result = string.Empty;
Stopwatch sw = Stopwatch.StartNew();
bool[] primes = SetPrimes(9999);
for (int i = 999; i <= maxFirstNumber; i++)
{
if (i == 1487)
{
continue;
}
int secondNumber = i + increament;
int thirdNumber = i + increament*2;
if (primes[i] && primes[secondNumber] && primes[thirdNumber])
{
IEnumerable<int?> permuations = GetSubset(i);
IEnumerable<int?> values = permuations as int?[] ?? permuations.ToArray();
if (values.Contains(secondNumber) && values.Contains(thirdNumber))
{
result = i.ToString() + secondNumber + thirdNumber;
sw.Stop();
break;
}
}
}
Console.WriteLine(result);
Console.WriteLine($"Time to calculate in milliseconds : {sw.ElapsedMilliseconds}");
Console.ReadKey();
}