terça-feira, 17 de maio de 2016

Recursividade

Recursividade é quando uma função chama a mesma função dentro dela mesma.
Vários problemas podem ser resolvidos com recursão. Para criarmos uma função recursiva primeiramente precisamos analisar o problema e entender a sequência para definirmos quando a função deverá sair da recursividade e retornar algum valor.
Por exemplo a série de Fibonacci. [1,1,2,3,5,8,13...]
Vamos criar uma função que calcule o valor da série e acordo com o índice que passamos. Se passarmos o número 3 a função tem que retornar o número 2 como resposta, se passarmos o número 6 como parâmetro a função tem que retornar o número 8 como resposta e assim sucessivamente.
Analisando o série concluímos que quando o parâmetro passado for 1 o retorno será 1, quando for 2 o retorno será 1, quando for 3 o retorno será 2, esses serão os retornos que farão com que a função saia da recursão.
Obs: estou fazendo dessa forma para ficar bem fácil de entender recirsividade
def fib_rec(n):
# essa é a condição que fará com que a função pare de chamar ela mesma 
 if n==1 or n==2:
  return 1 # e retorne algum valor, que nos dois casos será 1
# o retorno da função se não entrar no if que termina a recursão é ela chamar
# ela mesmo diminuindo o valor do parâmetro por 1 e depois por 2 e somar os resultados
# a função vai diminuindo o valor de n até que entre na função que para a recursão
 return fib_rec(n-1) + fib_rec(n-2) 
Nos comentários do código acima explico detalhadamente como a função funciona e consequentemente a recursividade.
Qualquer dúvida postem nos comentários.

segunda-feira, 16 de maio de 2016

Resposta do problema 19 - Counting Sundays

O objetivo desse problema é somar quantos domingos aconteceram no primeiro dia do mês entre as datas: 01/01/1901 e 31/12/2000 usando apenas as informações passadas que são:

  • 01/01/1900 foi uma segunda-feira
  • 30 dias tem setembro, abril, junho e novembro
  • todos os outros meses tem 31 exceto fevereiro que pode ter 28 ou 29 (quando for bissexto)
  • o ano bissexto ocorre em todos os anos divisíveis por 4 exceto na virada do século, a menos que esse ano seja divisível por 400

Se fosse para usar as bibliotecas de data de qualquer linguagem esse problema seria muito simples, mas o objetivo é fazer com que o programador crie uma lógica apenas com as informações passadas.
You are given the following information, but you may prefer to do some research for yourself.
  • 1 Jan 1900 was a Monday.
  • Thirty days has September,
  • April, June and November.
  • All the rest have thirty-one,
  • Saving February alone,
  • Which has twenty-eight, rain or shine.
  • And on leap years, twenty-nine.
  • A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.
How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?
Link para a pergunta: https://projecteuler.net/problem=19 

Criei o seguinte script para resolver o problema;
#Retorna true se o ano é bissexto e false se não for
def is_leap_year(ano):
 if (ano % 2 != 0):
  return False
 else:
  if (ano % 100 == 0):
   if (ano % 400 == 0):
    return True
   else:
    return False
  else:
   if (ano % 4 == 0):
    return True
   else:
    return False

dia_semana = 1 # 1 = segunda-feira / 7 = domingo
total_domingo_inico_mes = 0

def proximo_dia_semana(dia_atual):
 if dia_atual == 7:
  return 1
 else:
  return dia_atual + 1

for y in range(1900, 2001):
 for m in range(1,13):
  if m in (4,6,9,11): # meses com 30 dias
   for d in range(1,31):
    if (d == 1 and y != 1900 and dia_semana == 7):
     total_domingo_inico_mes += 1
    dia_semana = proximo_dia_semana(dia_semana)

  elif m in (1,3,5,7,8,10,12): # meses com 31 dias
   for d in range(1,32):
    if (d == 1 and y != 1900 and dia_semana == 7):
     total_domingo_inico_mes += 1
    dia_semana = proximo_dia_semana(dia_semana)
  else: # fevereiro
   if is_leap_year(y):
    for d in range(1,30): # se o ano for bissexto fevereiro terá 29 dias
     if (d == 1 and y != 1900 and dia_semana == 7):
      total_domingo_inico_mes += 1
     dia_semana = proximo_dia_semana(dia_semana)
   else:
    for d in range(1,29): # se o ano não for bissexto fevereiro terá 28 dias
     if (d == 1 and y != 1900 and dia_semana == 7):
      total_domingo_inico_mes += 1
     dia_semana = proximo_dia_semana(dia_semana)

print(total_domingo_inico_mes)

