def problem_50():

    limit = 10**6

    # find the smallest list of primes which will sum up to at least the limit we set above
    for x in count(1):
        primes = list(get_primes_under(10**x))
        if sum(primes) > limit:
            break

    # starting with the longest possible sequence (every element in the list), and then working
    # to smaller sequence lengths
    for length in reversed(range(1, len(primes) + 1)):

        # Use a "sliding window" approach. Starting at the low end of the list, grab the first
        # `length` elements and check it. Then slide the window right 1 element, and grab those,
        # and check it, etc
        for start in range(len(primes)-length):
            candidate = sum(primes[start:start+length])

            # if the candidate sum is bigger than the limit, no point in checking any more windows
            # of size `length`. Break the inner loop, and reduce the length
            if candidate >= limit:
                break

            # if the candidate sum is prime, we're done!
            if is_prime(candidate):
                print(candidate)
                return
def problem_49():

    # get a generator of primes under 10k (since we know the primes in question are 4 digits)
    # and then discard all 3-digit ones
    primes = get_primes_under(10**4)
    while next(primes) < 999:
        pass

    # turn the remainder of the primes in the generator into a list
    primes = list(primes)

    for p in primes:
        if p in (1487, 4817, 8147):
            # skip this one since the problem is asking for the other one
            continue

        # get the digits of the current prime in sorted order, and get any other primes
        # in the list which contain exactly the same digits
        sorted_digits = sorted(str(p))
        others = [x for x in primes if x != p and sorted(str(x)) == sorted_digits]

        # if we don't have at least 2 other primes with the same digits, skip to the next prime
        if len(others) < 2:
            continue

        # check each pair of primes in the `others` list, and if the difference between them is
        # the same as the difference between our current prime and the first of these, we have
        # a winner
        for n1 in others:
            for n2 in [x for x in others if x > n1]:
                if (n2 - n1) == (n1 - p):
                    print('{}{}{}'.format(p, n1, n2))
                    return
def can_be_written_as_prime_plus_twice_square(n):
    """ Returns True if n can be written as the sum of a prime and twice a square number, or
    returns False otherwise. """

    if is_prime(n):
        return True

    # For every prime under n, find what value of x satisfies:   n = prime + 2x^2
    # If any of those values x are an integer, then n can be written as prime + 2x square number
    for prime in get_primes_under(n):
        x = ((n - prime) / 2) ** 0.5
        if x % 1 == 0:
            return True

    # If we get here, n cannot be written as prime + 2x square number
    return False
def problem_60():

    # get list of primes, discard 2
    primes = list(get_primes_under(9000))
    primes.pop(0)

    for a in primes:

        # build up a list of primes which pair with `a` to concatenate and form other primes
        pairs_with_a = list()
        for b in primes:
            if a == b: continue
            if concats_to_prime(a, b):
                pairs_with_a.append(b)

        # For every combination of 4 in the primes which pair with `a`:
        #     if every pair of them also concats_to_prime, we found the winner!
        # 
        # We know the first result obtained must be the correct answer, since combinations()
        # yield combos in lexographical order, so we'll get combos with smaller numbers first
        for combo in combinations(pairs_with_a, 4):
            if all(concats_to_prime(p1, p2) for p1, p2 in product(combo, repeat=2) if p1 != p2):
                print(sum(combo) + a)
                return
def problem_10():
    print(sum(get_primes_under(2000000)))
644 = 2² × 7 × 23
645 = 3 × 5 × 43
646 = 2 × 17 × 19.

Find the first four consecutive integers to have four distinct prime factors. What is the first
of these numbers?
"""

from utils.timer import time_it
from utils.primes import get_primes_under
from itertools import count

#-------------------------------------------------------------------------------------------------

# this limit determined experimentally
primes = list(get_primes_under(10**3))

def has_4_distinct_prime_factors(n):
    """ Returns True if n has exactly 4 distinct prime factors, or False otherwise. """

    factor_count = 0
    for p in primes:
        if n % p == 0:
            # if it already has 4 factors so far, another will disqualify it, so just return False
            if factor_count == 4:
                return False
            factor_count += 1

    # we don't have any more than 4 here, but make sure we don't have less
    return factor_count == 4