Introdução à Computação e Programação em MATLAB · 2 Capítulo 1 Preliminares Objectivos...

48
Introdução à Computação e Programação em MATLAB Jaime Ramos, Amílcar Sernadas e Paulo Mateus DMIST, Setembro de 2006

Transcript of Introdução à Computação e Programação em MATLAB · 2 Capítulo 1 Preliminares Objectivos...

Introdução à Computação e Programação em MATLAB

Jaime Ramos, Amílcar Sernadas e Paulo Mateus

DMIST, Setembro de 2006

2

Capítulo 1 Preliminares Objectivos Utilização básica do ambiente MATLAB para computação interactiva e visualização de dados. Variáveis, funções e formatos. Instrumentos de ajuda. Espaço de trabalho e seu arquivo. Matrizes e vectores. Gráficos. Arquivo de guiões. Primeiros exemplos Uma vez instalado o sistema MATLAB (por exemplo, a versão 5.3 da Prentice Hall utilizada neste texto), este é invocado da maneira usual no sistema operativo (por exemplo, WINDOWS XP, que serve de base aos exemplos neste texto que envolvam interação com o sistema operativo), iniciando-se uma sessão de trabalho que poderá ser terminada da maneira usual. A maneira mais elementar de utilizar o sistema MATLAB é como calculadora interactiva. Uma vez iniciada uma sessão de trabalho, na janela de comandos (Command Window) escreve-se uma expressão depois de >> e esta é avaliada quando se prime na tecla ENTER. Por exemplo: >> 1+1 ans = 2 Os exemplos seguintes ilustram as operações artiméticas básicas bem como as suas precedências relativas: >> 5+3*2 ans = 11 >> 1+2^3 ans = 9 >> 3*2^3 ans = 24 >> (3*2)^3 ans = 216 >> 16^1/2 ans = 8 >> 16^(1/2) ans = 4 >> 3^(1/2) ans =

3

1.7321 >> -2^2 ans = -4 >> (-2)^2 ans = 4 >> Destes exemplos conclui-se que a potência tem a mais alta precedência, seguida pela multiplicação e divisão, e, finalmente, pela adição e subtração. Naturalmente, é possível memorizar valores. Por exemplo, o comando de atribuição seguinte >> A = 133 A = 133 manda guardar na variável A o valor 133. A partir deste momento podemos aceder ao valor guardado em A usando a variável A em qualquer expressão. Por exemplo: >> A+7 ans = 140 >> A A = 133 Vale a pena aqui aprender o modo silencioso de dar comandos: basta terminar o comando com ponto e vírgula. Por exemplo: >> A=55; A execução deste comando não gera resposta (ans) mas tem o efeito pretendido. O valor guardado em A passou a ser 55. Com efeito: >> A A = 55 A forma geral do comando de atribuição é

variável = expressão em que a variável pode ter até vinte e um caracteres incluindo letras, dígitos e símbolo de sublinhado, começando obrigatoriamente por letra. É preciso cuidado para não alterar valores de variáveis pré-definidas no sistema MATLAB. Por exemplo:

4

>> pi ans = 3.1416 >> i ans = 0 + 1.0000i Note-se que o sistema MATLAB é normalmente sensível à diferença entre letras minúsculas e maiúsculas. Por exemplo: >> a �??? Undefined function or variable 'a'. >> A A = 55 >> Pi �??? Undefined variable or capitalized internal function Pi; Caps Lock may be on. >> PI �??? Undefined variable or capitalized internal function PI; Caps Lock may be on. Assim, e como a maioria das variáveis (e funções) primitivas (ou internas) do sistema são escritas com minúsculas, recomenda-se definir variáveis (e funções) começando com letra maiúscula. Para conhecer o que está disponível no sistema recorra aos intrumentos de ajuda (Help e Help Desk). Neste último encontra-se muita informação útil sobre o sistema e em particular informação sobre as funções primitivas. EXERCÍCIO: Procure informação sobre as funções trigonométricas disponíveis no sistema MATLAB recorrendo aos instrumentos de ajuda. Também se pode obter informação através do comando help desde que se conheça o nome da função: >> help sin SIN Sine. SIN(X) is the sine of the elements of X. Overloaded methods help sym/sin.m >> help pi PI 3.1415926535897.... PI = 4*atan(1) = imag(log(-1)) = 3.1415926535897.... >> help i I Imaginary unit.

5

As the basic imaginary unit SQRT(-1), i and j are used to enter complex numbers. For example, the expressions 3+2i, 3+2*i, 3+2j, 3+2*j and 3+2*sqrt(-1) all have the same value. Since both i and j are functions, they can be overridden and used as a variable. This permits you to use i or j as an index in FOR loops, etc. See also J. É possível examinar também o conjunto das variáveis (espaço de trabalho) que foram introduzidas até ao momento na sessão corrente, recorrendo à janela Workspace Browser acessível a partir do menu File da janela de comandos, escolhendo a opção Show Workspace. Neste caso aparecem apenas duas variáveis: a variável A que foi introduzida durante a sessão e a variável primitiva ans que contém a última resposta. A mesma informação pode ser obtida com o comando who: >> who Your variables are: A ans O comando whos dá mais informação: >> whos Name Size Bytes Class A 1x1 8 double array ans 1x1 16 double array (complex) Grand total is 2 elements using 24 bytes O espaço de trabalho é perdido quando é limpo com o comando clear ou quando se termina a sessão. Para arquivar o espaço de trabalho tem de se utilizar o comando save. Então, na sessão seguinte é possível recuperá-lo usando o comando load. Vectores e matrizes É possível guardar numa memória matrizes de valores. Por exemplo: >> M = [11 12 13; 21 22 23; 31 32 33] M = 11 12 13 21 22 23 31 32 33 >> whos M Name Size Bytes Class M 3x3 72 double array Grand total is 9 elements using 72 bytes Os elementos de cada linha podem também ser separados por vírgula em vez de espaço. As linhas são separadas por ponto e vírgula.

6

>> V=[1,2,3] V = 1 2 3 >> W=[1; 2; 3] W = 1 2 3 >> whos V W Name Size Bytes Class V 1x3 24 double array W 3x1 24 double array Grand total is 6 elements using 48 bytes É possível aceder a cada elemento de uma matriz. Por exemplo: >> M(1,2) ans = 12 Nos capítulos subsequentes serão dados vários exemplos de manipulação de matrizes recorrendo a programas imperativos que acedem aos elementos das matrizes um a um. Mas vale a pena desde já ilustrar as operações primitivas do sistema MATLAB disponíveis sobre matrizes: >> M*M ans = 776 812 848 1406 1472 1538 2036 2132 2228 >> V*W ans = 14 >> W*V ans = 1 2 3 2 4 6 3 6 9 >> M*M-5*W*V ans = 771 802 833 1396 1452 1508 2021 2102 2183

7

>> M1=[1,2; 5,7] M1 = 1 2 5 7 >> det(M1) ans = -3 >> N=inv(M1) N = -2.3333 0.6667 1.6667 -0.3333 >> M1*N ans = 1.0000 -0.0000 -0.0000 1.0000 Como exemplo de aplicação veja-se a resolução de um sistema de equações: 3x+5y+z=3 4x+6y-2z=1 x-y+z=0 >> clear Sejam: >> M=[3, 5, 1; 4, 6, -2; 1, -1, 1] M = 3 5 1 4 6 -2 1 -1 1 >> B=[3; 1; 0] B = 3 1 0 Então, a solução do sistema acima pode ser obtida como se segue: >> inv(M)*B ans = -0.2143 0.5714 0.7857 Ou mais eficientemente recorrendo ao operador matricial primitivo \: >> M\B

8

