Blog
Implementação do jogo liga4
Este é um trabalho que usamos a muito tempo atrás na disciplina ci208 - Programação de Computadores. Ainda é muito útil para ser usada como exercício para os mais ambiciosos.
O Jogo
O jogo "Liga 4" é um jogo para dois jogadores, formado por um tabuleiro de N linhas por M colunas. Cada jogador é representado por um determinado caracter ('0' para o jogador 1, e 'X' para o jogador 2). O objetivo do jogo é que cada jogador consiga arranjar seus caracteres em uma sequência de 4. Linhas horizontais, verticais ou diagonais são válidas. O jogador pode apenas escolher uma coluna para posicionar seu caracter sendo ela então colocada na sua primeira posição livre (de baixo para cima). Ou seja, caso exista um ou mais caracteres na coluna em que ele escolher, o caracter só poderá ser posto na posição livre subsequente. Se não houver espaço nesta coluna, ela não pode ser escolhida pelo jogador. As jogadas são feitas de maneira alternada entre o jogador um e o jogador dois. Ao mesmo tempo que um jogador tenta alcançar seu objetivo, ele pode atrapalhar as jogadas do seu oponente. Vence aquele que primeiro alcançar o objetivo de colocar seus 4 caracteres em sequencia.
Implementação
Assumiremos que o jogador 1 começa a partida. O jogo consiste basicamente nos seguintes passos:
- Mostrar uma representação do tabuleiro na tela;
- Pedir ao jogador 1 a coluna em que ele quer jogar sua peça. Se o jogador escolher uma coluna inválida (cheia) o programa deve emitir uma mensagem de erro correspondente e pedir novamente a jogada até que ela seja válida;
- Repetir o processo para o segundo jogador e assim sucessivamente.
O programa deve ser encerrado ao encontrar um vencedor ou até que não existam mais posições livres no tabuleiro, indicando um empate. O trabalho deverá ser implementado utilizando necessariamente um conjunto de funções especificadas a seguir. A saída gerada pelo programa, assim como sua entrada de dados, devem também obedecer rigorosamente o padrão estabelecido nesta especificação.
Interface
A saída do programa consiste de:
- Representação do tabuleiro;
- Mensagem indicando a vez de qual jogador (1 ou 2);
- Mensagem indicando se uma jogada é inválida;
- Mensagem indicando um vencedor.
A impressão de um tabuleiro vazio deverá ser representada da seguinte maneira:
1 2 3 4 5 6 7
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
Um tabuleiro preenchido após quatro rodadas poderia ter o seguinte layout visual:
1 2 3 4 5 6 7
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . X . . . .
. . 0 X 0 . .
A cada rodada, o programa deve indicar de qual jogador é a vez, para que ele forneça o valor (inteiro) de uma coluna. A seguinte mensagem deverá ser gerada como saída, caso seja a vez do jogador 1:
1>
Ou então, caso seja a vez do jogador 2:
2>
No caso do jogador fornecer uma coluna inválida (fora da faixa válida) ou uma coluna já totalmente preenchida, o programa deve mostrar a seguinte mensagem:
Jogada inválida.
Lembrando que a única entrada do programa é a coluna escolhida pelo jogador a cada rodada. Ao encontrar um vencedor, o programa deve mostrar a seguinte mensagem antes de ser encerrado, caso o jogador 1 tenha ganho a partida:
Fim! 1
Ou então, caso o jogador 2 tenha ganhado:
Fim! 2
Ou ainda, no caso que não haja vencedor:
Empate!
Especificação das Funções
O trabalho terá que obrigatoriamente utilizar funções na solução do problema. Todas as funções devem trabalhar com um tabuleiro cujas dimensões são definidas pelas seguintes constantes:
#define N 6
#define M 7
Sendo N o número de linhas e M o de colunas. O programa deve funcionar se for escolhido para N e M qualquer outro valor inteiro positivo. As seguintes funções devem ser implementadas:
void escreveTabuleiro(int tab[N][M]);
- Mostra o tabuleiro tab na tela.
int ganhou(int tab[N][M], int jogador);
- Verifica se o jogador no tabuleiro tab ganhou. Devolve 1 se o jogador ganhou, devolve 0 caso contrário.
int estaCheio(int tab[N][M]);
- Verifica se o tabuleiro tab esta cheio. Devolve 1 se verdadeiro ou 0 caso contrário.
int leJogada(int jogador);
- Recebe como parâmetro jogador que pode ser 1 ou 2, lê a jogada e devolve a coluna escolhida.
void executaJogada(int tab[N][M], int jogador, int coluna);
- Executa a jogada indicada por coluna do jogador jogador no tabuleiro tab. (Preenche a posição correspondente da matriz).
int jogadaValida(int tab[N][M],int coluna);
- A partir de coluna escolhida pelo jogador, devolve 1 caso a jogada sejá válida no tabuleiro tab ou devolve 0 caso seja inválida.
programação
liga4, programação, c, c++
Algebra linear usando SciPy
O módulo de álgebra linear em SciPy é basicamente uma interface para funções implementadas em C e Fortran, como o ATLAS e o LAPACK, mas com a conveniência de estarem acessíveis em python.
Abaixo seguem alguns exemplos do que esta biblioteca fornece.
O tipo matrix
O tipo matrix é fornecido pela biblioteca NumPy e é usado para representar uma matriz. É mais conveniente usar este tipo do que o tipo array, já visto em um post anterior, por que os operadores se comportam da forma que se espera em uma matriz. Por exemplo, o operador de multiplicação entre dois arrays faz a multiplicação elemento a elemento, que não é o que se espera de uma matriz. Veja o exemplo abaixo:
>>> A=array([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])
>>> B=array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
>>> A*B
[[-1.48 0. 0. ]
[ 0. 0.08 -0. ]
[ 0. -0. 0.04]]
>>> A=matrix([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])
>>> B=matrix([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
>>> A*B
[[-1.48 0.36 0.88]
[ 0.56 0.08 -0.36]
[ 0.16 -0.12 0.04]]
Muitas das funções em algebra linear aceitam uma variedade de formatos, inclusive lista, matrix e array, que depois são convertidos internamente. Porém em alguns casos é importante saber a diferença entre eles.
Operações básicas sobre matrizes
Antes de mais nada, você deve importar o módulo "linalg"
>>> from scipy import linalg
A inversa de uma matriz pode ser calculada de duas formas, usar "A.I" se A for um variável do tipo matrix ou então usar linalg.inv() conforme exemplo abaixo.
>>> A=matrix([[1, 3, 5],
[2, 5, 1],
[2, 3, 8]])
>> A.I
matrix([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])
>>> from scipy import linalg
>>> linalg.inv(A)
array([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])
Repare que um sistema linear no formato A.X = b pode ser resolvido multiplicando a inversa de A por b. Porém recomenda-se que seja usada a função linalg.solve() que é menos suscetível a erros de arredondamento e é geralmente mais rápida.
>>> A = matrix([[1, 3, 5], [2, 5, 1], [2, 3, 8]])
>>> b =matrix([[10],
[ 8],
[ 3]])
>>> A.I*b
matrix([[-9.28],
[ 5.16],
[ 0.76]])
>>> from scipy import linalg
>>> linalg.solve(A,b)
array([[-9.28],
[ 5.16],
[ 0.76]])
Para achar o determinante de uma matriz basta usar a função linalg.det(). Veja exemplo usando a mesma matriz A definida acima:
>>> linalg.det(A)
-25.000000000000004
A biblioteca possui também vários métodos para decompor uma matriz. Por exemplo, para efetuar a fatoração LU vista em sala de aula, basta fazer
>>> linalg.lu(A)
(array([[ 0., 0., 1.],
[ 1., 0., 0.],
[ 0., 1., 0.]]),
array([[ 1. , 0. , 0. ],
[ 1. , 1. , 0. ],
[ 0.5 , -0.25, 1. ]]),
array([[ 2. , 5. , 1. ],
[ 0. , -2. , 7. ],
[ 0. , 0. , 6.25]]))
Que devolve uma matriz informando os elementos que foram permutados devido ao pivoteamento, a matriz L e a matriz U.
Referências
métodos numéricos
algebra linear, sistema linear, matriz
Introdução à biblioteca NumPy
Numpy é um pacote básico para computação científica em python. Sua principal utilidade é uma classe array multi-dimensional que permite cálculos vetorias, como os usados em algebra linear.
Por que usar a classe array se eu posso utilizar listas?
Imagine um exemplo simples, que é multiplicar cada elemento de uma sequência com o elemento de outra sequência e armazená-lo em uma terceira. Isto pode ser feito de formar simples usando listas assim:
c = []
for i in range(len(a)):
c.append(a[i]*b[i])
O que fornece a resposta certa, porém com um preço na performance, como acontece em quase toda linguagem interpretada (quem já implementou uma lista em C sabe que acontece muitas outras coisas por baixo do pano quando se utiliza listas). Em um teste com 10 milhões de elementos o código foi executado em 7 segundos. Uma versão mais rápida pode ser implementada em C:
for (i = 0; i < colunas; i++): {
c[i] = a[i]*b[i];
}
Um teste no meu computador executou em 300 ms. Repare que o código em C fica mais tedioso de escrever quando existem múltiplas dimensões:
for (i = 0; i < linhas; i++): {
for (j = 0; j < colunas; j++): {
c[i][j] = a[i][j]*b[i][j];
}
}
Usando a biblioteca numpy escrevemos nos dois casos simplesmente
c = a * b
Cuja performance é equivalente a C. Ou seja, o melhor dos dois mundos: performance de C e fácil de escrever e ler como python. Isto é possível por que um array em padrão em numpy é homogêneo (aceita apenas um tipo) e por que muito de sua implementação é feita por baixo dos panos com código pré-compilado de C.
Este último exemplo ilustra dois aspectos importantes da biblioteca: vetorização e broadcasting.
Vetorização descreve a ausência de qualquer tipo de loop e indexação no código, tornando o código mais fácil de ler, lembrando a notação matemática. Broadcasting é o termo que descreve o comportamento das operações envolvendo vetores. As operações são aplicadas elemento a elemento de forma implícita. Todas os operadores na biblioteca NumPy funcionam desta maneira. No exemplo acima, a e b poderiam ser vetores multidimensionais, um deles poderia ser um escalar ou mesmo vetores de tamanho diferentes.
Criando arrays
Arrays podem ser criados de outras sequências em python (por exemplo, listas).
>>> import numpy
>>> x = numpy.array([2, 3, 1, 0])
>>> y = numpy.array([[5., 6., 7., 8.], [2., 3., 1., 0.]])
>>> print y
[[ 5. 6. 7. 8.]
[ 2. 3. 1. 0.]]
Existem outras funções que podem ser úteis para criação de arrays
>>> numpy.zeros((2,3)) # matriz 2x3 preenchida com 0's
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
>>> numpy.ones((2,3)) # matriz 2x3 preenchida com 1's
array([[ 1., 1., 1.],
[ 1., 1., 1.]])
>>> numpy.arange(10) # array com elementos de 0 a 9
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.arange(2, 3, 0.1) # elementos de 2 a 3, com incremento de 0.1
array([ 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9])
>>> np.linspace(1., 4., 6) # 6 elementos igualmente espaçados entre 1 e 4
array([ 1. , 1.6, 2.2, 2.8, 3.4, 4. ])
Atributos
Um array é um objeto, e como tanto, possui alguns atributos podem ser interessantes. Por exemplo: array.shape mostra a dimensão do array, array.size mostra o número de elementos que o array contém e array.ndim mostra o número de dimensões.
>>> a=numpy.ones((2,3))
>>> a.ndim
2
>>> a.shape
(2, 3)
>>> a.size
6
Broadcasting
Como mencionado antes, os operadores matemáticos são aplicados em todos os elementos do array. No caso mais simples, os dois arrays tem a mesma dimensão.
>>> a = numpy.array([1.0, 2.0, 3.0])
>>> b = numpy.array([2.0, 2.0, 2.0])
>>> a * b
array([ 2., 4., 6.])
Outro caso é quando um dos operandos é um escalar.
>>> a = numpy.array([1.0, 2.0, 3.0])
>>> b = 2.0
>>> a * b
array([ 2., 4., 6.])
NumPy redefine todas as funções disponíveis na biblioteca de matemática, logo, o mesmo pode ser feito com estas funções:
>>> a = numpy.linspace(0., 1., 5)
>>> a
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
>>> numpy.sin(a)
array([ 0. , 0.24740396, 0.47942554, 0.68163876, 0.84147098])
A regra funciona inclusive para arrays de tamanhos diferentes. Consulte a documentação para ver as regras de broadcasting.
Referências
- NumPy user guide - http://docs.scipy.org/doc/numpy/user/
- Tentative NumPy Tutorial - http://www.scipy.org/Tentative_NumPy_Tutorial
métodos numéricos
python, numpy, array
Tipos compostos em python
Python possui diversos tipos compostos, mas para os nossos propósitos o mais útil é a "lista". Uma lista pode ser criada escrevendo uma lista de valores entre colchetes. Os elementos da lista não precisam ter todos o mesmo tipo.
>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234]
Assim como strings, elementos devem ser acessados a partir do índice 0, podem ser concatenadas, repetidas e fatiadas.
>>> a = ['spam', 'eggs', 100, 1234]
>>> b = a + ['bacon', 'batatinha']
>>> b
['spam', 'eggs', 100, 1234, 'bacon', 'batatinha']
>>> a[1]
'eggs'
>>> b*2
['spam', 'eggs', 100, 1234, 'bacon', 'batatinha', 'spam', 'eggs', 100, 1234, 'bacon', 'batatinha']
>>> b[1:3] # lista formada pelos elementos 1 até 3 (exclusive)
['eggs', 100]
>> len(b) # a função len() devolve o tamanho de uma lista
6
Para acrescentar um elemento a uma lista, podemos usar o método "append"
>>> a.append(2.0)
>>> a
['spam', 'eggs', 100, 1234, 2.0]
Listas também podem conter outras listas (para representar, por exemplo, uma amatriz).
>>> matriz = [ [1, 2, 3], [4, 5, 6]]
>>> matriz
[[1, 2, 3], [4, 5, 6]]
>>> matriz[1][2]
6
A lista contém um monte de métodos úteis, consulte o tutorial: http://docs.python.org/tutorial/datastructures.html#more-on-lists
>>> a = [66.25, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
O comando for
Em python, o comando for é um pouco diferente do usado em pascal e C, que basicamente iteragem sobre uma progressão aritmética. Em python, o comando for é usado para executar iterações sobre cada elementos de uma sequência (por exemplo, uma lista). Por exemplo:
>>> a = ['cat', 'window', 'defenestrate']
>>> for x in a:
... print x, len(x)
...
cat 3
window 6
defenestrate 12
A função range()
A função range() é útil se você precisa de uma progressão aritmética como em C. Por exemplo:
>>> range(10) # lista com inteiros de 0 a 10 (exclusive)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(5,10) # lista com inteiros de 5 a 10 (exclusive)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3) # lista com inteiros de 0 a 10 (exclusive)
[0, 3, 6, 9]
Logo, é útil misturar a função range() com o comando for. Por exemplo: veja um código para mostrar todos os números divisíveis por 13 entre 0 e 999.
>>> for i in range(1000):
... if i % 13 == 0:
... print i
Outros comandos úteis:
-
break- como em C, interrompe o laço mais interno do while ou for -
continue- como em C, passa para a próxima iteração do loop
Programação Funcional
Em ciência da computação, programação funcional é um paradigma que trata computação como a avaliação de funções matemáticas e evita o uso de variáveis mutáveis, ao contrário do paradigma imperativo, ensinado em ci208.
Em português, isso significa que se usa funções e expressões para tudo.
Python pegou emprestado alguns conceitos de linguagens que eram tipicamente funcionais, o que possibilita códigos bem enxutos. Três funções exemplificam bem isso: filter(), map() e reduce().
A função filter(f, seq) devolve uma lista com itens cujo valor de f(item) é verdadeiro. Por exemplo, para recuperar os números primos até 25:
>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
A função map(f, seq) devolve uma lista aplicando f() em cada item. Por exemplo, para calcular x^3 para números de 1 a 10:
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
A função reduce(f, seq) devolve um único valor aplicando a função f(item1, item2) nos dois primeiros itens, em seguida aplicando o resultado com o item seguinte e assim por diante. Por exemplo, para somar todos os números de 1 a 10:
>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55
Outros tipos compostos
Python suporta outros tipos compostos, como tuplas, conjuntos e dicionários que podem ser úteis em outros casos, como manipulação de texto. Recomendo o tutorial completo em http://docs.python.org/tutorial/datastructures.html.
Referências
- http://docs.python.org/tutorial/introduction.html#lists
- http://docs.python.org/tutorial/datastructures.html#more-on-lists
métodos numéricos
python, listas
Zero de funções usando SciPy
A biblioteca SciPy já fornece algoritmos prontos para determinar o zero de uma função. Estas funções se encontram dentro do módulo scipy.optimize que contém, além de algoritmos para resolução de zero de funções, outras funções de otimização como ajuste de curvas, minimizar uma função, etc.
Por exemplo, o método da bisseção pode ser chamado assim:
>>> from scipy.optimize import bisect
>>> def f(x): return x**2 - 1000
...
>>> bisect(f, 0, 1000)
31.622776601683888
Consulte a documentação para ver outros parâmetros aceitos pela função
Já no método de newton, você pode passar a derivada da função para achar mais rapidamente a raiz. Se você omiti-la, é utilizado o método das tangentes:
>>> from scipy.optimize import newton
>>> def f(x): return x**2 - 1000
...
>>> def f_linha(x): return 2*x
...
>>> newton(f, 0, f_linha)
Warning: zero-derivative encountered.
0
>>> newton(f, 1.0, f_linha)
31.622776601683793
Consulte a documentação para ver outros parâmetros opcionais.
Referências
métodos numéricos
zero de função, newton, tangente
Módulos em python
Se você sai do interpretador python e entra novamente, todas as funções e variáveis que você definiu são perdidas. Ou seja, se você quer escrever um programa que deseja "reaproveitar" depois, o melhor é criar um arquivo usando algum editor de texto, e depois chamá-lo dentro do interpretador. É natural um programa crescer em tamanho e você desejar separá-lo em arquivos para uma melhor organização.
Para isso, python tem um suporte a módulos, que basicamente são arquivos que contém definições de funções, classes ou mesmo código executável. Um módulo pode ser "importado" dentro de outro módulo ou dentro do próprio interpretador, para se ter acesso ao que foi definido dentro do arquivo.
Por exemplo, você pode usar seu editor de textos favoritos para criar o arquivo fibo.py com o seguinte conteúdo:
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
Dentro do interpretador python, você pode importar este módulo usando o seguinte comando (desde que ele esteja no mesmo diretório de trabalho):
>>> import fibo
Com isso, você tem acesso as funções definidas dentro de fibo, usando a seguinte sintaxe:
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Repare que você deve colocar o nome do módulo na frente do nome da função, isso é útil para evitar colisão de nomes com funções definidas em outros módulos. Porém, se você acha isso muito tedioso você pode importar o nome das funções diretamente para o módulo em que você esta trabalhando (ou dentro do próprio interpretador).
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
Ou ainda, usando o atalho para importar tudo o que o módulo define:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
Geralmente os desenvolvedores fazem cara feia quando veem um "import *" por que o código fica mais difícil de ler. Mas se você esta trabalhando sozinho dentro do interpretador é só não contar pra ninguém que você faz isso.
Python contém uma série de módulos padrões com funções úteis, basta importá-los. Python contém módulos para acesso a funções do sistema operacional, biblioteca de matemática, casamento de padrões em strings, manipulação de datas, etc. Veja por exemplo:
>>> import sys
>>> sys.ps1 = 'Digite seu comando: '
Digite seu comando: import math
Digite seu comando: print math.pi/2.0
1.57079632679
Referências
métodos numéricos
módulos, python
Strings e funções em python
Introdução a orientação a objetos
Programação orientada a objetos era considerado um tema avançado a 15 anos atrás. Mas hoje é praticamente regra em linguagens de programação modernas, de forma que é quase impossível ensinar conceitos simples da linguagem sem ter que mencionar algum conceito de orientação a objetos.
Vamos comparar um exemplo com a linguagem C. Uma string em C nada mais é que um vetor de caracteres que pode ser tratado como qualquer outro vetor, porém a biblioteca string.h define algumas funções interessantes, por exemplo, para encontrar a primeira ocorrência de um caracter 'x' dentro de um string escrevemos:
strchr(str, 'x')
Ou seja, str é apenas um dado. O que pode ser feito com esse dado é definido pelo conjunto de funções em string.h. Em orientação a objetos, uma variável pode ser mais do que somente dados, ele agrega também as funções para operar em cima deste dado. Chamamos a isso de "objeto", e as funções que operam com os dados desse objeto chamamos de método.
Por exemplo, em python, escreveríamos o mesmo assim:
string.find('x')
Significa que estamos chamando o método "find" do objeto string.
Assim como um dado possui um tipo, como float ou int, um objeto também possui. Nesse caso é mais comum chamarmos esse tipo de "classe". Uma classe define todos os dados e métodos que um objeto terá quando for criado. Alguns exemplos de classe em python incluem a própria string, listas, dicionários, entre outros. Cada uma dessas classes definem métodos como "inserir um elemento", "remover um elemento", "ordenar", etc. É possível também definir suas próprias classes, mas não veremos isto neste curso.
Strings
Comparado com C, trabalhar com strins em python é fácil. Para definir uma string literal basta colocar o texto entre aspas simples ou duplas.
string = "batatinha"
Do mesmo jeito que C, a string pode ser acessada como se fosse um vetor:
print string[0] # mostra o valor "b"
Ao contrário de C, podemos usar os operadores relacionais como ==, <, <=, > e >= exatamente como fazemos com números.
print "aaaa" < "bbbb" # mostra o valor True
Podemos usar o operador "+" para concatenar (juntar) duas strings, e o operador "*" para repetir a string.
print "hua"*10+"!"*3 # mostra huahuahuahuahuahuahuahuahuahua!!!
Além disso, a string possui vários outros métodos, basta consultar a documentação da biblioteca de python: http://docs.python.org/library/string.html
Funções
Veja por exemplo, uma função que mostra a série de fibonacci até o número n
def fib(n):
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
A palavra chave "def" define uma nova função. É seguida pelo nome desta função e por seus argumentos, se eles existirem. Repare que não é necessário informar o tipo de retorno da função, do mesmo jeito que era necessário em C.
No bloco de código abaixo, é definido o corpo da função. Existe um comando estranho no corpo da função que merece uma explicação melhor: a atribuição múltipla. Por exemplo, na linha:
a, b = b, a+b
A atribuição múltipla evita que usemos muitas variáveis auxiliares. No comando acima, a recebe o valor de b, e b recebe o valor de a+b, a diferença é que o valor b e a+b é avaliado antes de ser feita a atribuição, então não precisamos nos preocupar com o valor de a ser sobrescrito antes de ser usado. É como se as duas atribuições fossem feitas ao mesmo tempo.
Para chamar a função, fazemos da mesma maneira que em C, apenas usamos o nome da função seguida de seus argumentos entre parênteses:
fib(2000) #mostra 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
É mais comum escrever uma função que devolve algum valor ao invés de simplesmente mostrar um valor na tela. Usamos aqui o comando return do mesmo jeito que na linguagem C. Por exemplo:
def fib2(n):
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
Aqui estamos criando uma lista chamada "result", adicionando um valor nesta lista a cada iteração (result.append(b)) e devolvendo o resultado. Uma lista é parecida com um vetor em C, mas podemos colocar uma quantidade arbitrária de elementos, inclusive de tipos diferentes. Este assunto será visto com mais detalhes mais para frente.
Em python, é possível também definir valores padrões para argumentos de uma função. Vamos supor uma função que pergunte para o usuário algo que se espere um "sim/não" como resposta, e repetindo a pergunta caso o usuário digite algo diferente de "S" ou "N". Podemos escrever assim:
def pergunta_ok(pergunta, tentativas=4, reclamacao='S ou N por favor!'):
while True:
ok = raw_input(pergunta)
if ok == 'S':
return True
if ok == 'N':
return False
tentativas = tentativas - 1
if tentativas < 0: return False
print reclamacao
No corpo da função aparecem duas constantes novas: True e False. Ao contrário de C, que utiliza sempre números, essas constantes representam o valor verdadeiro e falso, respectivamente.
A função pergunta_ok pode ser chamada de várias formas. A mais simples seria algo como pergunta_ok("Posso continuar?"). Neste caso, o número de tentativas será igual a 4 e a reclamação que será mostrada na tela será "S ou N por valor!". Isto pode ser alterado, escrevendo por exemplo:
pergunta_ok("Posso continuar?", 5, "Digite apenas S ou N, seu noob")
ou
pergunta_ok("Posso continuar?", 6)
Em python, uma função se comporta exatamente como uma variável. Por exemplo, podemos atribuir a outra variável a função fib2.
f = fib2 print f(2000) #mostra [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
Isso é útil pois é possível passar uma função com argumento para outra função. Por exemplo, podemos definir uma função que execute uma iteração do método de Newton-Raphson, mas para isso é necessário conhecer antes a aproximação inicial, a função e a sua derivada.
def newton(x0, f, derivada_f):
return x0 - f(x0)/derivada_f(x0)
E podemos chamar a função da seguinte maneira, definindo as funções que serão usadas como parâmetro antes:
def minha_funcao(x):
return x*x - 2.0
def deriv_minha_funcao(x):
return 2*x;
print newton(1.0, minha_funcao, deriv_minha_funcao)
De maneira análoga, podemos chamar com qualquer outra função.
Referências:
- http://docs.python.org/tutorial/introduction.html#strings
- http://docs.python.org/tutorial/controlflow.html#defining-functions
métodos numéricos
ci202, métodos numéricos, python, string, funções
Introdução à linguagem python
Este é o primeiro de uma série de artigos para apresentação da linguagem python. O objetivo é apresentar o mínimo necessário para que seja possível resolver problemas úteis. Se você precisar de maiores detalhes, existem excelentes tutorias e livros para aprofundar o assunto. Todos esses artigos assumem que você conhece um mínimo da linguagem C e já entende como construir um algoritmo. Se você não lembra muita coisa da linguagem C, pode começar com este tutorial. A melhor forma para ler e entender este texto é primeiramente instalando o interpretador e executando os códigos a medida que eles são apresentados.
Por que python?
Python é uma linguagem interpretada de alto nível. O que isso significa? Interpretada é por que ela não precisa de um compilador. Ou seja, enquanto o processo de desenvolvimento em C era editar -> compilar -> executar em python só precisamos editar -> interpretar, tornando o processo de desenvolvimento um pouco mais rápido. Onde antes precisavamos de um compilador agora precisamos de um interpretador para permitir que o programa seja executado diretamente a partir de seu código fonte. Dizemos "alto nível" pois a linguagem esconde detalhes de implementação de uma forma mais abrangente do que uma linguagem de baixo nível como C.
Python tem se tornado popular na área de computação científica talvez devido a facilidade com que é possível invocar funções escritas em outras linguagens como C ou Fortran, ou talvez devido a grande quantidade de bibliotecas disponíveis. Existem softwares específicos para trabalhar com a área científica que tem uma tradição muito maior (por exemplo, o MatLab) mas uma vantagem de python é que, por ser uma linguagem de propósito geral, pode ser utilizada para automatizar praticamente qualquer tarefa do computador e não apenas aquelas que envolvem computação científica.
O ambiente
Existem duas maneiras de executar código em python: você pode digitar e executar diretamente a partir do interpretador ou salvar o programa em um arquivo para depois executá-lo. Se você esta em ambiente linux, para chamar o interpretar basta digitar:
ipython -pylab
O "-pylab" é desnecessário se você não for utilizar a biblioteca SciPy, tornando o carregamento inicial mais rápido. Se você quer salvar o programa em um arquivo o processo é simples, basta salvar o programa com extensão .py (por exemplo: meu_programa.py) e executar
python meu_programa.py
Se você esta em ambiente windows, você pode abrir o interpretador entrando em Iniciar -> Programas -> EPD32-6.1 -> PyLab (IPython). Se você deseja salvar o programa em um arquivo, a maneira mais simples é abrir o ambiente de desenvolvimento IDLE entrando em Iniciar -> Programas -> EPD32-6.1 -> IDLE. Isto também abrirá um interpretador que irá aceitar diretamente comandos Python, mas para criar um arquivo você deve entrar em "File -> New window" para então digitar seu programa. Não esqueça de salvá-lo com extensão ".py". Para executá-lo, basta apertar F5 ou entrar em Run -> Run module. Repare que sempre haverão duas janelas abertas, uma para o editor e outra para o interpretador, onde o programa poderá ser testado.
Dentro do interpretador IDLE existem dois atalhos que eu não conseguiria viver sem: alt+p (Previous) mostra o comando digitado anteriormente e alt+n (Next) volta para o próximo comando no histórico. No PyLab estes comandos são mais fáceis de serem acessados, basta usar a flecha para cima e para baixo do teclado.
Comando de saída
Se você já esta familiarizado com o ambiente, podemos começar com os comandos básicos. A maneira mais simples de mostrar algo na tela é usando o comando "print". Por exemplo, se em C você escreveria:
#include <stdio.h>
int main(void)
{
printf("Ola Mundo!\n");
return 0;
}
Em python você escreve:
print "Ola Mundo"
Repare que não existe um "esqueleto mínimo" para um programa em python, que não é mais necessário ponto-e-vírgula ao terminar um comando e que por padrão o comando print pula linha ao final do texto, sendo desnecessário o "\n". Se você não deseja pular linha (isto pode ser necessário em alguns exercícios) basta terminar o comando print com vírgula.
print "Este texto ",
print "vai ficar grudado com esse"
print "E esse vai aparecer em uma linha por si só"
Variáveis e o comando de atribuição
Uma das características mais marcantes das linguagens interpretadas é que as variáveis não precisam ser declaradas e elas não possuem tipo (int, float, etc.). Se você precisa utilizar uma variável, basta atribuir um valor a ela, usando o mesmo comando em C, que é o operador "=". Exemplo:
x=2
y=3
print "O valor de x é ", x, "e o valor de y é ", y
Nas duas primeiras linha criamos duas variáveis chamadas x e y, apenas atribuindo um inteiro a elas. Na linha seguinte mostramos o seu valor. Aqui podemos ver que o comando print pode ser usado para mostrar tanto um texto quanto uma variável, basta separar os argumentos por vírgula e usar quantos argumentos forem necessários. Note também que não foi necessário dizer que x e y eram variáveis inteiras. O que possui tipo em python é o valor da variável e não a variável em sí. Nada impede de fazermos:
x = 2
print x
x = "batatinha"
print x
x = 2.5
print x
No primeiro print, x era um inteiro, no segundo uma string, e no terceiro um float. A título de curiosidade, variáveis inteiras em python são armazenadas usando o tipo "int" em C e variáveis em ponto flutuante são equivalentes ao tipo "double".
Na atribuição e em qualquer lugar que se espera um valor numérico podemos usar expressões aritméticas do mesmo jeito que utilizamos em C. Por exemplo:
y = 1
x = 2*(y+1)
Se você esta dentro de um interpretador python e deseja inspecionar o valor de uma variável, não é necessário usar o comando print, basta digitar o nome da variável e apertar <enter>.
Entrada
A maneira mais simples de ler algo informado pelo usuário é utilizando a função raw_input(). O formato dela é:
raw_input([mensagem])
Onde mensagem é a mensagem que será mostrada para o usuário antes de pedir o valor. O colchetes que coloquei acima é apenas para denotar que este parâmetro é opcional.
Exemplo:
eco = raw_input("Usuário, digite algo por favor: ");
print eco
Infelizmente esta função apenas devolve uma string. Uma string é apenas uma concatenação de carateres e com ele não podemos fazer cálculos aritméticos. Repare que nosso velho amigo scanf() em C fazia isso automaticamente quando informávamos o tipo da variável a ser lida (%f ou %d, por exemplo). Em python, podemos simular o mesmo comportamento usando o comando int() e float() que fazem essa conversão.
x = int(raw_input())
y = float(raw_input())
print "x = ", x
print "y = ", y
No exemplo acima, x é um inteiro e y um float. Se o usuário digitar algo que o interpretador não puder converter (por exemplo, 'blabla') o programa irá falhar com uma mensagem de erro:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'blabla'
Neste curso vamos confiar no usuário e não veremos como resolver esse problema. Se o assunto lhe interessar, pesquise por tratamento de exceções (Exception Handling) no manual da linguagem python.
Comando condicional
Vamos apresentar através de um exemplo. Considere o problema de ler uma nota e saber se o aluno esta aprovado, em final ou reprovado. Em C escreveríamos:
#include <stdio.h>
int main(void)
{
float nota;
scanf("%f", ¬a);
if (nota >= 70)
{
printf("Aprovador\n");
}
else if (nota < 70 && nota >= 40)
{
printf("Final\n");
}
else
{
printf("Reprovado\n");
}
return 0;
}
Em python escrevemos:
nota = float(raw_input())
if nota >= 70:
print "Aprovado"
elif nota < 70 and nota >= 40:
print "Final"
else:
print "Reprovado"
Em C um bloco é denotado por chaves. Alguns comandos pedem um bloco de comandos, como o comando if e o comando while. O mesmo conceito também existe em python mas é escrito de forma diferente. O comando if pede um bloco, e fazemos isso colocando ":" no final do comando e acrescentando espaços na frente dos comandos do bloco correspondente. Chamamos isso de identação e em python isso é obrigatório. Para informar o fim do bloco, basta remover a identação. Geralmente se utiliza 4 espaços para isso. Repare que se você esta usando um editor de textos decente ele percebe que você esta criando um bloco e coloca os espaços automaticamente.
Mais três pequenos detalhes: não é necessário o parênteses na condição do while, como era necessário em C. Para escrever o operador "E" lógico usamos a palavra "and" ao invés de "&&". De maneira análoga, usamos "or" e "not" ao invés de "||" e "!". Por último, o comando "else if" é tão comum que foi abreviado como "elif".
O comando de repetição
Considere o seguinte problema: escreva um programa que mostre todos os números de 1 a 100 e ao final mostre a soma destes números. Em C isso seria:
#include <stdio.h>
int main(void)
{
int contador;
int soma;
contador = 1;
soma = 0;
while (contador <= 100)
{
printf("%d\n", contador);
soma = soma + contador;
contador = contador + 1;
}
printf("%d\n", soma);
return 0;
}
Em python o comando é quase indêntico, respeitando a definição de bloco de comandos e a não obrigatoriedade dos parênteses.
contador = 1
soma = 0
while contador <= 100:
print contador
soma = soma + contador
contador = contador + 1
print soma
É possível escrever de forma ainda mais enxuta, já apresentando o operador de soma e atribuição:
contador = 1
soma = 0
while contador <= 100:
print contador
soma += contador
contador += 1
print soma
Da mesma forma, temos o operador de subtração e atribuição "-=", multiplicação e atribuição "*=", etc.
Algumas referências sobre python
- Site oficial (em inglês) - http://www.python.org/
- Tutorial oficial (em inglês) - http://docs.python.org/tutorial/
- Python na prática - http://www.async.com.br/projects/python/pnp/
- Aprendendo a programar - http://www.freenetpages.co.uk/hp/alan.gauld/port/
- Google - http://www.google.com
métodos numéricos
tutorial, python
Python - Guia de instalação
Python será a linguagem de programação que utilizaremos para implementar os métodos numéricos visto neste curso. Uma vantagem de utilizar python é que a linguagem abstrai muitos detalhes que são impossíveis de ser ignorados na linguagem C. Outra vantagem é a grande quantidade de bibliotecas específicas para uso em ambiente científico. Serão apresentadas as seguintes bibliotecas:
- numpy - Torna possível trabalhar com vetores de tamanho e dimensões arbitrárias de forma eficiente
- matplotlib - Ferramenta para plotar gráficos em 2D
- SciPy - Possui implementação de quase todos os métodos numéricos que serão vistos e outros programas para matemática e engenharia
Para conter seus ânimos, essas bibliotecas serão vistas de forma superficial e apenas introdutória. O objetivo é apresentar uma ambiente novo de programação e treinar a implementação de algoritmos para resolução de problemas de cálculo numéricos.
Instalação em Windows e MacOSX
A maneira mais fácil de instalar todas as bibliotecas de uma só vez é baixando o pacote fornecido pela empresa Enthought.
Repare que o software é fornecido como uma assinatura anual, mas felizmente temos a opção de baixar sem custo para fins acadêmicos. Clique na opção "Academic" que você será direcionado para a página de download. Selecione seu ambiente (Windows ou Mac), forneça seu e-mail e clique em download.
O arquivo é grande (236 Mb) pois inclui muitas outras bibliotecas que não utilizaremos. O processo de instalação é simples, só não esqueça de selecionar "academic" no tipo de licença, e depois aceitar os valores padrões clicando em "Next".
Instalação em Linux (Ubuntu e Debian)
Digite:
sudo apt-get install python-scipy ipython python-matplotlib
Que todas as bibliotecas e suas dependências serão instaladas.
Testando a instalação
Para verificar se tudo esta instalado corretamente, podemos abrir um console e digitar alguns comandos para plotar uma função.
No linux, digite:
ipython -pylab
No windows, entre em Iniciar -> Programas -> EPD32-6.1 -> PyLab (IPython).
Isto abrirá um console em que comandos da linguagem python podem ser digitados e imediatamente executados. Por exemplo, se você digitar:
print "Funciona!"
Irá mostrar a frase "Funciona!" na tela. Para testar todas as bibliotecas, você pode digitar o pequeno programa abaixo:
def runge(x): return 1.0/(1.0 + 25.0 * pow(x,2))
x = numpy.arange(-1, 1, 0.01)
y = runge(x)
plt.plot(x, y, 'b-')
Apenas não esqueça que é necessário uma linha em branco após digitar a primeira linha, para que o interpretador saiba que a definição da função acabou. Se tudo foi instalado corretamente, você deve visualizar algo como a figura abaixo:

Para encerrar o programa digite exit()
métodos numéricos
tutorial, python
Revisão da linguagem C: parte 4
Variáveis indexadas
Considere o seguinte problema: escreva um programa em C que leia 5 números e mostre-os na ordem inversa da ordem lida.
Esse problema pode ser facilmente resolvido se utilizarmos 5 variáveis e 5 chamadas de scanf() seguido de 5 chamadas de printf() na ordem inversa. Mas e se o problema fosse com 500 variáveis?
Em C, possuímos um atalho para declarar 500 variáveis de uma só vez. Poderíamos escrever:
int v[500];
Que declara 500 inteiros que são acessíveis por "v". Esses inteiros podem ser acessados como v[0], v[1], ..., v[499]. O número que colocamos entre colchetes chamamos de "índice" e pode ser qualquer expressão que devolva um inteiro. A variável v é chamada de "vetor". Veja a resolução do problema:
#include <stdio.h>
#include <math.h>
int main(void)
{
int v[5];
int i;
i = 0;
while ( i < 5 )
{
scanf("%d", &v[i]);
i = i + 1;
}
i = 5 - 1;
while ( i >= 0 )
{
printf("%d\n", v[i]);
i = i - 1;
}
return 0;
}
Repare que utilizamos como índice a variável "i", o que é perfeitamente válido pois é uma variável inteira. Consequentemente, conseguimos escrever uma estrutura de repetição para a leitura e a escrita, não precisando escrever 5 printf()'s e 5 scanf()'s. Para alterar o programa para ler 500 variáveis, basta substituir todas as ocorrências de 5 por 500.
Outro detalhe da sintaxe de C é que o colchetes tem significado diferente na declaração e no uso da variável. Na declaração o colchetes significa a quantidade de variáveis que serão criadas. No uso da variável, o colchetes significa o índice da variável desejada. Não esqueça também que o índice começa em 0. Uma vez definido o tamanho de um vetor, este não poderá ser aumentado ou diminuído.
O conceito pode ser generalizado para usar mais de uma dimensão, para representar, por exemplo, uma matriz. Neste caso basta colocar mais uma sequencia de colchetes. Por exemplo:
int matriz[10][20];
Declara uma matriz de 10x20 inteiros. Para acessar a matriz, basta usar dois índices. De maneira análoga, é possível ter uma quantidade maior de dimensões.
métodos numéricos
tutorial, linguagem c
Revisão da linguagem C: parte 3
Alterando o fluxo de execução
Até o momento vimos programas em C que substituiam operações executadas pela calculadora. O que separa um computador de uma calculadora é a capacidade de tomar decisões, alterando o fluxo de execução das instruções. Ou seja, ao invés de executar as instruções sequencialmente como temos visto até agora, podemos definir dois blocos, que serão executados ou não dependendo de alguma condição.
Voltando ao exemplo de resolução de uma função do segundo grau. O que acontece se houver apenas uma raiz real, ao invés de duas? Ou pior ainda, se não houver nenhuma raiz real? Para isso podemos usar o comando de decisão "if". Veja o exemplo:
A linha
#include <stdio.h>
#include <math.h>
float discriminante(float a, float b, float c)
{
return b*b - 4*a*c;
}
float menor_raiz(float a, float b, float c)
{
return (-b - sqrt(discriminante(a,b,c)))/(2*a);
}
float maior_raiz(float a, float b, float c)
{
return (-b + sqrt(discriminante(a,b,c)))/(2*a);
}
int main(void)
{
float a;
float b;
float c;
float delta;
scanf("%f", &a);
scanf("%f", &b);
scanf("%f", &c);
delta = discriminante(a,b,c);
if ( delta >= 0 )
{
printf("%f\n", menor_raiz(a,b,c));
}
if ( delta > 0 )
{
printf("%f\n", maior_raiz(a,b,c));
}
return 0;
}
if ( delta >= 0 )
É o começo do comando de decisão, significa que o bloco de comandos que irá mostrar a menor raiz só será executado se o delta for maior ou igual a zero. Da mesma maneira que temos o operador >= podemos utilizar também > (maior), < (menor), <= (menor ou igual), e == (igual). Podemos agrupar duas condições diferentes usando outros conectores lógicos, como && (E), || (ou) e ! (não)
A forma geral do if é a seguinte:
if (expressao)
{
comandos;
}
Que funciona da seguinte maneira:
- A expressão é avaliada
- Se ela não for falsa, o bloco de "comandos" é executado.
Decisão dupla
Considere o seguinte exercício: Implemente a função
int resolve(float a, float b, float *x)
Que devolve 0 se a equação ax = b não tem solução e 1 caso contrário; neste caso coloca em *x a solução da equação.
Uma solução possível é a seguinte:
int resolve(float a, float b, float *x)
{
int soluvel;
soluvel = 1;
if ( a != 0 )
{
*x = b/a;
}
if ( a == 0 )
{
if ( b == 0 )
{
*x = 0;
}
if ( b != 0 )
{
soluvel = 0;
}
}
return soluvel;
}
Repare que a linha
if ( a == 0 )
É exatamente a negação de
if ( a != 0 )
C possui um atalho para este tipo de situação, que é o comando "else", sempre usado em conjunto com o if. O formato é o seguinte:
if (expressao)
{
comandos1;
}
else
{
comandos2;
}
Que é executado da seguinte maneira: se "expressao" for verdadeira, o bloco de comandos1 será executado, caso contrário, será executado o bloco de comandos2.
A mesma função pode então ser escrito da seguinte maneira (que é equivalente):
int resolve(float a, float b, float *x)
{
int soluvel;
soluvel = 1;
if ( a != 0 )
{
*x = b/a;
}
else
{
if ( b == 0 )
{
*x = 0;
}
if ( b != 0 )
{
soluvel = 0;
}
}
return soluvel;
}
O comando de repetição
O comando de repetição é a estrutura elementar do raciocínio algoritmico. Existem várias formas de fazer isso em C mas o mais simples é usando o comando while. Seu formato é:
while (expressao)
{
comandos;
}
E funciona da seguinte maneira:
- Primeiramente, a expressão é avaliada.
- Se a expressão for verdadeira, o bloco de comandos é executado.
- Em seguida, a expressão é novamente avaliada. Se ela for verdadeira, o bloco de comandos é novamente executado, repetindo o ciclo. Caso contrário, o comando é encerrado.
Veja por exemplo, um programa que lê um número N e escreve os números de 1 a N.
#include <stdio.h>
#include <math.h>
int main(void)
{
int i;
int n;
scanf("%d", &n);
i = 0;
while ( i < n )
{
i = i+1;
printf("%d\n", i);
}
return 0;
}
Apesar do comando de repetição ser sintaticamente simples, é muito comum os alunos terem dificuldade em aplicá-lo na resolução de um problema. O melhor a fazer neste momento é treinar resolvendo exercícios antes de continuar com tópicos mais avançados.
Continue para a parte 4 - vetores.
métodos numéricos
tutorial, linguagem c
Revisão da linguagem C: parte 2
Funções
O conceito de modularização é tão importante e prático em programação que geralmente vemos o assunto antes de continuar com comandos de repetição ou mesmo de decisão.
O conceito de função é emprestado do mesmo conceito em matemática, onde uma função tem domínio e contra-domínio. Aqui apenas chamamos o domínio de argumentos (pode ter nenhum, um ou vários) e chamamos o contra-domínio de valor de retorno. Já vimos no artigo anterior a função sqrt(x) que recebe um número real x e devolve (ou retorna) a raiz quadrada de x. Qualquer linguagem de programação possui o conceito de biblioteca, que é onde são disponibilizadas um grande número de funções já prontas para uso.
Infelizmente é muito improvável que você encontre funções para todos os problemas que você encontrar na vida, por issoé muito útil você saber como definir e criar suas próprias funções.
Vamos começar com um exemplo completo, que é o mesmo programa anterior só que utilizando uma função:
#include <stdio.h>
#include <math.h>
float discriminante(float a, float b, float c)
{
return b*b - 4*a*c;
}
int main(void)
{
float soma;
float produto;
float raiz_discriminante;
scanf("%f", &soma);
scanf("%f", &produto);
delta = discriminante(1.0, soma, produto);
raiz_discriminante = sqrt(delta);
printf("%f\n", (soma - raiz_discriminante)/2);
printf("%f\n", (soma + raiz_discriminante)/2);
return 0;
}
A linha float discriminante(float a, float b, float c) define uma função chamada discriminante que recebe 3 argumentos reais. O "float" no começo da linha denota o tipo de retorno esperado da função. Apenas um valor pode ser devolvido pela função, assim como no conceito matemático. Eventualmente é útil criar uma função que não devolve nada, por exemplo, uma função que apenas escreve uma mensagem de boas vindas na tela. Neste caso, como nenhum valor é devolvido, é possível definir o tipo de valor de retorno como "void".
As linhas seguintes contem o corpo da função, delimitado pelas chaves. O comando
return b*b - 4*a*c;
é utilizado para devolver um valor para o programa que invocou a função. No caso, o valor devolvido é número resultante do cálculo da expressão "b*b - 4*a*c". Este comando encerra imediatamente a execução da função.
É importante realçar que esse trecho de código apenas define a função. Isto não implica que o trecho de código será executado, para isto, a função terá que ser invocada. O fluxo de execução continua começando no bloco "main". A linha
delta = discriminante(1.0, soma, produto);
é a linha que efetivamente invoca a função. Os parâmetros 1.0, soma e produto são copiados para as variáveis da função chamadas a, b e c, respectivamente. A partir da execução da função o valor é devolvido e o resultado é armazenado na variável delta, que recebe o valor de "soma*soma - 4*1.0*produto". Repare que o nome dos argumentos podem ser diferentes dos nomes dados na definição da função. Ou seja, o que importa não é o nome do argumento, e sim a sua posição (primeiro, segundo, etc.).
O exemplo abaixo mostra a utilização de mais uma função, desta vez incluindo uma função que não devolve nada e não possui argumentos.
#include <stdio.h>
#include <math.h>
void mensagem_inicio()
{
printf("Seja bem vindo ao meu programa!\n");
printf("===============================\n");
}
float discriminante(float a, float b, float c)
{
return b*b - 4*a*c;
}
int main(void)
{
float soma;
float produto;
float raiz_discriminante;
mensagem_inicio();
scanf("%f", &soma);
scanf("%f", &produto);
delta = discriminante(1.0, soma, produto);
raiz_discriminante = sqrt(delta);
printf("%f\n", (soma - raiz_discriminante)/2);
printf("%f\n", (soma + raiz_discriminante)/2);
return 0;
}
Devolvendo mais de um valor
Repare que nosso problema de ler a soma e o produto de dois números e descobrir quais são esses números nada mais é que resolver uma equação do segundo grau no formato:
x^2 - soma * x + produto = 0
Logo, podemos deixar nossa solução um pouco mais genérica. Podemos escrever uma função que descobre raiz de uma equaçao do segundo grau qualquer e depois apenas aplicá-la ao nosso problema. Esta função precisa receber como argumento 3 números reais: a, b e c representando os coeficientes da equação ax^2 + bx + c = 0 e ela precisa devolver a menor e a maior raiz.
Infelizmente em uma função em C podemos devolver no máximo um valor usando o comando return. Para resolver esse problema usamos um tipo de parâmetro especial que chamaremos de "parâmetro de saída". Veja a função resolvida:
void raizes(float a, float b, float c, float *x1, float *x2)
{
*x1 = (-b - sqrt(discriminante(a,b,c)))/(2*a);
*x2 = (-b - sqrt(discriminante(a,b,c)))/(2*a);
}
Os parâmetros x1 e x2 são parâmetros de saída. A função raizes foi definida como void pois não devolve nada usando o comando return. Repare como a maneira de definir os parâmetros é diferente, com um asterisco antes da variável, que é usado também no comando de atribuição.
Apenas isso não é suficiente, ao chamar a função, precisamos ter o cuidade de informar uma variável para x1 e x2, veja o programa completo:
#include <stdio.h>
#include <math.h>
float discriminante(float a, float b, float c)
{
return b*b - 4*a*c;
}
void raizes(float a, float b, float c, float *x1, float *x2)
{
*x1 = (-b - sqrt(discriminante(a,b,c)))/(2*a);
*x2 = (-b - sqrt(discriminante(a,b,c)))/(2*a);
}
int main(void)
{
float soma;
float produto;
float raiz1;
float raiz2;
scanf("%f", &soma);
scanf("%f", &produto);
raizes(1.0, -soma, produto, &raiz1, &raiz2);
printf("%f\n", raiz1);
printf("%f\n", raiz2);
return 0;
}
Repare na linha:
raizes(1.0, -soma, produto, &raiz1, &raiz2);
Aqui precisamos informar que as variáveis raiz1 e raiz2 são variáveis de saída, e por isso podem ser alteradas. Isso é denotado pelo "&" na frente do nome da variável. Após a chamada de raizes(), as variáveis raiz1 e raiz2 vão ter o valor de *x1 e *x2, respectivamente.
Como isso é implementado? Por padrão, na passagem de parâmetros é feita uma cópia para a variável da função. Já &raiz1 e &raiz2 são ponteiros de memória. Significa que x1 e x2 possuem a informação de onde na memória se encontra raiz1 e raiz2, que podem então ser alterados. Ou seja, é como se a passagem de parâmetro fosse feita por referência e não por cópia. Felizmente você não precisa se preocupar com isso, desde que você compreenda o objetivo do uso, que é devolver mais de um valor.
Apenas como último lembrete, repare que a função não funciona se não houver raiz real, que felizmente não é nosso caso.
Qual a vantagem de utilizar funções?
Basicamente são dois os motivos principais. O mais óbvio é que você pode definir uma função e invocá-la mais de uma vez, evitando duplicação de código. Como regra geral, se você esta usando muito o "copy 'n paste" é por que provavelmente o código pode ser escrito de uma forma melhor usando funções. O segundo motivo, que você começa a perceber com um pouco mais de experiência em programação, é que você pode gerenciar a complexidade de um programa, "escondendo" os detalhes cabeludos. Por exemplo, definimos uma função chamada "raizes" que resolve uma equação do segundo grau usando a famosa fórmula de Báskara. Uma vez escrita esta função, você pode esquecer que Báskara existe, e pode se concentrar em outros detalhes mais importantes. Chamamos isso de "abstração".
Continue para a parte 3 - decisão e repetição.
métodos numéricos
tutorial, linguagem c
Revisão da linguagem C: parte 1
Esta é uma versão minimalista da linguagem C, que surgiu da necessidade de melhoria da disciplina ci208 - Programação de Computadores. O objetivo é diminuir a complexidade da linguagem para facilitar o entendimento de lógica de programação. Este artigo tem como objetivo apenas servir como referência para aqueles que já cursaram a disciplina e reduzir o tão conhecido efeito de atenuação do conhecimento que alguns alunos sofrem alguns meses após concluírem a disciplina. Se você ainda é aluno de ci208, este não é o seu lugar. Assista as aulas ao invés disso.
Edição / compilação / execução
Antes de mais nada, você precisa de um editor de textos e um compilador. Veja as "instruções para o programador novato" no fim da página da disciplina. No ambiente windows há instruções para usar o Dev-C++ que serve tanto como editor quanto compilador. Existem também instruções para usar o gEdit e o gcc em ambiente Linux.
Veja o esqueleto de um programa em C:
#include <stdio.h>
#include <math.h>
int main(void)
{
Comandos;
return 0;
}
Um programa em C sempre irá seguir este padrão. Basta substituir os comandos onde indicado.
Comando de saída
Em C a maneira mais simples de escrever alguma coisa é utilizando a função printf().
#include <stdio.h>
#include <math.h>
int main(void)
{
printf("Como vai, mundo iluminado!\n");
return 0;
}
Variáveis
Uma variável nada mais é que uma representação de uma posição na memória. Você define o nome que desejar para a variável. Ela também possui um tipo, que indica que tipo de informação esta sendo armazenada. Por exemplo, o tipo "int" representa um número inteiro e o tipo "float" representa um número real.
#include <stdio.h>
#include <math.h>
int main(void)
{
float v;
printf("%f\n", v);
return 0;
}
float v é a declaração da variável
o %f no printf indica que o que será escrito deve ser interpretado como um número real em base decimal. No caso de inteiros (int), utilize %d.
Para colocar um valor em uma variável usamos o comando de atribuição, em C, este comando é denotado por =
#include <stdio.h>
#include <math.h>
int main(void)
{
float v;
v=2;
printf("%f\n", v);
return 0;
}
Entrada
Todo programa de interesse lê algo fornecido pelo usuário. Em C, a maneira mais simples de fazer isso é usando o comando scanf():
#include <stdio.h>
#include <math.h>
int main(void)
{
float v;
scanf("%f", &v);
return 0;
}
Neste programa, a linha scanf() lê um valor real (denotado pelo %f) e o coloca em v. Não esqueça que no scanf não aparece o "\n" e este "&" é obrigatório.
Agora já podemos entender o que é um comando e uma expressão. Um comando é alguma instrução que pedimos que o computador execute. Por exemplo: atribuir um valor a uma variável ou escrever algo na tela. Uma expressão é uma construção formada com alguns operadores (por exemplo, operadores aritméticos como + e -) e que se resolve em um número. Geralmente uma expressão é usada como argumento para um comando.
Um exemplo de comando é
printf("%f\n", v);
No lugar de "v", poderíamos escrever:
printf("%f\n", v*2);
Que mostra na tela o dobro do valor de v. "v*2" é uma expressão que resulta em número real. Além do operador * (multiplicação) também temos os operadores -, +, / e % (resto da divisão). Note que a precedência é a mesma que utilizamos em álgebra e que podemos usar parênteses para alterá-la. Exemplo:
printf("%f\n", 2*(v+1));
Com isso, já podemos escrever programas simples que substituem uma calculadora. Por exemplo, o programa abaixo lê a soma e o produto de dois números arbitrários e mostra quais são esses números.
#include <stdio.h>
#include <math.h>
int main(void)
{
float soma;
float produto;
float raiz_discriminante;
scanf("%f", &soma);
scanf("%f", &produto);
raiz_discriminante = sqrt(soma * soma - 4 * produto);
printf("%f\n", (soma - raiz_discriminante)/2);
printf("%f\n", (soma + raiz_discriminante)/2);
return 0;
}
A única novidade aqui é a função de biblioteca sqrt() que calcula a raiz quadrada de um dado número (sqrt vem do inglês SQuare RooT). Uma função de biblioteca funciona exatamente como uma função como aprendemos em matemática (Ex: f(x)). Uma função tem um nome, recebe um ou mais parâmetros (domínio) e resulta em algum valor (contra domínio). Exemplo de outras funções de biblioteca incluem sin() - seno, cos() - cosseno, etc.
Continue para a parte 2 - funções.
métodos numéricos
tutorial, linguagem c
Olá Mundo
Sejam bem vindos.
Esta área será utilizada para compartilhar notas de aulas e para ajudar no preparo de material preliminar para a disciplina de métodos numéricos. Outra novidade deste semestre é que você pode inscrever seu e-mail na página da disciplina e receber as atualizações em primeira mão. Não deixe de participar e dar sua opinião.
Egon
geral
07 de Junho de 2010
egon