Showing posts with label 프로젝트 오일러. Show all posts
Showing posts with label 프로젝트 오일러. Show all posts

Thursday, May 21, 2015

Project Euler #41 - largest pandigital prime

프로젝트 오일러 41번
1,2,..,n 이 한번씩만 나오는 n자리수 소수 중 가장 큰 것은?

1,2,3,...,9를 한번씩 사용하는 9자리 소수가 존재할까?
=> 각 자리수를 더하면 45, 3의 배수이므로 소수가 될 수 없다.
그럼 8자리 소수는?
=> 각 자리수를 더하면 36, 역시 소수가 될 수 없다.
7자리 소수는? => 찾아보자!
6자리, 5자리 소수는? => 역시 3의 배수

7자리에서 찾아보고, 안 되면 4자리수를 찾아봐야지~

코딩하기 쉬운 방법: 7자리 모든 소수를 찾고, 8이나 9가 들어간 걸 빼고, pandigital인지 체크하고, 이들 중 가장 큰 것을 찾는다. => 천만까지의 소수를 찾아야 한다. 약 7^7 만큼의 수(에서 소수인 것들 중에서) pandigital인지 체크해야 한다.

연산을 줄일 수 있는 방법: 1,2,..,7의 순열(permutation)으로 만들 수 있는 모든 수에 대해서 소수인지 체크한다. 이왕이면 큰 수부터 나오도록 순열을 만든다. => 소수인지 '체크'를 위해서는 sqrt(7654321) 이하의 소수를 미리 찾아두어야 한다.
약 0.023초 걸린다.

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

def isprime(n, primes):
    for p in primes:
        if n % p == 0: return False
        if p*p > n: return True
    return True

def perm(lst):
    if len(lst) == 1:
        return [(lst[0],)]
    rst = []
    for i, e in enumerate(lst):
        rst += [(e,)+x for x in perm(lst[:i]+lst[i+1:])]
    return rst

pn = rwh_primes1(int(7654321**.5))
seven = '7654321'
for pm in perm(range(7)):
    n = int(''.join([seven[pm[i]] for i in range(7)]))
    if isprime(n,pn):
        print n
        break

permutation의 13번째만에 필요한 답이 나왔다. perm함수를 리스트로 return하는 대신 하나씩 yield하는 방법을 찾으면 더 좋을텐데(지금은 쓸데 없이 7!=5040길이의 리스트를 return), 재귀함수에 yield를 어떻게 쓰는지 모르겠다.

perm함수를 정의하는 대신, itertools의 permutations함수를 가져오면 이 문제가 해결되는 듯 하다. 실행시간이 1/50로 줄어든다.

from itertools import permutations as pm

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

def isprime(n, primes):
    for p in primes:
        if n % p == 0: return False
        if p*p > n: return True
    return True

pn = rwh_primes1(int(7654321**.5))
for pm in pm('7654321'):
    if isprime(int(''.join(pm)),pn):
        print ''.join(pm)
        break


Tuesday, May 19, 2015

Project Euler #39 - Interger right triangle

프로젝트 오일러 39번
둘레의 길이가 120이 되는 각 변의 길이가 정수인 직각삼각형은 3가지가 있다.
둘레의 길이가 얼마일 때 경우의 수가 가장 많을까? 단, 둘레의 길이는 1000이하.

제일 긴 변을 c 라고 하자.
a^2 + b^2 = c^2
=> a^2 = (c+b)(c-b)
=> c-b < a < c+b
d = c-b 라고 두면 아래와 같이 코딩할 수 있다.

from sympy import divisors

rs = {}
for a in range(3,333):
    for d in [x for x in divisors(a**2) if x < a]:
        if (d + a**2/d) % 2 == 0:
            c = (d + a**2/d) / 2
            b = c - d
            if a < b and a + b + c < 1000:
                if a+b+c in rs: rs[a+b+c] += 1
                else: rs[a+b+c] = 1
    
print max(rs,key=rs.get)