ans = -0.2143 0.5714 0.7857 Às vezes pretendemos executar as operações aritméticas usuais sobre matrizes, aplicando-as elemento a elemento. Por exemplo, dados dois vectores com o mesmo tamanho, podemos estar interessados em multiplicar o primeiro elemento do primeiro vector com o primeiro elemento do segundo vector, o segundo elemento do primeiro vector com o segundo elemento do segundo vector, e assim sucessivamente. Tal é obtido através da operação .*, como se ilustra no exemplo seguinte: >> clear >> X=[1, 2, 3, 4, 5, 6, 7] X = 1 2 3 4 5 6 7 >> Y=[1, 2, 1, 2, 1, 3, 2] Y = 1 2 1 2 1 3 2 >> X.*Y ans = 1 4 3 8 5 18 14 A operação .* corresponde à multiplicação elemento a elemento. Não confundir com a operação de produto matricial! De modo semelhante se podem aplicar as operações ./ e .^. Por exemplo: >> X.^Y ans = 1 4 3 16 5 216 49 O sistema MATLAB faculta diversas funções para visualização gráfica. Seguem-se alguns exemplos simples. Primeiro, considere-se o problema de gerar o gráfico da função seno entre 0 e 2pi. Para isso, é preciso começar por criar um vector linha contendo os pontos no domínio da função que pretendemos ver: >> X=linspace(0, 2*pi, 1000); >> help linspace LINSPACE Linearly spaced vector. LINSPACE(x1, x2) generates a row vector of 100 linearly equally spaced points between x1 and x2. LINSPACE(x1, x2, N) generates N points between x1 and x2. See also LOGSPACE, :. >> X1=linspace(0,2*pi,5) X1 = 0 1.5708 3.1416 4.7124 6.2832

9

De seguida, já se pode criar o vector linha dos valores da função nesses pontos: >> Y=sin(X); Finalmente, o gráfico é obtido usando a função plot: >> plot(X,Y)

0 1 2 3 4 5 6 7-1

-0.8

-0.6

-0.4

-0.2

0

0.2

0.4

0.6

0.8

1

Considere-se agora o problema de gerar o gráfico da função que a cada x faz corresponder x.sen(1/x) no intervalo 0.02 a 0.35: >> X=linspace(0.02,0.35,2000); >> Y=X.*sin(1./X); >> plot(X,Y)

10

0 0.05 0.1 0.15 0.2 0.25 0.3 0.35-0.25

-0.2

-0.15

-0.1

-0.05

0

0.05

0.1

0.15

EXERCÍCIO: Procure informação sobre as funções de geração de gráficos disponíveis no sistema MATLAB recorrendo aos instrumentos de ajuda. Guiões A terminar esta introdução ao ambiente MATLAB vale a pena ver como arquivar sequências de comandos (guiões, que em inglês surgem na lituratura como script files) que se pretendam utilizar noutra sessão. No capítulo seguinte a utilidade do arquivo de guiões será muito reforçada no caso particular de definições de funções. Para ilustração, suponha-se que se pretende arquivar o guião para gerar o gráfico da função seno. Para isso, há que começar por verificar qual é a pasta corrente onde o sistema MATLAB irá arquivar o guião, o que se consegue abrindo a janela Path Browser a partir do menu File da janela de comandos (Set Path). É possível alterar a pasta corrente e declará-la ao sistema MATLAB (opção add to path). Suponha que a pasta corrente é ...\MATLAB_SE_5.3\work, que já está no caminho de pesquisa do sistema. Pretende-se agora arquivar o guião referido acima no ficheiro plotsine.m. Notas importantes:

(i) o sufixo m é obrigatório para que o sistema MATLAB reconheça o ficheiro; (ii) o sistema MATLAB em ambiente WINDOWS não distingue maiúsculas de minúsculas nos

nomes destes ficheiros. É necessário criar e editar o ficheiro plotsine.m colocando nele a sequência de comandos que se pretende guardar. Esta operação pode ser feita com o seu editor de texto preferido (mas não se esqueça de usar o sufixo m). Pode também ser feita no sistema MATLAB recorrendo à janela de edição de guiões (Editor/Debugger) que se invoca da janela de comandos no menu File, opção New -> M-file. >> clear Uma vez arquivado o ficheiro plotsine.m com o conteúdo pretendido

11

X=linspace(0, 2*pi, 1000); Y=sin(X); plot(X,Y) é possível mandar executar o guião, mesmo noutra sessão, como se segue: >> plotsine

0 1 2 3 4 5 6 7-1

-0.8

-0.6

-0.4

-0.2

0

0.2

0.4

0.6

0.8

1

Introdução à Computação e Programação em MATLAB

Jaime Ramos, Amílcar Sernadas e Paulo Mateus

DMIST, Setembro de 2005

2

Capítulo 2 Programação imperativa Objectivos Expressões versus instruções. Instruções básicas: atribuições, entrada e saída de dados. Composição de instruções: sequencial, iterativa e alternativa. Variantes. Funções versus procedimentos. Efeitos colaterais. Definição, arquivo e invocação de funções. Invocação recursiva de funções. Aplicações numéricas. Conceitos básicos Vimos no capítulo anterior que as expressões denotam valores e que estes podem ser guardados em células de memória (variáveis). Programar imperativamente consiste em mandar o computador executar uma sequência de instruções para ir alterando o valores guardados em variáveis até se obter o resultado pretendido. Assim, um programa imperativo é uma lista de instruções que, quando executadas em sequência pelo computador, alteram sucessivamente o valor de variáveis (até se obter o resultado pretendido, se tudo correr bem). Existem instruções simples (como as de atribuição da forma genérica variável=expressão já ilustradas como comandos no capítulo anterior) e formas de compor instruções (por exemplo, a composição alternativa que manda executar uma instrução ou outra dependendo do valor de uma condição). Como primeiro exemplo considere-se o programa seguinte que arquivámos no ficheiro exemplo1.m: I=1; R=0; while (I<=50) R=R+I; I=I+1; end Este programa consiste na composição sequencial de três instruções: a atribuição I=1;, a atribuição R=0;, e a instrução composta while (I<=50) R=R+I;I=I+1; end. Assim, o sistema ao executar este programa começa por guardar o valor 1 na variável de memória I; depois guarda o valor 0 na variável de memória R; e, finalmente, executa a instrução iterativa while (I<=50) R=R+I;I=I+1; end como se descreve a seguir. A instrução iterativa while (I<=50) R=R+I;I=I+1; end consiste em repetir a instrução composta R=R+I;I=I+1; até que o valor da guarda (I<=50) seja falso, ou seja, até que o valor da variável I seja maior do que 50. Em cada passo de execução do ciclo é executada a instrução composta R=R+I;I=I+1;, ou seja, é executada a atribuição R=R+I; e depois é executada a atribuição I=I+1;. Em conclusão, após a execução deste programa, o valor da variável R deverá ser a soma dos números naturais entre 1 e 50 (ou seja, 50*(1+50)/2=1275). A execução deste programa é obtida com o comando exemplo1 e se de seguida examinarmos o conteúdo da variável R R R = 1275 obtemos a resposta esperada. A forma geral da instrução iterativa é a seguinte:

3

while guarda instrução end que, como já foi ilustrado, consiste em executar o comando instrução enquanto a condição guarda for verdadeira. Neste programa muito simples, vimos exemplos de atribuição (vários exemplos), de composição sequencial de instruções (dois exemplos; quais?) e ainda de composição iterativa de instruções (um exemplo apenas). Para além das atribuições, existem outras instruções básicas ou atómicas, nomeadamente as de leitura e escrita na janela de comandos (entrada e saída). No programa seguinte, arquivado no ficheiro exemplo2.m X=input('Valor inicial: '); Y=input('Valor final: '); % I=X; R=0; while (I<=Y) R=R+I; I=I+1; end % fprintf('Resultado: %g\n', R); encontram-se dois exemplos de instruções de entrada de dados (as duas primeiras instruções) e um exemplo de instrução de saída de dados (a última). O símbolo % é utilizado para comentar programas. Quando este símbolo surge no início de uma linha, essa linha é ignorada pelo computador. Mandando executar este programa a partir do editor de texto (Word) em que se está a escever este texto, surge a mensagem de erro seguinte: exemplo2 Valor inicial: Warning: Input command not available when using MATLAB as Engine or Notebook. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 1 Warning: One or more output arguments not assigned during call to 'input'. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 1 Valor final: Warning: Input command not available when using MATLAB as Engine or Notebook. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 2 Warning: One or more output arguments not assigned during call to 'input'. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 2 Warning: Reference to uninitialized variable X in exemplo2 at line 4. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 4 Warning: Reference to uninitialized variable I in exemplo2 at line 6. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 6 Warning: Reference to uninitialized variable Y in exemplo2 at line 6. > In C:\MATLAB_SE_5.3\folhasCP\cap02\exemplo2.m at line 6 Resultado: 0 Assim, deixa-se como exercício experimentar o programa acima a partir da janela de comandos do sistema MATLAB. Por exemplo: EDU» exemplo2

