Project Euler Problem 41

Pandigital prime.

We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once. For example, 2143 is a 4-digit pandigital and is also prime.

What is the largest n-digit pandigital prime that exists?

Link to original description
Source code examples on Github

First of all we have to try simplify problem. We have to find pandigital primes from following digits permutations:

                            [1,2]
                            [1,2,3]
                            [1,2,3,4]
                            [1,2,3,4,5]
                            [1,2,3,4,5,6]
                            [1,2,3,4,5,6,7]
                            [1,2,3,4,5,6,7,8]
                            [1,2,3,4,5,6,7,8,9]

Divisible by 3 rule. So rule is: “number is divisible by 3 if and only if the digit sum of the number is divisible by 3”. And we can figure out pandigital primes can be only with [1,2,3,4] and [1,2,3,4,5,6,7] permutations.

First of all will try to find answer from 7 digits primes:

Erlang version 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname p41
% vim:syn=erlang

main(_) -> 
    L = lists:reverse(prime(7654321)),
    io:format("Answer: ~p ~n", [t41(L)]).

t41([H|T]) ->
    case isPandigital(integer_to_list(H)) of
        true -> H
        ;_   -> t41(T)
    end.

isPandigital(N) ->
    case {length(N), lists:usort(N)} of
        {7, "1234567"} -> true
        ;_             -> false
    end.


%------------------------------------- prime library from problem 10------------
is_prime(X) ->
    case ets:lookup(prim, X) of
        [] -> false
        ;_ -> true
    end.

prime(N) ->    
    ets:new(comp, [public, named_table, {write_concurrency, true} ]),
    ets:new(prim, [public, named_table, {write_concurrency, true}]),
    composite_mc(N),
    primes_mc(N),
    lists:sort([X||{X,_}<- ets:tab2list(prim)]).

primes_mc(N) ->
    case erlang:system_info(schedulers) of
        1 -> primes(N);
        C -> launch_primes(lists:seq(1,C), C, N, N div C)
    end.
launch_primes([1|T], C, N, R) -> P = self(), spawn(fun()-> primes(2,R), P ! {ok, prm} end), launch_primes(T, C, N, R);
launch_primes([H|[]], C, N, R)-> P = self(), spawn(fun()-> primes(R*(H-1)+1,N), P ! {ok, prm} end), wait_primes(C);
launch_primes([H|T], C, N, R) -> P = self(), spawn(fun()-> primes(R*(H-1)+1,R*H), P ! {ok, prm} end), launch_primes(T, C, N, R).

wait_primes(0) -> ok;
wait_primes(C) ->
    receive
        {ok, prm} -> wait_primes(C-1)
    after 1000    -> wait_primes(C)
    end.

primes(N) -> primes(2, N).
primes(I,N) when I =< N ->
    case ets:lookup(comp, I) of
        [] -> ets:insert(prim, {I,1})
        ;_ -> ok
    end,
    primes(I+1, N);
primes(I,N) when I > N -> ok.


composite_mc(N) -> composite_mc(N,2,round(math:sqrt(N)),erlang:system_info(schedulers)).
composite_mc(N,I,M,C) when I =< M, C > 0 ->
    C1 = case ets:lookup(comp, I) of
        [] -> comp_i_mc(I*I, I, N), C-1
        ;_ -> C
    end,
    composite_mc(N,I+1,M,C1);
composite_mc(_,I,M,_) when I > M -> ok;
composite_mc(N,I,M,0) ->
    receive
        {ok, cim} -> composite_mc(N,I,M,1)
    after 1000    -> composite_mc(N,I,M,0)
    end.

comp_i_mc(J, I, N) -> 
    Parent = self(),
    spawn(fun() ->
        comp_i(J, I, N),
        Parent ! {ok, cim}
    end).

comp_i(J, I, N) when J =< N -> ets:insert(comp, {J, 1}), comp_i(J+I, I, N);
comp_i(J, _, N) when J > N -> ok.

it is super slow solution, because we have to generate a lot of prime numbers befoe we can check they are pandigital:

1
2
3
4
5
6
7
mkh@mkh-xps:~/work/mblog/pr_euler/p41$ time ./p41.erl
./p41.erl:24: Warning: function is_prime/1 is unused
Answer: 7652413 

real    1m45.544s
user    6m17.215s
sys     0m2.127s

Erlang version 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname p41
% vim:syn=erlang

main(_) -> 
    io:format("Answer: ~p ~n", [t41(7654321)]).

t41(N) ->
    case is_prime(N) of
        true -> case isPandigital(integer_to_list(N)) of
                    true -> N
                    ;_   -> t41(N-1)
                end
        ;_   -> t41(N-1)
    end.

isPandigital(N) ->
    case {length(N), lists:usort(N)} of
        {7, "1234567"} -> true
        ;_             -> false
    end.

is_prime(N)   -> is_prime(N, 2).
is_prime(N,M) when N rem M =:= 0 -> false;
is_prime(N,M) -> 
    case (M+1) < math:sqrt(N) of
        true -> is_prime(N,M+1)
        ;_   -> true
    end.

this verion much faster:

1
2
3
4
5
6
mkh@mkh-xps:~/work/mblog/pr_euler/p41$ time ./p41_1.erl
Answer: 7652413 

real    0m3.047s
user    0m3.044s
sys     0m0.016s

and most optimal version:

Erlang version 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname p41
% vim:syn=erlang

main(_) -> 
    Answer = t41([list_to_integer(string:join(X,""))||X<-permutations(["7","6","5","4","3","2","1"])]),
    io:format("Answer: ~p ~n", [Answer]).

permutations([]) -> [[]];
permutations(L)  -> [[H|T] || H <- L, T <- permutations(L--[H])].

t41([N|T]) ->
    case is_prime(N) of
        true -> N
        ;_   -> t41(T)
    end.

is_prime(N)   -> is_prime(N, 2).
is_prime(N,M) when N rem M =:= 0 -> false;
is_prime(N,M) -> 
    case (M+1) < math:sqrt(N) of
        true -> is_prime(N,M+1)
        ;_   -> true
    end.

this version more 10 times faster than second version:

1
2
3
4
5
6
mkh@mkh-xps:~/work/mblog/pr_euler/p41$ time ./p41_2.erl
Answer: 7652413 

real    0m0.252s
user    0m0.244s
sys     0m0.020s

And finally python implementation:

Python version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python

import itertools, math

def is_prime(n):
    if n % 2 == 0 and n > 2: 
        return False
    for i in range(3, int(math.sqrt(n)) + 1, 2):
        if n % i == 0:
            return False
    return True

for x in list(map("".join, itertools.permutations( list("7654321") ))):
    if is_prime(int(x)):
        print "Answer: %s" % x 
        break

as usual python version faster than erlang

1
2
3
4
5
6
mkh@mkh-xps:~/work/mblog/pr_euler/p41$ time ./p41.py
Answer: 7652413

real    0m0.022s
user    0m0.021s
sys     0m0.000s