피타고라스 수의 성질을 이용하면 종이에(또는 암산으로) 답을 찾을 수 있다.
a, b, c = m^2-n^2, 2mn, m^2+n^2 이라고 하면,
둘레의 길이는 2m(m+n)이다. 다르게 표현하면, 둘레의 길이 l(숫자 일이 아니라 알파벳 엘)은 2*m*(m+n)의 형태로 표현될 수 있다. 1000이하의 수 중 약수의 수가 최대가 되는 l 을 찾으면 되지 않을까? 
소수를 늘어놓고.. 2, 3, 5, 7, 11
l = 2*3*5*7*11은 1000을 넘어가므로 not good
l = 2*2*3*5*7 은 1000을 넘지 않고 약수가 충분히 많다
l = 2*2*2*3*5*7 도 1000을 넘지 않고 약수가 충분히 많다. 약수가 더 많아질 수는 없다.
그래서 답은 840으로 추측.
(m > n 조건도 체크해야 하는데 생략했으므로 '추측'이라고 했다.)

Monday, May 18, 2015

Project Euler #38 - Pandigital multiples

프로젝트 오일러 38번
192에는 1, 2, 3을 곱하면
192
384
576
1~9가 한번씩 나온다. 이런 성질을 갖는 가장 큰 수는?

9자리 중에 가장 큰 수는 987654321.
9 _ _ _ * 2 = 18 _ _ _ 의 성질을 만족하는 숫자를 찾을 수 있으면 좋겠다.
9, 1, 8을 빼고 남은 숫자는 2, 3, 4, 5, 6, 7
9 a _ _ 에서 a 는 5보다 작아야 하므로 2나 3이 올 수 있다. 4가 오면 *2 했을 때 문제.
그럼 9 3 _ _ * 2 = 1 8 6 _ _ 또는 9 3 _ _ * 2 = 1 8 7 _ _ 의 형태가 될 수 있을까?
남은 숫자가 몇 개 안 되니까 모든 경우를 따져볼 수 있는데.. 932718654 가 답이다.

이번에는 코딩으로 풀어보자.
우선 9, 18, 27, 36, 45는 알려진 후보이므로 이보다 큰 걸 찾는 문제로 생각하면 된다.
첫번째 수가 9로 시작하는 2자리라면, *2=>3자리, *3=>3자리.. 붙여서 9자리를 만들 수 없다.
첫번째 수가 9로 시작하는 3자리라면, *2=>4자리, *3=>4자리.. 붙여서 9자리를 만들 수 없다.
첫번째 수는 9로 시작하는 4자리라야 한다. (이것도 아니라면 답은 918273645)

def pandigital():
    for i in range(9876,9123,-1):
        if set(str(i)+str(i*2)) == set('123456789'):
            return i*100000+i*2
    return 918273645

print pandigital()

끝.

Project Euler #37 - 잘라도 소수인 소수

프로젝트 오일러 37번
3797은 왼쪽에서 잘라도, 오른쪽에서 잘라도 계속 소수이다. 3, 37, 379, 3797 / 7, 97, 797, 3797
이런 수가 11개 있는데, 이들을 다 더하면?

1번 방법 - 0.2초
2번 방법 - 0.05초

우선 1번 방법

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

pn = rwh_primes1(1000000)
pns = set(pn)

rs = []
for p in pn:
    ps = str(p)
    chk = 1
    for i in range(1,len(ps)):
        if int(ps[:i]) not in pns or int(ps[i:]) not in pns:
            chk = 0
            break
    if chk == 1 and p > 10:
        rs.append(p)
        if len(rs) == 11:
            print sum(rs)
            break

100만까지의 모든 소수를 만들고, 작은 것부터 하나씩 위의 조건을 만족하는지 체크한다. 11개를 다 찾으면 답을 출력하고 끝낸다. 11개를 다 못 찾았으면 아무 것도 출력하지 않는다. 아마 1000만까지 소수를 찾은 다음에 다시 시도해야겠지.


2번 방법
1자리 소수 - 2, 3, 5, 7의 왼쪽/오른쪽에 숫자를 하나씩 붙여보면서 소수가 되는지 체크한다. 그런 식으로 왼쪽에서부터 하나씩 잘라가도 계속 소수가 되는 2자리수, 3자리수, ... 오른쪽에서부터... 2자리수, 3자리수.. 를 모두 찾는다. 둘의 교집합을 찾는다.

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

pn = rwh_primes1(1000)

def chk_pn(n,pn):
    if n == 1: return False
    for p in pn:
        if n % p == 0: return False
        if p**2 > n: return True