4

Valor inicial: 1 Valor final: 50 Resultado: 1275 EDU» exemplo2 Valor inicial: 1 Valor final: 10 Resultado: 55 EDU» No programa seguinte, arquivado no ficheiro exemplo3.m, surge outra forma de composição de instruções – a composição alternativa de instruções. N=input('N: '); % I=1; J=0; R=0; while (J<N) if isprime(I) R=R+I; J=J+1; end; I=I+1; end % fprintf('Soma dos N primeiros primos: %g\n', R); O corpo do ciclo deste programa é a composição sequencial if isprime(I) R=R+I; J=J+1; end; I=I+1; Assim, em cada passo do ciclo, o computador começa por verificar se o valor guardado na variável I é primo: em caso afirmativo, soma o valor de I ao valor da variável R e incrementa o valor da variável J; caso contrário, não faz nada. Depois de executar esta composição alternativa conclui o passo do ciclo com a execução da atribuição I=I+1;. Facilmente se conclui que, após a execução do programa, o conteúdo da variável R será a soma dos N primeiros números primos. Por exemplo: EDU» exemplo3 N: 1 Soma dos N primeiros primos: 2 EDU» exemplo3 N: 2 Soma dos N primeiros primos: 5 EDU» exemplo3 N: 10 Soma dos N primeiros primos: 129 EXERCÍCIO: Modifique o programa anterior para calcular o N-ésimo primo. A composição alternativa de instruções tem duas formas principais. A primeira já foi ilustrada: if guarda instrução end;

5

A segunda forma permite mandar executar uma instrução no caso de a condição guarda ser falsa: if guarda instrução1 else instrução2 end; Esta forma de composição alternativa é utilizada no programa seguinte (exemplo4.m): N=input('N: '); % I=1; NP=0; P=0; while (I<=N) if isprime(I) P=P+1; else NP=NP+1; end I=I+1; end % fprintf('Razão entre primos e não primos até %g: %g\n',N, P/NP); O programa anterior calcula a razão entre os números primos e os não primos que existem até um determinado número N. Veja-se que assim é através de alguns exemplos: EDU» exemplo4 N: 10000 Razão entre primos e não primos até 10000: 0.140121 EDU» exemplo4 N: 10 Razão entre primos e não primos até 10: 0.666667 EDU» exemplo4 N: 100 Razão entre primos e não primos até 100: 0.333333 EDU» exemplo4 N: 1000 Razão entre primos e não primos até 1000: 0.201923 EDU» exemplo4 N: 10000 Razão entre primos e não primos até 10000: 0.140121 Conhecendo as formas principais de composição de instruções, vale a pena exercitá-las em mais pequenos exercícios de programação imperativa. O programa seguinte (exemplo5.m) calcula o factorial: N=input('N: '); % I=1; R=1; while (I<=N) R=R*I; I=I+1; end % fprintf('Factorial de N: %g\n', R);

6

O programa seguinte (exemplo6.m) também calcula o factorial: N=input('N: '); % I=N; R=1; while (I>1) R=R*I; I=I-1; end % fprintf('Factorial de N: %g\n', R); O programa seguinte (exemplo7.m) calcula a maior diferença entre dois primos consecutivos até ao N-ésimo primo (supondo que N>1): N=input('N: '); % if (N<2) fprintf('Escolha N>1!\n'); else I=4; J=2; PA=3; R=1; while (J<N) if isprime(I) if (I-PA>R) R=I-PA; end; PA=I; J=J+1; end; I=I+1; end fprintf('Maior diferença entre primos até ao N-ésimo: %g\n', R); end Vale a pena explicar como este programa funciona. Note-se que a variável I é usada como índice para percorrer os sucessivos números naturais, a variável J é usada para contar os números primos já encontrados, a variável PA é usada para guardar o último primo encontrado e a variável R é usada para guardar o resultado. Uma vez garantido que N>1, a inicialização (instruções antes do ciclo) leva a que nos coloquemos na situação em que já foram detectados dois primos (o 2 e o 3) e, portanto, J=2. Nessa situação podemos pôr I=4 pois o próximo número a examinar será o 4. Mais, em PA guardamos o último primo encontrado até aí, ou seja pomos PA=3 e em R guardamos a diferença entre os dois números primos já considerados (3-2=1). Em cada passo do ciclo faz-se o seguinte. Se I for primo então verifica-se se R tem de ser revisto, actualiza-se PA e J. Em qualquer caso no fim do passo incrementa-se I. Para além da variante já analisada (while), a composição iterativa de instruções admite a variante for: for índice=domínio instrução end

7

que é utilizada no programa seguinte (exemplo8.m) que calcula a soma dos números entre X e Y (compare com o exemplo 2 acima): X=input('Valor inicial: '); Y=input('Valor final: '); % R=0; for (I=X:Y) R=R+I; end % fprintf('Resultado: %g\n', R); Por exemplo: EDU» exemplo8 Valor inicial: 1 Valor final: 10 Resultado: 55 EDU» exemplo8 Valor inicial: 1 Valor final: 50 Resultado: 1275 O domínio do índice usado no programa anterior é muito simples (índice = valor inicial : valor final). Existem outras formas de fixar o domínio de variação do índice, valendo a pena destacar aqui a possibilidade de fixar o passo de variação (índice = valor inicial : passo : valor final) como se ilustra no programa seguinte, que calcula a soma dos ímpares entre 1 e 50 (exemplo9.m) : R=0; for (I=1:2:50) R=R+I; end % fprintf('Resultado: %g\n', R); exemplo9 Resultado: 625 Uma variante importante da composição alternativa tem a forma geral seguinte: switch expressão case teste1 instrução1 case teste2 instrução2 ... end que é utilizada no programa seguinte, que calcula o número de primos até N que terminam num certo dígito D(exemplo10.m) : N=input('N: '); % I=1; J=0; UM=0; DOIS=0;

8

TRES=0; CINCO=0; SETE=0; NOVE=0; while (J<N) if isprime(I) switch mod(I,10) case 1 UM=UM+1; case 2 DOIS=DOIS+1; case 3 TRES=TRES+1; case 5 CINCO=CINCO+1; case 7 SETE=SETE+1; case 9 NOVE=NOVE+1; end; J=J+1; end; I=I+1; end D=input('Digito: '); switch D case 1 fprintf('primos: %g\n',UM); case 2 fprintf('primos: %g\n',DOIS); case 3 fprintf('primos: %g\n',TRES); case 5 fprintf('primos: %g\n',CINCO); case 7 fprintf('primos: %g\n',SETE); case 9 fprintf('primos: %g\n',NOVE); case {0,4,6,8} fprintf('primos: 0\n'); end No primeiro caso, a expressão consiste em calcular o resto da divisão de I por 10 e, consoante o resultado obtido, incrementar a variável correspondente (se o resultado for 1 então o primeiro teste é verdadeiro e a variável UM é incrementada, caso contrário, se o resultado for 3 então o segundo teste é verdadeiro e a variável TRES é incrementada, e assim sucessivamente). No segundo caso, a expressão é o valor lido para a variável D. Neste caso, a única novidade em relação ao caso anterior tem a ver com o último teste, em que se testa se o valor de D pertence a um determinado conjunto de valores. Como exemplo final, considere-se o problema de calcular o integral de uma função f num intervalo [a,b], ou seja, a área abaixo do gráfico de f em [a,b]. Por exemplo, considere-se o gráfico da função sin no intervalo [0,pi]. x=linspace(0,pi,30); y=sin(x); plot(x,y)

