PROBLEM: In the first generation of Pokemon, there are 164 different attacks. Each Pokemon can learn a number of attacks. As a Pokemon trainer, you want to be the team of 6 Pokemon whose union of attacks they can learn is maximum.

The problem

Located here: http://primers.xyz/3. The website is in french, here is a quick translation :

Welcome to Primers, a site dedicated to algorithmic problems whose optimal solution is not achievable in a reasonable time. We must therefore design algorithms to find approximate solutions. Your goal: to do better than your competitors! :)

PROBLEM: In the first generation of Pokemon, there are 164 different attacks. Each Pokemon can learn a number of attacks. As a Pokemon trainer, you want to be the team of 6 Pokemon whose union of attacks they can learn is maximum.

So it is a bit like the old Google Code Jam: you execute your code locally and give back to the website your best solution, then the website gives you a ranking.

Regarding the Pokemon problem, my first approach was to do random hill climbing: start with an initial solution (=a team of 6 pokemon), and randomly replace one of the Pokemon with an other random one. If this yield a better score, continue to iterate with this new team. With a small Python script, I achieved a score of 104.

Returning on the website, I could see in the ranking table that the best score was 105. Some comments were hinting that an optimal solution could be found for this problem.

For sure, an optimal solution would be to enumerate all the teams that can be created with 6 Pokemons. This is called brute-forcing the problem, could it be done here ?

How many combinations of 6 pokemons out of 150 are they ?

If the set has n elements, the number of k-combinations is equal to the binomial coefficient :

\[\binom{n}{k} = \frac{n(n-1)...(n-k+1)}{k(k-1)...1}\]
>>> import scipy.misc
>>> scipy.misc.comb(150, 6, exact=True)
14297000725

So there is about 14 billions of combinations to test.If you can test 1 billion of combinations per seconds, it will take 14 seconds… Today processors are running north of 1 GHz, so it is something reachable.

To achieve that performance, Python was not possible, therefore I wrote a solution in C. Of course you better turn the optimisation on when doing that (-O3 for example).

Some colleague of mine was doubtful that it was possible to test a combination on a single CPU cycle.

Of course, you need more, but if coded well the inner loop (that test one combination) can take in the order of 10 instructions.

If only they had put a more difficult problem, for example teams of 8+ pokemons or a set of 250 different pokemons, then it would have been almost impossible to brute-force the problem … ​

The solution

See pokemon.c in my Github repository.

Below is the assembly produced with gcc-8 -O3 -march=native. GCC does a very good job here, the inner loop is straightforward : 3 OR + 3 POPCNT per test, a good x86-64 processor will be crushing through all the combinations in a few seconds …

L7:
	movq	(%rdx), %rax
	movq	8(%rdx), %rsi
	movq	16(%rdx), %rcx
	orq	%r10, %rax
	orq	%r9, %rsi
	popcnt	%rax, %rax
	popcnt	%rsi, %rsi
	orq	%r8, %rcx
	addl	%esi, %eax
	popcnt	%rcx, %rcx
	addl	%ecx, %eax
	cmpl	%ebp, %eax
	jle	L6
	movb	7(%rsp), %cl
	movb	6(%rsp), %sil
	movb	$1, 4(%rsp)
	movl	%eax, %ebp
	movb	5(%rsp), %r15b
	movb	1(%rsp), %r13b
	movb	%bl, %r14b
	movb	%dil, %r12b
	movb	%cl, 3(%rsp)
	movb	%sil, 2(%rsp)
L6:
	incl	%edi
	addq	$24, %rdx
	cmpl	%r11d, %edi
	jne	L7