rpn = []
stage = [3,7]
found = []
d = 1
while stage:
    for b in stage:
        for x in range(1,10):
            if chk_pn(x*10**d+b,pn): found.append(x*10**d+b)
    rpn += stage
    d += 1
    stage, found = found, []

lpn = []
stage = [2,3,5,7]
found = []
d = 1
while stage:
    for b in stage:
        for x in [1,3,7,9]:
            if chk_pn(b*10+x,pn): found.append(b*10+x)
    lpn += stage
    d += 1
    stage, found = found, []

print sum([x for x in lpn if x in rpn and x > 10])


Sunday, May 17, 2015

Project Euler #36 - 거꾸로 해도 같은 수

프로젝트 오일러 36번
100만 이하의 수 중에서, 10진수로 하든 2진수로 하든 거꾸로 해도 자기자신이 되는 수를 모두 더하면?

33 = 100001 (2) 같은 것도 되지만, 313 = 100111001 같은 경우도 고려해야 한다.

def d2b(d):
    b = ''
    while True:
        b += str(d%2)
        d /= 2
        if d == 0: return b == b[::-1]

sm = 0
for i in ['']+[str(x) for x in range(1,1000)]:
    for j in ['']+[str(x) for x in range(10)]:
        s = str(i)+str(j)+str(i)[::-1]
        if len(s) >= 1 and len(s) <= 6:
            pd = int(s)
            if d2b(pd):
                sm += pd

print sm

0.03~0.05초 정도 걸린다.

d2b함수는 10진수를 2진수(정확히는 2진수를 거꾸로 쓴 수)를 찾고 그게 palindrome인지 체크한다.
i는 10진수로 했을 때 왼쪽 절반을, j는 가운데 숫자를 의미한다.
i=58, j=3 => 58385
i = 2, j='' => 22
i = '', j=7 => 7

컴퓨터 내부적으로는 2진수가 기본일테니 여기에서처럼 10진수를 2진수로 바꾸는 대신, 2진수를 기본으로 palindrome을 만들고, 조건에 맞을 때 10진수로 변환해서 palindrome인지 체크하는 게 더 빠를 것 같은데.. 어떻게 하는지도 모르겠고, 지금 속도도 문제가 되지 않는 듯 해서 패쓰~


Monday, April 20, 2015

Project Euler #25 - 피보나치 수열은 몇번째 항에서 1000자리를 넘어가는가?

피보나치 수열에서 1000자리를 넘어가는 첫번째 항은?

포럼에는 역시 기발한 방법들이 있지만, 여기서는 나이브하게...

i, t, f = 1, 0, 1

while len(str(f)) < 1000:
    i += 1
    t, f = f, f+t

print i



Project Euler #24 - 100만번째 permutation

0~9의 permutation 으로 만들 수 있는 숫자들을 차례로 늘어놓았을 때, 100만번째 수는?

1번째는 0123456789
9!+1번째는 1023456789
9!*2+1번째는 2013456789
그럼 제일 앞에 2를 고정하고, 9개의 숫자로 permutation을 만들었을 때  100만 - 9!*2 번째 숫자를 찾는 문제로 바뀐다.

코드로 표현하면,

factorial = {1:1}
for i in range(2,10):
    factorial[i] = i*factorial[i-1]

answer = ''
d = range(10)
N = 1000000-1
for i in range(9,0,-1):
    answer += str(d.pop(N / factorial[i]))
    N %= factorial[i]

print answer + str(d[0])


Project Euler #23 - Non-abundant sums

Non-abundant sums: 두 개의 abundant numbers(자기 자신을 뺀 약수의 합이 자기 자신 보다 커지는 숫자)를 더해서 만들 수 없는 숫자들의 합은?

문제에서 28123보다 큰 수는 모두 두 개의 a.n 합으로 표현할 수 있다고 했으니 그보다 작은 수만 체크하면 된다. 두 개의 a.n 로 만들 수 "없는" 것보다는 만들 수 "있는" 쪽을 찾는 게 쉬워 보인다.

sympy를 쓰면 어렵지 않다.

from sympy import divisors

abun = []
for i in range(12,28123):
    if sum(divisors(i)[:-1]) > i: abun.append(i)