9

0 0.5 1 1.5 2 2.5 3 3.50

0.1

0.2

0.3

0.4

0.5

0.6

0.7

0.8

0.9

1

Uma solução para o problema passa por procurar uma aproximação numérica ao valor do integral da função. A abordagem mais simples a este problema consiste em dividir o intervalo [a,b] em n sub-intervalos consecutivos, com n suficientemente grande para se obter uma boa aproximação,

[a,b] = [x0,x1] ∪...∪ [xn-1,xn]

e aproximar o valor da função em cada um dos sub-intervalos [xi-1,xi] por f(xi-1). plot(x,y); hold on; x1=0:0.2:pi; y1=sin(x1); stairs(x1,y1)

10

0 0.5 1 1.5 2 2.5 3 3.50

0.1

0.2

0.3

0.4

0.5

0.6

0.7

0.8

0.9

1

Nestas condições, o valor do integral é aproximado por

∑ = −−−n

1i 1i1ii )).f(xx(x

Escolhendo os sub-intervalos todos com o mesmo comprimento

nabd −

=

chega-se a

∑ =−+

n

1i1).d)(id.f(a

ou seja,

∑ =−+

n

1i1).d)(if(ad.

O programa seguinte (exemplo11.m) solicita ao utilizador os limites de integração A e B e o número de sub-intervalos desejados e calcula o integral da função sin no intervalo [A,B]. A=input('Limite inferior:'); B=input('Limite superior:'); N=input('Número de intervalos:'); % D=(B-A)/N; S=0; X=A; I=1; while (I<=N)

11

S=S+sin(X); X=X+D; I=I+1; end; fprintf('O integral é: %g\n',D*S); Seguem-se alguns exemplos, para diferentes valores de N. Limite inferior:0 Limite superior:pi Número de intervalos:10 O integral é: 1.98352 Limite inferior:0 Limite superior:pi Número de intervalos:100 O integral é: 1.99984 Limite inferior:0 Limite superior:pi Número de intervalos:1000 O integral é: 2 Mais à frente apresentar-se-ão outros métodos para calcular o integral, bem como funções que permitam calcular o integral de qualquer função (integrável). Funções e procedimentos Para além das funções já disponíveis em MATLAB, o utilizador pode introduzir novas funções sempre que tal for útil. Recorde-se o problema de somar todos os números entre X e Y. No início do capítulo foi apresentado um exemplo em que os valores eram lidos e o resultado era escrito na janela de comandos. Uma alternativa consiste em definir uma função com dois argumentos X e Y que devolve o resultado pretendido. Tal definição é feita no ficheiro SomaInt.m: function R=SomaInt(X,Y) %SomaInt(x,y) soma os números inteiros entre x e y I=X; R=0; while (I<=Y) R=R+I; I=I+1; end SomaInt(1,50) ans = 1275 A definição da função anterior tem quatro componentes: o nome da função, SomaInt, os parâmetros X e Y, o resultado R e o corpo I=X;R=0;while I<=Y R=R+I;I=I+1; end. De cada vez que a função é chamada, X e Y recebem os valores dos argumentos da chamada (no caso do exemplo 1 e 50, respectivamente), e em seguida o programa comporta-se como descrito anteriormente. No fim da execução é devolvido o valor em R. A segunda linha (que está comentada) é utilizada para fornecer ao utilizador informação sobre a função: help SomaInt

12

SomaInt(x,y) soma os números inteiros entre x e y O nome do ficheiro é atribuido, por defeito, pelo MATLAB e coincide com o nome da função. Considere-se agora uma função SomaPrimos para somar os N primeiros números primos (guardada no ficheiro SomaPrimos.m): function Y=SomaPrimos(N) % SomaPrimos(N) calcula a soma dos N primeiros números primos I=1; J=0; Y=0; while (J<N) if isprime(I) Y=Y+I; J=J+1; end I=I+1; end SomaPrimos(1) ans = 2 SomaPrimos(2) ans = 5 SomaPrimos(10) ans = 129 Finalmente, considere-se o problema de definir uma função para calcular o factorial: function R=factI(N) % factI(N) calcula o factorial de N I=1; R=1; while (I<=N) R=R*I; I=I+1; end factI(5) ans = 120 Muitas vezes é conveniente proteger a função que se está a definir contra argumentos indesejados: factI(-2) ans = 1 No caso do factorial pretende-se que a função apenas esteja definida para números naturais. Comece-se por definir uma função nat (nat.m), que testa se um dado número é natural:

13

function R=nat(N) % nat(N) testa se N é um número natural if ((N>0) & (N==floor(N))) R=1; else R=0; end nat(5) ans = 1 nat(-2) ans = 0 nat(2.1) ans = 0 Com esta função, pode-se redefinir a função factI de modo a não aceitar argumentos que não sejam números naturais (factI1.m): function R=factI1(N) % factI1(N) calcula o factorial de N if (~nat(N)) fprintf('Erro. Argumento indesejado.\n'); else I=1; R=1; while (I<=N) R=R*I; I=I+1; end end factI1(3) ans = 6 factI1(-2) Erro. Argumento indesejado. As variáveis utilizadas dentro da definição de uma função são locais a essa definição, ou seja, apenas são conhecidas dentro da função. Alterações efectuadas a essas variáveis não se reflectem para fora do corpo da função: I=2+1 I = 3 factI1(5) ans =

14

120 I I = 3 ou seja, após a execução da função, o valor da variável I, que é utilizada localmente pela função, não foi alterado. Igualmente, os parâmetros de uma função também não podem ser alterados, mesmo que o argumento seja uma variável. Mas podem, no entanto, ser utilizados como variáveis locais. Considere-se uma outra versão do factorial, em que se utiliza o parâmetro N como variável auxiliar: function R=factI2(N) % factI2(N) calcula o factorial de N if (~nat(N)) fprintf('Erro. Argumento indesejado.\n'); else R=1; while (N>1) R=R*N; N=N-1; end end As alterações provocadas no parâmetro N não se reflectem para fora da função: X=4 X = 4 factI2(X) ans = 24 X X = 4 É no entanto possível declarar variáveis globais em MATLAB, ou seja, variáveis que são conhecidas para além do âmbito da definição de uma função. Em MATLAB uma variável pode ser declarada como global através da instrução global. A função seguinte (factG.m) calcula o factorial do número guardado em N e deixa o resultado na variável global R: function factG() % factG() calcula o valor do factorial guardado na variável N % e deixa o resultado na variável global R global N R if (~nat(N)) fprintf('Erro. Argumento indesejado.\n'); else I=1; R=1; while (I<=N) R=R*I; I=I+1; end end

15

global N R N=5 N = 5 factG R R = 120 Repare-se que, na chamada da função, não foram passados parâmetros e que a chamada da função não retornou nenhum valor. O resultado ficou guardado na variável global R. As variáveis globais apenas são conhecidas onde tenham sido declaradas como globais. Repare-se que no exemplo anterior, foi necessário declarar as variáveis N e R como globais na janela de comandos antes da chamada da função, para garantir que, quer na função quer na janela de comandos, as variáveis existiam como globais. À modificação de variáveis globais, como no caso do exemplo anterior, dá-se o nome de efeito colateral. Funções com efeitos colaterais são vulgarmente designadas por procedimentos. Os seus efeitos observam-se nas variáveis globais afectadas. A possibilidade de definição de procedimentos é essencial à programação imperativa para definir novas instruções, enquanto que a possibilidade de definir funções serve apenas para introduzir novas operações. Programação recursiva Consiste em definir recursivamente funções à custa de outras funções. Toda a função definida imperativamente pode também ser definida recursivamente e vice-versa. Recorde-se o problema de calcular o factorial de um número natural n, que pode ser definido recursivamente pela expressão:

n! = !"#

>−

=

0n se1)!n.(n0n se1

Apresenta-se a seguir a definição da respectiva função MATLAB (factR.m): function R=factR(N) %fact(N) calcula o factorial de N (recursivamente) if (N==0) R=1; else R=N*factR(N-1); end factR(1) ans = 1 factR(5) ans = 120 Deixa-se como exercício proteger os parâmetros da função contra argumentos indesejados. factR(-1)

16

�??? Maximum recursion limit of 500 reached. Use set(0,'RecursionLimit',N) to change the limit. Be aware that exceeding your available stack space can crash MATLAB and/or your computer. Error in ==> C:\MATLAB_SE_5.3\folhasCP\cap02\factR.m On line 6 ==> R=N*factR(N-1); Considere-se agora a sucessão Fn, n≥0, dos números de Fibonacci, que pode ser definida como se segue:

Fn=1n se 1n se 0n se

FF10

1n2n >

=

=

!"

!#

$

+ −−

A sua definição em MATLAB é a seguinte (fib.m): function R=fib(N) %fib(N) calcula o N-esimo número de Fibonacci if (N==0) R=0; else if (N==1) R=1; else R=fib(N-2)+fib(N-1); end end fib(2) ans = 1 fib(6) ans = 8 Mas à frente, serão apresentados mais exemplos de funções definidas recursivamente, aquando do estudo das estruturas matriciais em MATLAB.

17

Aplicações numéricas Seguem-se alguns exemplos de computação numérica. No primeiro exemplo, pretende-se calcular raízes de uma equação não linear. A solução proposta consiste na implementação do método da bissecção, que se baseia no teorema do valor intermédio. Seja f a função obtida da equação f(x)=0, contínua em [a,b] e tal que f(a)f(b)<0. Então existe uma raiz neste intervalo. Sejam a0=a, b0=b, I0=[a0,b0] e x0 o ponto médio do intervalo I0. Uma das três condições seguintes verifica-se:

1) f(a0)f(x0)<0, e então existe uma raiz em [a0,x0]; 2) f(a0)f(x0)>0, e então existe uma raiz em [x0,b0]; 3) f(a0)f(x0)=0, e então x0 é uma raiz.

