## Project Euler Problem #60

### November 20, 2016

Read the details of the problem here

**Summary**

Prime pair sets.

**Solution**

This problem took a little bit of thinking about, primarily to get it to perform adequately – on the basis that I’m trying to get this to run in under 5 seconds. When I first solved this problem I simply couldn’t get the computational performance out of Groovy to do the prime validation quickly enough and ended up calling out to a native Java method. With the introduction of the @CompileStatic AST transformation this has made it possible to do. After my next laptop upgrade, I’m going to be having to aim for maybe 2 seconds at the most on these questions!

The solution uses a fairly naive implementation of an *isPrime()* method which is still substantially faster than using *BigInteger#isProbablePrime()* from Groovy when using @CompileStatic – at least for the range of numbers being used here. I made an assumption that 4-digit primes would be sufficient to solve this question and, luckily, it worked out to be correct.

The program starts by constructing a list of primes mapped to a list of other primes that conform to the question’s constraints of both orders of concatenation also being prime. Using lists of strings rather than integers saves a lot of overhead in the subsequent checks. The lists only contain primes that are greater than the key. So, for example, 3’s list contains 7 (as both 37 and 73 are prime), but that of 7 doesn’t contain 3. Any numbers that have fewer than 4 entries in their list are dropped as a set of 5 primes are being sought. The lowest number in that set will have the full membership in it’s list.

Having generated this list of prime-pairs, a set of primes are grown to find a five members set that fulfil the criteria. At each recursion the entries of the list of the most recently added member are checked to make sure that they meet the concatenation criteria of each of the other member of the list (by making sure that it’s on their respective lists of potential pairs) and that it wouldn’t exceed the current minimum value found. For each of the values that fulfil this criteria, a recursion is called with this appended to the current list. The minimum value found for that particular path is returned. In this respect it’s like creating a directed graph with the nodes (of which there are only 1,227) being the primes and the edges (of which there are 17,845) being the fact that two nodes fulfil the concatenation criteria.

There are probably some algorithmic optimisations that could be made here but, as it is, the code is quite simple to understand.

import groovy.transform.CompileStatic @CompileStatic boolean isPrime(final int num) { if (num <= 1) return false if (num <= 3) return true if ((num % 2 == 0) || (num % 3 == 0)) return false int r = (int)Math.sqrt(num) int f = 5 while (f <= r) { if (num % f == 0) return false if (num % (f + 2) == 0) return false f += 6 } true } def getPrimePairs(maxnum) { def primes = ((2..maxnum).findAll { isPrime(it) })*.toString() def primeCount = primes.size() def pairs = [:] for (ip1 in 0..<primeCount - 1) { def sp1 = primes[ip1] pairs[sp1] = [] as Set for (ip2 in (ip1 + 1)..<primeCount) { def sp2 = primes[ip2] if (isPrime((sp1 + sp2).toInteger()) && isPrime((sp2 + sp1).toInteger())) { pairs[sp1] << sp2 } } if (pairs[sp1].size() < 4) pairs.remove(sp1) } pairs } def find5(list, min, pairs) { def sum = list*.toInteger().sum() if (list.size() == 5) return sum pairs[list[-1]].findAll { p -> ((sum + p.toInteger()) < min) && (list.every { pairs[it].contains(p) }) } .each { min = [ min, find5(list + it, min, pairs) ].min() } min } def (answer, primePairs) = [ Integer.MAX_VALUE, getPrimePairs(10000) ] for (p in primePairs.keySet()) { answer = [ answer, find5([p], answer, primePairs) ].min() }

This executes in Groovy 2.4.7 under Java 8u112 in around 2.9 seconds, so within my 5 second cut-off. The build of the list of primes takes about 115 ms, with the construction of the prime pair map taking another 1.5 seconds. The isPrime() method is called around 850,000 times and takes around 960 ms of the total runtime.

**Conclusion**

Quite surprisingly, Groovy was perfectly good for this problem as long as @CompileStatic is used to optimise the isPrime() method. Left as dynamic, the runtime pushes out to just under 30 seconds. The Groovy listops made it easy to construct the prime-pair lists and for quite an expressive statement for the recursive descent conditions.