allabuncomb = [True] * 28123
for i,a in enumerate(abun):
    for b in abun[i:]:
        if a+b >= 28123: break
        allabuncomb[a+b] = False

print sum([n for n in range(28123) if allabuncomb[n]])


역시나 다른 문제들처럼 소수-소인수분해-약수합 부분을 직접 구현하면 약간 빨라진다. 하지만 abundant number 를 찾는 부분보다, 그 숫자들로 조합을 만드는 부분이 오래 걸리는 거라 큰 개선은 없다.

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

def factor(n,pn):
    rs = {}
    for p in pn:
        if n % p == 0:
            cnt = 0
            while n % p == 0:
                n /= p
                cnt += 1
            rs[p] = cnt
            if n == 1: return rs
        if p*p > n:
            rs[n] = 1
            return rs

def isAbun(n,pn):
    fDic = factor(n,pn)
    dn = 1
    for k in fDic:
        dn *= sum([k**i for i in range(fDic[k]+1)])
    return (dn - n > n)

pn = rwh_primes1(28123)
abun = []
for n in range(12,28123):
    if isAbun(n,pn): abun.append(n)

allabuncomb = [True] * 28123
for i,a in enumerate(abun):
    for b in abun[i:]:
        if a+b >= 28123: break
        allabuncomb[a+b] = False

print sum([n for n in range(28123) if allabuncomb[n]])


Project Euler #22 - 이름 숫자

알파벳을 숫자로 변환하고 주어진 공식으로 순위와의 가중합을 구하는 문제

포럼에 올라온 글에 자극받아서 아예 한 줄 코드로 만들어 봤다.

print sum([(i+1)*sum([ord(c)-64 for c in name]) for i,name in enumerate(sorted([name.strip('"') for name in open('p022_names.txt').read().split(',')]))])

ord는 문자에 해당하는 아스키번호를 알려주는 함수라고 한다.

아래와 같이 정상적인(?) 코드를 작성할 수도 있다.

ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
alphanum = {}
for i,c in enumerate(ABC):
    alphanum[c] = i + 1

def calcname(name):
    return sum([alphanum[s] for s in name])

with open('/Users/Dongug/Downloads/p022_names.txt') as f:
    names = [name.strip('"') for name in f.readline().split(',')]

rs = 0
for i,name in enumerate(sorted(names)):
    rs += (i + 1) * calcname(name)

print rs


Sunday, April 19, 2015

Project Euler #21 - amicable numbers

10000이하의 친화수를 찾는 문제

sympy의 divisors함수를 쓰면 쉽다.

from sympy import divisors

d_nDic = {}
for i in range(3,10000):
    d_nDic[i] = sum(divisors(i))-i

ami = []
for i in d_nDic:
    if d_nDic[i] > 3 and d_nDic[i] < 10000:
        if i == d_nDic[d_nDic[i]] and i != d_nDic[i]:
            ami.append(i)

print sum(ami)


10000까지 반복해서 소인수분해를 해야 하는데, 좀 더 빠르게 하려면 소수를 직접 찾고, 소인수 분해를 직접 한 다음에, 약수의 합을 계산하면 된다.
마지막 스텝은 중학교 때 배운 공식 24 = 2^3 * 3 => 24의 약수의 합=(1+2+4+8)*(1+3)=1+2+4+8+3+6+12+24 를 쓰면 된다.
코드는 길어지지만 실행시간은 1/4로 줄어든다.

def rwh_primes1(n):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

def factor(n,pn):
    rs = {}
    for p in pn:
        if n % p == 0:
            cnt = 0
            while n % p == 0:
                n /= p
                cnt += 1
            rs[p] = cnt
            if n == 1: return rs
        if p*p > n:
            rs[n] = 1
            return rs

def d_n(n,pn):
    fDic = factor(n,pn)
    dn = 1
    for k in fDic:
        dn *= sum([k**i for i in range(fDic[k]+1)])
    return dn - n

pn = rwh_primes1(10000)
d_nDic = {}
for i in range(3,10000):
    d_nDic[i] = d_n(i,pn)

ami = []
for i in d_nDic:
    if d_nDic[i] > 3 and d_nDic[i] < 10000:
        if i == d_nDic[d_nDic[i]] and i != d_nDic[i]:
            ami.append(i)

print sum(ami)