Sem perda de generalidade, suponha-se que se verifica a condição 1). Nesse caso, sejam a1=a0, b1=x0, I1=[a1,b1] e x1 o ponto médio do intervalo I1. Repita-se o processo para o intervalo I1. Uma das três condições seguintes verifica-se:

1) f(a1)f(x1)<0, e então existe uma raiz em [a1,x1]; 2) f(a1)f(x1)>0, e então existe uma raiz em [x1,b1]; 3) f(a1)f(x1)=0, e então x1 é uma raiz.

Deste modo, obtém um intervalo I2. Aplicado este procedimento sucessivamente, é calculada uma sequência de intervalos I0⊃ I1⊃I2⊃... onde a raiz r∈Ik para todo o k. Note-se que a amplitude dos intervalos Ik diminui à medida que k aumenta. A função bissec (bissec.m) recebe como argumentos a função F, os extremos A e B do intervalo onde se pretende procurar a raiz e a amplitude mínima dos intervalos E. function R=bissec(F,A,B,E) %bissec(f,a,b,e) procura uma raiz da equação f(x)=0 %no intervalo [a,b], com erro e. X=A; Y=B; D=(B-A)/2; Z=A+D; while ((D>E) & (feval(F,Z)~=0)) if (feval(F,X)*feval(F,Z)<0) Y=Z; else X=Z; end D=D/2; Z=X+D; end R=Z; Considere-se a equação e-x-sin(x)=0, sabendo que existe uma raiz no intervalo [0.25,0.75]. Começa-se por definir a função teste1 (teste1.m): function y=teste1(x) y=exp(-x)-sin(x); teste1(0.25) ans = 0.5314 teste1(0.75) ans = -0.2093

18

e portanto, a função está nas condições do teorema do valor intermédio. Então, bissec('teste1',0.25,0.75,0.000001) ans = 0.5885 e conclui-se que 0.5885 é uma raiz da equação (com um erro máximo de 0.000001). De facto, teste1(0.5885) ans = 4.5413e-005 A função bissec apresenta uma novidade em relação aos exemplos anteriores: a passagem de funções por parâmetro. Com efeito, o primeiro parâmetro F é o nome de uma função. Para avaliar a função correspondente em X, recorre-se à função do MATLAB feval. Em geral, esta função recebe como argumentos o nome de uma função (entre plicas) e o ponto onde se pretende avaliar a função e devolve o valor da função nesse ponto. Considerem-se os seguintes exemplos feval('sin',pi/2) ans = 1 A expressão anterior é equivalente a sin(pi/2). Voltando ao exemplo da função bissec, o seu funcionamente é o seguinte. As variáveis auxiliares X e Y correspondem aos extremos do intervalo I (a0 e b0); a variável D corresponde à amplitude do intervalo e a variável Z corresponde ao ponto médio do intervalo (x0). Enquanto a amplitude do intervalo for maior do que E e ainda não se tiver encontrado uma raiz, testa-se qual das situações se verifica: se f(a0)f(x0)<0 então escolhe-se como extremo superior de I o valor em Z, caso contrário escolhe como extremo inferior de I o valor em Z. Por fim, divide-se a amplitude do intervalo ao meio e actualiza-se o ponto médio. Este processo repete-se até a amplitude de I ser inferior a E ou até se encontrar uma raiz de F. Recorde-se agora o problema apresentado atrás para calcular o integral da função sin. É fácil adaptar este programa de modo a definir uma função que calcule o integral de qualquer função F integrável num dado intervalo I. Esta função tem quatro parâmetros: o nome da função que se pretende integrar, F, os extremos do intervalo, A e B, e o número de subintervalos pretendidos N (nintegrate.m): function R=nintegrate(F,A,B,N) %nintegrate(F,A,B,N) calcula o integral de F %no intervalo [A,B] particionado em N subintervalos D=(B-A)/N; S=0; X=A; I=1; while I<=N S=S+feval(F,X); X=X+D; I=I+1; end; R=D*S; nintegrate('sin',0,pi,10) ans = 1.9835

19

nintegrate('sin',0,pi,1000) ans = 2.0000 nintegrate('teste1',0,pi,100) ans = -1.0279 nintegrate('teste1',0,pi,10000) ans = -1.0431 Este último valor já é uma aproximação razoável da solução (e-

π(-1-eπ) ≈ -1.04321).

Introdução à Computação e Programação em MATLAB

Jaime Ramos, Amílcar Sernadas e Paulo Mateus

DMIST, Setembro de 2005

2

Capítulo 3 Complementos de programação Objectivos Exemplos complementares de programação sobre estruturas matriciais. Funções auxiliares. Ordenação de vectores. Listas sobre vectores. Vectores e Matrizes Recordem-se as noções de vector e de matriz, apresentadas no capítulo 1. Neste capítulo, apresentam-se exemplos de programas sobre essas estruturas. Como primeiro exemplo, considere-se o problema de somar os elementos de um vector. A função seguinte, somaVector (somaVector.m), resolve o problema imperativamente: function R=somaVector(V) %somaVector(V) soma os elementos do vector V I=1; R=0; while I<=length(V) R=R+V(I); I=I+1; end V=[1,2,3,4,5] V = 1 2 3 4 5 somaVector(V) ans = 15 Esta função percorre os elementos do vector, posição a posição, recorrendo à variável I. Cada elemento de V é somado a R. No fim R contém a soma de todos os elementos de V. É interessante pensar numa definição recursiva desta função. Considere-se a função somaVectorR com dois argumentos, um vector V e uma posição I, para somar os elementos de V a partir da posição I. No caso geral, a soma dos elementos de V a partir de I pode ser definida como a soma do elemento nessa posição, V(I), com o resultado de somar os elementos de V dessa posição em diante, somaVectorR(V,I+1). Assim, a função somaVectorR (somaVectorR.m) define-se em MATLAB como se segue: function R=somaVectorR(V,I) %somaVectorR(V,I) soma os elementos do vector V a partir da posição I if I>length(V) R=0; else R=V(I)+somaVectorR(V,I+1); end somaVectorR(V,1) ans = 15 somaVectorR(V,4)