Primeiramente criei uma função que retorna True se o ano for bissexto e False se não for.
Depois eu criei a função que calcula o próximo dia da semana (se o dia for 7 o próximo tem que ser 1, pois a semana começa novamente). Eu considerei que segunda-feira foi o dia 1, pois no enunciado do problema fala que 01/01/1900 foi uma segunda-feira e é a partir dessa data que o programa inicia.
Depois disse fiz vários "for's" que passam pelos anos, meses e dias do período especificado.
O script começa a contar a partir de 01/01/1900, mas só pode considerar na soma de domingos no início do mês a partir de 01/01/1901.

Algumas outras soluções que encontrei no fórum:

Usuário: emandres
import datetime
count = 0
for y in range(1901,2001):
    for m in range(1,13):
        if datetime.datetime(y,m,1).weekday() == 6:
            count += 1
print count

Usuário: nmadnani
numdays ={1:31,2:28,3:31,4:30,5:31,6:30,7:31,8:31,9:30,10:31,11:30,12:31}
startday = 2
count = 0
for m in range(1,13):
    currday = startday
    for curryear in xrange(1901,2001):
        leap = False
        prevyear = curryear-1
        if prevyear % 100 == 0 and prevyear % 400 == 0:
            leap = True
        elif prevyear % 4 == 0:
            leap = True

        if leap:
            currday = (currday + 2) % 7
        else:
            currday = (currday + 1) % 7

        if currday == 0:
            count += 1
    tmp = numdays[m] + 1
    startday = (startday + (tmp - ((tmp/7)*7)) - 1) % 7
 
print count

terça-feira, 10 de maio de 2016

Resposta do problema 050 - Consecutive prime sum

Resolvi esse problema utilizando força-bruta, demorou 692 segundos.
O objeivo desse problema é encontrar o número primo abaixo de 1000000 que seja originado da maior sequência de números primos.
Consecutive prime sum
Problem 50
The prime 41, can be written as the sum of six consecutive primes:
41 = 2 + 3 + 5 + 7 + 11 + 13
This is the longest sum of consecutive primes that adds to a prime below one-hundred.
The longest sum of consecutive primes below one-thousand that adds to a prime, contains 21 terms, and is equal to 953.
Which prime, below one-million, can be written as the sum of the most consecutive primes?
Link para a pergunta: https://projecteuler.net/problem=50

Criei o seguinte script para resolver o problema:
def is_prime(n):
	if n == 2 or n == 3: return True
	if n < 2 or n%2 == 0: return False
	if n < 9: return True
	if n%3 == 0: return False
	r = int(n**0.5)
	f = 5
	while f <= r:
		#print('\t',f)
		if n%f == 0: return False
		if n%(f+2) == 0: return False
		f +=6
	return True

# lista que armazena todos os números primos até o limite desejado que no caso do problema é < 1000000
lista_primos = []

# número máximo pra verificar, no caso do problema é 1000000
limite = 1000000

# preenche a lista lista_primos com os números primos até o valor da variável limite
for x in range(1, limite):
	if is_prime(x):
		lista_primos.append(x)

def maior_numero_soma_seq(lista):
	i = 0
	lista.reverse()
	maior_numero_lista = max(lista)
	maior_sequencia = 0
	maior_numero = 0
	soma = []
	tamanho_lista = len(lista)
	while i < len(lista):
		j = i + 1
		soma.clear()
		soma.append(lista[i])
		while j < len(lista):
			if lista[j] < lista[i]:
				soma.append(lista[j])
				# fiz isso para que se o sum da lista soma for maior que o maior numero da lista
				# ele já passa e muda o indice i do while, ou seja passa para o próximo número
				# de início
				if sum(soma) > maior_numero_lista:
					j = len(lista) + 1
				else:
					if sum(soma) in lista:
						if len(soma) > maior_sequencia:
							maior_sequencia = len(soma)
							maior_numero = sum(soma)
							print("Maior sequencia = ",maior_sequencia)							
							print(soma, "=" , maior_numero)
							print("Indice i =", i,"de", tamanho_lista, "- % analisados =", (i/tamanho_lista)*100,"%")
							print("------------------------------------")
			j += 1
		i += 1
	return maior_numero

print("Resposta =", maior_numero_soma_seq(lista_primos))

Resposta (answer) - Selecione a linha abaixo para ver a resposta
997651

O script funciona da seguinte forma: crio uma lista com todos os números primos, depois vou analisando um a um com todas as somas possíveis (a soma tem que ser um número primo na lista).
Coloquei uns "print" para ver a evolução do processamento.
A medida que vai processando vai ficando cada vez mais lento, futuramente pretendo otimizar esse script.

