Project Euler problem 51

Prime digit replacements

By replacing the 1st digit of the 2-digit number *3, it turns out that six of the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime.

By replacing the 3rd and 4th digits of 56**3 with the same digit, this 5-digit number is the first example having seven primes among the ten generated numbers, yielding the family: 56003, 56113, 56333, 56443, 56663, 56773, and 56993. Consequently 56003, being the first member of this family, is the smallest prime with this property.

Find the smallest prime which, by replacing part of the number (not necessarily adjacent digits) with the same digit, is part of an eight prime value family.

Link to original description
Source code examples on Github

brutforce python solution is slow:

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
#!/usr/bin/python

import prime
from combinatorics import uniqueCombinations

cache = {}
def prime_family_length(n, digits):
    if cache.has_key((n, digits)): return cache[n, digits]

    num, nums, count = list(str(n)), [], 0
    if len(dict.fromkeys(num[d] for d in digits).keys()) > 1:
        return cache.setdefault((n, digits), 0)                                # The digits must have the same number

    for d in range(0 in digits and 1 or 0, 10):                                 # Ensure 0 is not the first digit
        for x in digits: num[x] = str(d)
        n = int(''.join(num))
        if prime.isprime(n): count += 1
        nums.append(n)
    for n in nums: cache[n, digits] = count
    return count

prime._refresh(100000)

n, max, max_count, combos = 10, 0, 0, {}
while max_count < 8:
    p = prime.prime(n)
    digits = range(0, len(str(p)))
    for size in xrange(1, len(digits)):
        patterns = combos.setdefault((len(digits), size),
            tuple(tuple(sorted(p)) for p in uniqueCombinations(digits, size)))
        for pat in patterns:
            count = prime_family_length(p, pat)
            if count > max_count: max, max_count = p, count
    n += 1

print p

Lets try to simplyfy solution algorithm. We are looking prime number with repeated digits. And we are looking 8 member family. And since we are looking for the smallest member of the family our repeated digits has to be 0, 1 or 2. Otherwise we wont be able to make an eight member family.

A number can be divided by 3 if sum all his digits is divisible by 3. We can use this rule to filter some prime candidates. If we calculate the digit sum mod 3 of the repeated digits we get the following results with the number of repeated digits:

|         | number of repeated digits   |
|         |  1  |  2  |  3  |  4  |  5  |
|_________|_____|_____|_____|_____|_____|
|   0     |  4  |  4  | 10  |  4  |  4  |
|_________|_____|_____|_____|_____|_____|
|   1     |  3  |  3  |  0  |  3  |  3  |
|_________|_____|_____|_____|_____|_____|
|   2     |  3  |  3  |  0  |  3  |  3  |
|_________|_____|_____|_____|_____|_____|

If the number we have has 1 repeated digit then the result n % 3 will be 0 4-times -> (0,3,6,9). It will be 1 a total of 3 times -> (1,4,7) and 2 a total number of 3 times -> (2,5,8). This means if we have 1 repeating digit and the “digit sum mod 3” of the non repeating digits is 0 we will add 0 4 times.

Which means that 4 of those numbers will be divisible with 3. Therefore 4 of those numbers wont be prime and we can’t have an 8-member family then. The same goes with all other numbers except 3, 6 and 9.

Based on that I can concluce that the repeating digit has to be three, otherwise I will never be able to make 8 different primes out of it, since too many of the candidates will be divisble by three no matter what the rest of the number is.

Another explanation:

We are have number with 1 repeated digit: *xxxxx…. . If we are replace * with 1 to get a prime P. Now if P % 3 = 1 we are cannot replace * with 0,3,6,9 otherwise the number become divisible by 3 (become composite). If P % 3 = 2 we are cannot replace * with 2,5,8 for the same reason. -> making 8-family-primes are impossible for any number of digits.

We are have number with 2 repeated digits: **xxxx… . Replace both * with 1 to get prime P. If P % 3 = 2 -> cannot replace with 0,3,6,9. If P % 3 = 1 -> cannot replace with 2,5,8.

So now we know a bit more about the kind of prime we are looking for. I assume that the prime will be 6 digits. It must have 3 digit being 0,1 or 2 excluding the last digit of the number.

so we are have python version for this:

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/python

import prime, string
prime._refresh(80000)

def is_8_prime_family(p, d):
    c = 0
    for r in '0123456789':
        np = int(string.replace(p, d, r))
        if(np > 100000 and np < 999999 and prime.isprime(np)): c += 1
    return c==8

n=9000
while(True):
    n += 1
    p = prime.prime(n)
    if p < 100000: continue
    if p > 999999: break
    ps = str(p)
    ld = ps[5:6]
    if (ps.count('0')==3 and is_8_prime_family(ps, '0')) or (ps.count('1')==3 and ld!='1' and is_8_prime_family(ps, '1')) or \
        (ps.count('2')==3 and is_8_prime_family(ps, '2')):
        print "Answer: %s %s" % (n, ps)
        break

and erlang version:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname p51
% vim:syn=erlang

-mode(compile).

main(_) ->
    get_primes(1000000),
    io:format("Answer ~p ~n",[ p51( lists:sort([K||{K,_}<-ets:tab2list(prim) ,K>100000,K<200000]) )]).

p51([N|T]) when N>100000 ->
    NS = integer_to_list(N),
    LD = lists:last(NS),
    case p51(NS, [LD], "0") of
        0 -> p51(T);
        A -> A 
    end;
p51([_|T]) -> p51(T).

p51(NS, "1", "1") -> p51(NS, "1", "2");
p51(NS, LD, R) ->
    case {c_count(NS,R), is_8_prime_family(NS,R), R} of
        {3, true,_} -> list_to_integer(NS);
        {_, _, "0"} -> p51(NS, LD, "1");
        {_, _, "1"} -> p51(NS, LD, "2");
        {_, _, "2"} -> 0
    end.

is_8_prime_family(Str, D) ->
    Fun = fun(N,A) ->
        case list_to_integer(re:replace(Str, D, integer_to_list(N), [global, {return, list}])) of
            NP when NP > 100000,NP < 999999 ->
                case is_prime(NP) of
                    true -> A + 1
                    ;_   -> A
                end
            ;_ -> A
        end
    end,
    Ret = lists:foldl(Fun, 0, lists:seq(0, 9)),
    Ret == 8.

c_count(Str, C) -> lists:foldl(fun(E,A) when [E]==C->A+1;(_,A)->A  end,0,Str).

%----------------------------------------------------------------------------------------------------------------------------

is_prime(N) ->
    case ets:lookup(prim, N) of
        [] -> false
        ;_ -> true
    end.
%----------------------------------------------- prime generator from Project Euler 10 (version 5 ---------------------------)
get_primes(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([P || {P,_} <-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.