3

ans = 9 somaVectorR(V,7) ans = 0 A função anterior é, à semelhança da função factR, uma função recursiva. No caso presente, a recursão é feita nas posições do vector e termina quando se ultrapassar o comprimento do vector. Mais tarde, quando se estudarem listas, serão apresentadas outras formas de recursão sobre estruturas deste tipo. No próximo exemplo, define-se uma função para contar o número de números primos que ocorrem num vector. Apresenta-se em primeiro lugar uma versão imperativa da solução (contaPrimos.m): function R=contaPrimos(V) %contaPrimos(V) conta o número de números primos no vector V I=1; R=0; while I<=length(V) if isprime(V(I)) R=R+1; end I=I+1; end V V = 1 2 3 4 5 contaPrimos(V) ans = 3 U=[4,6,8,10] U = 4 6 8 10 contaPrimos(U) ans = 0 Tal como no exemplo anterior, as posições do vector V são percorridas uma a uma pela variável I. Cada posição é analisada e se o elemento correspondente for um número primo a variável R é incrementada. O desenvolvimento da versão recursiva desta função é semelhante ao da função somaVectorR. No entanto, desta vez, pretende-se definir uma função com apenas um parâmetro (o vector), não tendo assim o utilizador que se preocupar em indicar a partir de que posição pretende contar os números primos. A solução deste problema passa por definir uma função auxiliar contaAux, com dois parâmetros, um vector e uma posição, para contar o número de números primos do vector a partir dessa posição. Esta função é depois utilizada pela função principal. A definição da função auxiliar é incluída no ficheiro da função principal, contaPrimosR (contaPrimosR.m):

4

function R=contaPrimosR(V) %contaPrimos(V) versão recursiva da função contaPrimos R=contaAux(V,1); function R=contaAux(V,I) %contaAux(V,I) função auxiliar que conta o número de números primos %em V a partir da posição I if I>length(V) R=0; else if isprime(V(I)) R=1+contaAux(V,I+1); else R=contaAux(V,I+1); end end contaPrimosR(V) ans = 3 contaPrimosR(U) ans = 0 O facto de se ter definido a função auxiliar contaAux dentro do ficheiro da função contaPrimosR tem algumas consequências. Nomeadamente, a função não é acessível a não ser à função contaPrimosR. Com efeito: contaAux(V,1) �??? Undefined function or variable 'contaAux'. Há sempre possibilidade de definir a função contaAux como uma função normal, dentro do seu próprio ficheiro. A definição da função como função auxiliar, dentro do ficheiro de outra função, ou como função, no seu próprio ficheiro, depende da situação. Cada uma das soluções tem vantagens e desvantagens: no primeiro caso, evita-se sobrecarregar demasiado os nomes disponíveis (pode ser necessário definir uma função contaAux na definição de uma função para contar os números primos de uma matriz), no segundo caso, a função é disponibilizada ao utilizador e pode eventualmente ser utilizada por outras funções. Considere-se agora o problema de pesquisar o elemento mínimo de um vector. A ideia consiste em percorrer o vector, elemento a elemento e, sempre que se encontrar um elemento menor do que o até aqui encontrado, guardar esse novo elemento. function R=minimo(V) %minimo(V) calcula o mínimo do vector V I=1; R=+Inf; while (I<=length(V)) if R>V(I) R=V(I); end I=I+1; end minimo([1,2,3])

5

ans = 1 minimo([1,-2,3,-2]) ans = -2 Deixa-se como exercício desenvolver uma versão recursiva desta função. Em seguida, estuda-se o caso de funções que retornam como resultado um vector. Este tipo de funções exige que esse vector seja construído, podendo esta construção ser feita de diversas formas. Considere-se o problema de multiplicar um determinado vector por um escalar. Embora esta função já esteja disponível em MATLAB, este é um exemplo simples que permite ilustrar algumas soluções para a construção do vector resultado. A primeira solução consiste em inicializar a variável resultado com um vector constituído apenas por zeros do mesmo tamanho do vector dado. Para tal, recorre-se à função primitiva zeros. Esta função pode ser usada de diversas maneiras: zeros(n), que gera uma matriz nxn de zeros: zeros(3) ans = 0 0 0 0 0 0 0 0 0 zeros(n,m), que gera uma matriz nxm de zeros: zeros(2,3) ans = 0 0 0 0 0 0 zeros(size(v)), que gera uma matriz de zeros com as mesmas dimensões que v: v=[1,2,3]; zeros(size(v)) ans = 0 0 0 A função seguinte(prodEscalar.m) apresenta uma solução para o problema proposto, em que a variável resultado é inicializada com um vector de zeros com as mesmas dimensões do vector argumento: function U=prodEscalar(V,X) %prodEscalar(V,X) multiplica o vector V pelo escalar X I=1; U=zeros(size(V)); while I<=length(V) U(I)=V(I)*X; I=I+1; end

6

V V = 1 2 3 4 5 prodEscalar(V,2) ans = 2 4 6 8 10 A função prodEscalar recebe como argumentos um vector V e um escalar X. Utiliza ainda uma variável auxiliar I para percorrer as posições de V. O resultado é calculado em U. Inicialmente, U contém o vector de zeros do mesmo tamanho que o vector V. Para cada posição de V, a posição correspondente em U é alterada de modo a conter o produto do elemento de V pelo escalar X. Este processo repete-se para todas as posições de V. Uma alternativa a esta inicialização pode ser fazer uma cópia de V para U, ficando os dois vector com o mesmo tamanho e em seguida proceder da mesma forma. Uma terceira solução passa por tirar partido da passagem de parâmetros no MATLAB (passagem por valor), discutida no capítulo anterior. Com efeito, é possível utilizar V como variável auxiliar que vai sendo alterada directamente, sem que essas alterações se reflictam para o exterior. function U=prodEscalar1(V,X) %prodEscalar1(V,X) multiplica o vector V pelo escalar X I=1; while I<=length(V) V(I)=V(I)*X; I=I+1; end U=V; ProdEscalar1(V,2) ans = 2 4 6 8 10 V V = 1 2 3 4 5 Existe ainda uma quarta solução, que consiste em ir construindo o vector, componente a componente. Este tipo de solução será discutido à frente. Note-se no entanto que nem todas as linguagens de programação permitem este tipo de construção dinâmica de vectores. Quando o MATLAB executa uma função, começa por testar se os parâmetros são alterados durante a execução da função. Em caso afirmativo, antes da execução da função é feita uma cópia deste que é reposta após a execução para que as alterações provocadas durante a execução não se reflictam para fora da função, obtendo-se assim a chamada por valor. Contudo, esta solução obriga a uma duplicação da informação (é necessário guardar uma cópia do parâmetro para repor o valor original após a chamada da função). Este problema pode ser resolvido recorrendo a variáveis globais. Em vez de passar o vector por parâmetro, este é declarado como variável global, que é alterada pela função (procedimento, porque provoca efeitos colaterais). Os resultados observam-se na variável global afectada. Neste caso considerou-se a variável global V: function prodEscalar2(X) %prodEscalar2(X) multiplica o vector V pelo escalar X in situ global VProdEscalar I=1; while I<=length(VProdEscalar)

7