Outras respostas postadas por outros usuários:
Usuário: dpinchbe
def primesieve(n):
    '''Sieve of Eratosthenes as suggested in Hans Riesel's Book'''
    ilist=[k for k in range(3,n+1,2)] #list of odds

    index=0
    p=3
    while p*p <= 2*((n-1)//2):   #only looking at oddsmax(P):
            return []
        if sum(P[k:k+length]) in P:
            return [P[k:k+length],sum(P[k:k+length])]
    return []

length=546  #I checked that 2+3+...+P_546 exceeds one million
while not is_sum(length):
    length-=1

answer=is_sum(length)
psum,firstp=answer[1],answer[0][0]
print(psum," is the sum of ",length," consecutive primes")
print("starting with ",firstp)

Usuário: skasch
def prm(n):
    f = [True] * n
    for i in [2] + list(range(3, int(sqrt(n)) + 1, 2)):
        if f[i]:
            f[2*i::i] = [False] * len(f[2*i::i])
    return [i for i in [2] + list(range(3, n+1, 2)) if f[i]]
N = 1000000
ps = prm(N)
n = 0
ms = 0
while ms < N:
    ms += ps[n]
    n += 1
r = 0
for l in reversed(range(n)):
    i0 = 0
    while sum(ps[i0:i0+l]) < N:
        if sum(ps[i0:i0+l]) in ps:
            r = sum(ps[i0:i0+l])
        i0 += 1
    if r != 0:
        break
print(r)

domingo, 8 de maio de 2016

Resposta do problema 002 - Even Fibonacci numbers

O segundo problema do site project euler faz uso da série de Fibonacci. O problema pede para somarmos todos os números da série de fibonacci menores que 4000000 (milhoes) e que sejam par.
Even Fibonacci numbers
Problem 2
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
Link para a pergunta: https://projecteuler.net/problem=2

Criei o seguinte script para resolver o problema:
a = 1
b = 1
x = 0
soma = 0
while x < 4000000:
 x = a + b
 if x % 2 == 0:
  soma = soma + x
 a = b
 b = x

print(soma)

Resposta (answer) - Selecione a linha abaixo para ver a resposta
4613732

Outras respostas postadas por outros usuários no fórum:
Usuário bael2212
class FibSequence:
    def __init__(self, endp):
        self.end = endp

    def fibtomax(self):
        fib1 = 0
        fib2 = 1
        fib3 = fib1 + fib2
        fibseq = []
        fibseq.append(fib3)
        fibeven = []
        while fib3 < self.end:
            fib1 = fib2
            fib2 = fib3
            fib3 = fib1 + fib2
            fibseq.append(fib3)
        for i in fibseq:
            if i % 2 == 0:
                fibeven.append(i)
        return sum(fibeven)

obj2 = FibSequence(4000000)

print obj2.fibtomax()

Usuário KaiserK
def even_fibs(n):
    a, b = 0, 2

    while b < n:
        a, b = b, 4 * b + a

    return (a + b - 2) / 4


print even_fibs(4000000)

Resposta do problema 001 - Multiples of 3 and 5

O primeiro problema do site é bem simples e acredito que muitos já fizeram scripts parecidos na faculdade. O objetivo é retornar a soma de todos os números múltiplos de 3 ou 5.
Multiples of 3 and 5
Problem 1
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
Link para a pergunta: https://projecteuler.net/problem=1

Script que criei para resolver o problema:
soma = 0
for x in range(1,1000):
 if (x % 3 == 0) or (x % 5 == 0):
  soma += x

print(soma)

Resposta (answer) - Selecione a linha abaixo para ver a resposta
233168

Devemos ficar atento a condição que indica que o número pode ser divisível por 3 OU 5.
A instrução "for x in range(1,1000)" faz como se fosse um while e a variável x vai tendo seu valor acrescido em 1 cada vez que faz o loop até o número 999. Quando atinge o valor 1000 o programa sai do loop. Se eu desejasse que o número 1000 também entrasse na verificação eu teria que escrever a instrução for da seguinte forma: "for x in range(1,1001)".

Segue abaixo algumas outras soluções postadas no fórum:
Usuário: galah92
def serie_sum(diff, limit):
    """ Calculate the sum of all numbers divisible by diff up to the limit.
    Using the formula of sum of an arithemetic serie (Sn = n*(A1+An)/2),
    and the formula to the An term (An = A1+(n-1)*d), combinig our special case
    where A1 = d, we can derive our expression.
    Args:
        diff: plays as 'A1' and 'd' according to the formula.
        limit: biggest 'An' to reach to according to the formula.
    Returns:
        The sum of all numbers divisible by diff up to the limit.
    """
    return diff * (limit/diff) * (limit/diff+1) / 2

def main():
    """ If we calculate the sum of the numbers divisible by 3 and 5 seperately,
    we need to substract the sum of the numbers that are divisible by 15
    because they were counted twice.
    """
    print serie_sum(3, 999) + serie_sum(5, 999) - serie_sum(15, 999)

if __name__ == '__main__':
    main()

Resposta em uma linha do usuário cromod
print sum( val for val in range(1000) if val%3==0 or val%5==0 )