VProdEscalar(I)=VProdEscalar(I)*X; I=I+1; end global VProdEscalar VProdEscalar=V VProdEscalar = 1 2 3 4 5 prodEscalar2(2) VProdEscalar VProdEscalar = 2 4 6 8 10 Deixa-se como exercício desenvolver uma versão recursiva da função prodEscalar. Considere-se agora o problema de calcular o produto interno de dois vectores (prodInterno.m). function R=prodInterno(V1,V2) %prodInterno(V1,V2) calcula o produto interno de V1 e V2 if length(V1)~=length(V2) fprintf('Erro! Os vectores não têm o mesmo comprimento!\n'); else I=1; R=0; while I<=length(V1) R=R+V1(I)*V2(I); I=I+1; end end V1=[1,2,3,4]; V2=[2,2,2,2]; prodInterno(V1,V2) ans = 20 V3=[1,2,3]; prodInterno(V1,V3) Erro! Os vectores não têm o mesmo comprimento! A função começa por testar se os dois vectores têm o mesmo comprimento e caso não tenham é enviada uma mensagem de erro. Caso contrário, os vectores em V1 e V2 são percorridos da primeira à última posição e para cada posição calcula-se o produto dos valores correspondentes e soma-se às parcelas anteriores, guardadas em R. Apresenta-se em seguida uma função recursiva para o cálculo do produto interno, que recebe como parâmetros os dois vectores e devolve o seu produto interno. O ficheiro prodInternoR.m inclui a definição de uma função auxiliar prodAux que calcula o produto interno dos vectores a partir de uma posição (como nos exemplos anteriores):

8

function R=prodInternoR(V1,V2) %prodIntR(V1,V2) calcula o produto interno de V1 e V2 recursivamente if length(V1)~=length(V2) fprintf('Erro! Os vectores não têm o mesmo comprimento!\n'); else R=prodAux(V1,V2,1); end function R=prodAux(V1,V2,I) if I>length(V1) R=0; else R=V1(I)*V2(I)+prodAux(V1,V2,I+1); end Embora já esteja disponível no MATLAB a função sort que permite ordenar vectores, vale a pena exercitar os conhecimentos de programação já adquiridos na construção de um programa para o mesmo efeito. Antes, experimente-se a função primitiva disponível: sort([4,7,2,3,1]) ans = 1 2 3 4 7 Apresentam-se agora dois algoritmos para ordenação de vectores. O primeiro algoritmo baseia-se no método da selecção (select sort), que consiste em procurar o elemento mínimo e colocá-lo no início do vector e em seguida fazer o mesmo com os restantes elementos. function selectSort() %selectSort() ordena o vector em W global W N=length(W); if N>1 M=1; while M<N K=M+1; while K<=N if W(M)>W(K) X=W(M); W(M)=W(K); W(K)=X; end K=K+1; end M=M+1; end end O algoritmo ordena o vector guardado na variável global W. A variável M indica qual a posição do vector que está a ser analisada no momento. No início, esta posição é a primeira posição do vector, onde se pretende colocar o menor elemento. Para cada posição M, procura-se nas posições seguintes (desde M+1 até ao fim do vector) qual o menor elemento e é esse elemento que é colocado na posição M. Esta pesquisa é feita no segundo ciclo, em que, sempre que se encontra numa posição K (maior do que M) um elemento menor do que o que está em M, este elementos são trocados ficando o menor na posição M. W=[4,7,2,3,1]

9

W = 4 7 2 3 1 SelectSort W W = 1 2 3 4 7 O segundo algoritmo consiste no método da inserção directa (insert sort) que consiste em determinar, para cada elemento que não foi ainda ordenado, qual a sua posição na lista já ordenada e inseri-lo na posição correspondente. function insertSort() %insertSort() ordena o vector em W global W N=length(W); if N>1 M=1; while M<N K=M; SINAL=1; while K>=1 & SINAL if W(K)>W(K+1) X=W(K); W(K)=W(K+1); W(K+1)=X; K=K-1; else SINAL=0; end end M=M+1; end end Em cada instante, os elementos até à posição M estão já ordenados entre si. Considera-se o elemento na posição seguinte e vai-se trocando este elemento com os elementos que estão nas posições anteriores até encontrar um que seja maior. Quando esse elemento for encontrado, o processo de troca pára e o elemento fica na posição correcta. Vale a pena considerar um exemplo para perceber como funciona o algoritmo. Considere-se o vector W=[1,3,4,2], ordenado até à posição 3 (ou seja, M=3). Para que o vector fique ordenado até à posição 4, há que inserir o elemento 2 na posição correcta. K toma o valor inicial 3 e SINAL o valor inicial 1. Em seguida compara-se o elemento na posição 3 (4) com o da posição 4 (2) e se for maior há que proceder à troca, ou seja, W fica com o vector [1,3,2,4]. Em seguida, passa-se para a posição anterior, ou seja, considera-se o elemento na posição 2 (3) e compara-se com o elemento na posição 3 (2). Novamente, há que proceder à troca de elementos, ficando W com o vector [1,2,3,4]. Finalmente, há que comparar o elemento na posição 1 (1) com o elemento na posição 2 (2). Neste caso, não há que proceder a nenhuma troca, pelo que SINAL é colocado a 0 e o ciclo termina. W=[1,3,4,2] W = 1 3 4 2 insertSort

10

W W = 1 2 3 4 A definição de funções sobre matrizes é semelhante à definição de funções sobre vectores. Há apenas que ter em linha de conta que, enquanto num vector apenas há uma dimensão a considerar, no caso das matrizes há que considerar as linhas e as colunas. É por isso necessário recorrer a dois ciclos encaixados, um para percorrer as linhas e, para cada linha, outro para percorrer as colunas. Como primeiro exemplo pretende-se definir uma função somaMatriz que soma todos os elementos de uma matriz. Recorde-se que as dimensões de uma matriz podem ser obtidas através da função MATLAB size. A=[1,2,3;3,2,1] A = 1 2 3 3 2 1 size(A) ans =

2 3 Em MATLAB é possível fazer uma atribuição simultânea a várias variáveis. Por exemplo: [L,C]=size(A) L = 2 C = 3 L L = 2 C C = 3 Esta atribuição permite guardar em L o número de linhas (primeiro valor dado pela função size) e em C o número de colunas (segundo valor dado pela função size). Este tipo de atribuições vai ser útil na definição das próximas funções. Apresenta-se a seguir uma função para somar os elementos de uma matriz. function R=somaMatriz(A) %somaMatriz(A) soma os elementos da matriz A [L,C]=size(A); I=1; R=0; while I<=L J=1; while J<=C R=R+A(I,J); J=J+1; end

11

I=I+1; end somaMatriz(A) ans = 12 Tal como no caso dos vectores, define-se agora uma função prodMEscalar para multiplicar uma matriz por um escalar function R=prodMEscalar(A,X) %prodMEscalar(A,X) multiplica a matriz A pelo escalar X [L,C]=size(A); I=1; R=zeros(L,C); while I<=L J=1; while J<=C R(I,J)=A(I,J)*X; J=J+1; end I=I+1; end A A = 1 2 3 3 2 1 prodMEscalar(A,3) ans = 3 6 9 9 6 3 Deixa-se como exercício apresentar alternativas à função anterior, uma que utilize o parâmetro A como variável auxiliar e um procedimento, tal como foi feito para os vectores. Apresenta-se a seguir uma função para somar matrizes. function R=somaMatrizes(A,B) %somaMatrizes(A,B) soma as matrizes A e B, %desde que sejam da mesma dimensão if ~isequal(size(A),size(B)) fprintf('Erro! As matrizes não têm a mesma dimensão!\n'); else [L,C]=size(A); I=1; R=zeros(L,C); while I<=L J=1; while J<=C R(I,J)=A(I,J)+B(I,J); J=J+1; end I=I+1; end end

12

B=[6,7,8;6,5,4] B = 6 7 8 6 5 4 somaMatrizes(A,B) ans = 7 9 11 9 7 5 C=[1,2;3,2] C = 1 2 3 2 somaMatrizes(A,C) Erro! As matrizes não têm a mesma dimensão! Repare-se que na guarda do if é necessário comparar dois vectores, a dimensão de A e a dimensão de B. Esta comparação é feita através da função isequal e não através da comparação usual ~=. Considerem-se os seguintes exemplos: size(A)~=size(C) ans = 0 1 ~isequal(size(A),size(B)) ans = 0 O resultado pretendido é o correspondente à avaliação da segunda expressão, um valor booleano (neste caso falso) e não o resultante da avaliação da primeira expressão, um vector de valores booleanos. O produto de matrizes é um pouco mais complicado. function R=prodMatrizes(A,B) %prodMatrizes(A,B) calcula o produto da matriz A pela matriz B [LA,CA]=size(A); [LB,CB]=size(B); if CA~=LB fprintf('Erro! As matrizes não podem ser multiplicadas!\n'); else I=1; R=zeros(LA,CB); while I<=LA J=1; while J<=CB K=1; X=0; while K<=CA X=X+A(I,K)*B(K,J); K=K+1; end R(I,J)=X;

13

J=J+1; end I=I+1; end end A variável I percorre as linhas da matriz A e a variável J percorre as colunas da matriz B. Para cada linha I de A e coluna J de B, a variável K percorre a linha I e a coluna J (que são da mesma dimensão) e multiplica o elemento de A com o elemento de B e soma a X. No fim, o resultado é inserido na linha I, coluna J da matriz resultado. A A = 1 3 3 2 2 1 C C = 1 2 3 2 prodMatrizes(C,A) ans = 5 7 5 7 13 11 C*A ans = 5 7 5 7 13 11 prodMatrizes(A,C) Erro! As matrizes não podem ser multiplicadas! A*C �??? Error using ==> * Inner matrix dimensions must agree. Vectores como listas Uma lista é uma colecção de objectos. Pode ser manipulada pelas operações append, que dada uma lista e um objecto, acrescenta esse objecto no fim da lista, prepend, semelhante ao append, mas acrescenta o objecto no início da lista, first, que devolve o primeiro elemento da lista, last, que devolve o último elemento da lista, length, que devolve o comprimento da lista, isempty, que testa se a lista é ou não a lista vazia, entre muitas outras operações que serão ilustradas adiante. Em MATLAB, podemos implementar listas sobre vectores, como se ilustra a seguir. Algumas das operações pretendidas são disponibilizadas pelo MATLAB: L=[2,4,6,8,10] L = 2 4 6 8 10

14

length(L) ans = 5 isempty(L) ans = 0 isempty([]) ans = 1 Outras funções, que não estão disponíveis no MATLAB, podem ser implementadas da seguinte forma: function X=first(L) %first(L) devolve o primeiro elemento da lista L if isempty(L) fprintf('Erro! Lista vazia!\n'); else X=L(1); end Esta função, quando aplicada a uma lista, devolve o primeiro elemento dessa lista. function W=rest(L) % rest(A) devolve a lista L sem o primeiro elemento if isempty(L) fprintf('Erro! Lista vazia.\n') else W=L(2:length(L)); end Esta função, quando aplicada a uma lista, devolve a lista sem o primeiro elemento. rest(L) ans = 4 6 8 10 L L = 2 4 6 8 10 rest([]) Erro! Vector vazio. function W=append(L,X) %append(A,X) acrescenta X no fim da lista L W=L; W(length(L)+1)=X; append(L,12) ans = 2 4 6 8 10 12

15

append([],1) ans = 1 function W=prepend(L,X) %prepend(A,X) acrescenta X do inicio da lista L W=X; W(2:length(L)+1)=L; prepend(L,0) ans = 0 2 4 6 8 10 prepend([],1) ans = 1 Considere-se agora o exemplo, já apresentado anteriormente para vectores, de somar os elementos de uma lista. As soluções apresentadas também funcionam no caso das listas, pois estas também dispõem de acesso directo às posições. No entanto, é possível definir novas versões sem recorrer a este tipo de operações, utilizando apenas as operações anteriores. A primeira versão é uma solução em estilo imperativo do problema (somaLista.m): function R=somaLista(L) %somaLista(L) soma os elementos da lista L R=0; while ~isempty(L) R=R+first(L); L=rest(L); end somaLista(L) ans = 30 Esta função soma os elementos de L da seguinte forma: inicialmente R=0 e L contém os elementos a somar e, enquanto houver elementos em L, o primeiro desses elementos é somado a R e eliminado de L. Este processo repete-se até todos os elementos terem sido eliminados de L e, consequentemente, somados a R. A solução recursiva do problema é semelhante à apresentada para o caso vectorial. Neste caso, no entanto, não é necessária a função auxiliar, sendo a recursão feita no comprimento da lista, isto é, a soma dos elementos de uma lista é a soma do primeiro elemento com a soma dos restantes (somaListaR.m): function R=somaListaR(L) %somaLista(L) soma os elementos da lista L, recursivamente if isempty(L) R=0; else R=first(L)+somaListaR(rest(L)); end somaListaR(L)

16

ans = 30 O próximo exemplo consiste em definir uma função que dada uma lista devolva a sublista dos números primos que ocorrem na lista argumento. Começa-se por apresentar uma solução em estilo imperativo (selPrimos.m): function W=selPrimos(L) %selPrimos(A) calcula a lista dos números primos que ocorrem em L. W=[]; while ~isempty(L) if isprime(first(L)) W=append(W,first(L)); end L=rest(L); end L=[1:10] L = 1 2 3 4 5 6 7 8 9 10 selPrimos(L) ans = 2 3 5 7 Esta função percorre a lista L e cada vez que encontra um número primo guarda-o em W. Em alterna-tiva, apresenta-se uma versão recursiva. function W=selPrimosR(L) %selPrimosR(A) calcula a lista dos números primos que ocorrem em L, %recursivamente if isempty(L) W=[]; else if isprime(first(L)) W=prepend(selPrimosR(rest(L)),first(L)); else W=selPrimosR(rest(L)); end end selPrimosR(L) ans = 2 3 5 7 Esta função pode ser generalizada de modo a seleccionar os elementos de uma lista de acordo com um predicado arbitrário. Apresenta-se a versão imperativa: function W=seleccionaI(L,P) %seleciona(L,P) selecciona os elementos da lista A para os quais %o predicado P seja verdadeiro W=[]; while ~isempty(L) if feval(P,first(L)) W=append(W,first(L)); end L=rest(L); end

17

seleccionaI(L,'isprime') ans = 2 3 5 7 L1=[1,2.1,3,4.2] L1 = 1.0000 2.1000 3.0000 4.2000 seleccionaI(L1,'nat') ans = 1 3 Outra função interessante é a função insert, que recebe como argumentos uma lista l, um objecto x e uma posição p e devolve a lista que corresponde a inserir o objecto x na posição p da lista l. Outros algoritmos de ordenação Seja W1 uma lista de números. O método da inserção permite ordernar a lista W1 do seguinte modo: constrói-se a lista ordenada W2 começando pelo primeiro elemento de W1 e depois inserindo cada elemento do resto de W1 no lugar devido em W2. Considere-se a seguinte solução imperativa com efeitos colaterais(insertSortL.m). function insertSortL() %insertSort() ordena o vector em W1 deixando o resultado em W2 global W1 W2 W2=[W1(1)]; I=2; while I<=length(W1) insere(W1(I)); I=I+1; end function insere(X) %insere(X) insere o elemento X em W2 de modo a manter %a lista W2 ordenada global W2 I=1; SINAL=1; while SINAL & I<=length(W2) if X>W2(I) I=I+1; else SINAL=0; end end; W2(I+1:length(W2)+1)=W2(I:length(W2)); W2(I)=X; global W1 W2 W1=[4,7,2,3,1] W1 = 4 7 2 3 1

18

insertSort W2 W2 = 1 2 3 4 7