Algoritmos – Modularização Modularização Sempre é possível dividir problemas grandes e complicados em problemas menores e de solução mais simples. A decomposição de um problema é fator determinante para a redução da sua complexidade. Um algoritmo que envolve um problema grande pode ser dividido em um algoritmo principal e em diversos subalgoritmos ou módulos (tantos quantos forem necessários ou convenientes). Modularização O algoritmo principal é aquele por onde começa a execução, e chama, eventualmente, os demais subalgoritmos. Subalgoritmo é um algoritmo que, geralmente, resolve um pequeno problema, e que está subordinado a um outro algoritmo que solicitará seu acionamento. É possível que um subalgoritmo chame outro subalgoritmo. Construindo sub-algoritmos Critérios para orientar o processo de decomposição. Dividir o problema em suas partes principais. Analisar a divisão obtida para garantir coerência. Se alguma parte ainda permanecer complexa, subdividi-la mais. Analisar o resultado para garantir entendimento e coerência. Vantagens da Modularização Dividir problemas grandes em vários problemas menores, de baixa complexidade. Número pequeno de variáveis Poucos caminhos de controle (caminhos do início ao fim) Possibilidade de utilizar-se soluções gerais para classes de problemas em lugar de soluções específicas para problemas particulares. Reusabilidade Solucionar uma única vez o problema Vantagens da Modularização Permite delimitar o escopo (nivel de abrangência) de variáveis. Variáveis locais. Evita a repetição, dentro de um mesmo algoritmo, de uma sequência de ações em diferentes pontos. Variáveis globais e locais Todo módulo é constituído por um sequência de comandos que operam sobre um conjunto de variáveis que podem ser globais ou locais. Variáveis globais : Podem ser usadas em módulos internos a outro módulo do algoritmo onde foram declaradas. Variáveis locais: Podem ser usadas no módulo do algoritmo onde foram declaradas. Elas não possuem significado fora deste módulo. Variáveis globais e locais Uma variável local é criada (alocada na memória) no momento em que o sub-algoritmo que a define é chamado Uma variável local é liberada da memória no momento em que o sub-algoritmo que a define termina Uma variável local somente existe (só pode ser utilizada) dentro do subalgoritmo que a define Caso um mesmo identificador (nome de variável) seja declarado em sub-algoritmos distintos, esses identificadores são considerados distintos entre si (variáveis distintas) O uso de variáveis locais minimiza a ocorrência de “efeitos colaterais” : o programador pode definir e utilizar as variáveis que desejar em um subalgoritmo sem interferir com outros subalgoritmos Tipos de sub-algoritmos Sintaxe de um algoritmo modularizado Algoritmo <nome> Definição de tipos Declaração de variáveis globais Definição de módulos Inicio Conjunto de ações do algoritmo principal (incluidas as chamadas aos módulos) Fim Tipos de Sub-algoritmos: Funções (functions) Procedimentos (procedures) Procedimentos Procedimento: Um conjunto de ações que não irá devolver valores ao (sub)algoritmo chamador. Forma geral de um procedimento (sintaxe): Procedimento <nome>(<parâmetros>) Declaração de variáveis locais do procedimento Inicio Comandos do procedimento Fim Procedimentos Chamada de um procedimento (sintaxe): Nome_procedimento(argumentos) Quando o nome de um procedimento é encontrado, ocorre um desvio no (sub)algoritmo para que os comandos do procedimento sejam executados. Ao término do subalgoritmo, a execução retornará ao ponto subsequente a da chamada do Procedimento. Procedimentos Juntando definição e chamada de um procedimento : Algoritmo <nome_algoritmo> Definição de tipos Declaração de variáveis globais Procedimento <nome_procedimento>(<parâmetros>) Declaração de variáveis locais do procedimento Inicio Comandos do procedimento Fim /* algoritmo principal*/ Inicio Comandos do algoritmo principal nome_procedimento(argumentos) Comandos do algoritmo principal Fim Procedimentos Exemplo 1: Faça um algoritmo que dado um valor real global x, chame um procedimento que calcula o quadrado de x. Algoritmo <Quad> real: x Procedimento Quadrado() real: z Início z ← x*x Escreva (“O quadrado do número é =“,z) Fim Início Escreva (“Digite um número: “) Leia ( x ) Quadrado() Fim Procedimentos Exemplo 2 (muito simples com finalidade de explicar a diferença entre variáveis locais e globais) : Faça um algoritmo que use um procedimento para ler o nome de uma pessoa e outro para mudá-lo. Algoritmo <EscreveNome> literal: nome Procedimento le_nome() Início Leia (nome) Fim Procedimento muda_nome() Inicio escreva (“Vamos mudar o nome”) leia (nome) Fim Início Le_nome Escreva (nome) Muda_nome Escreva (nome) Procedimentos Exemplo 3 (muito simples com finalidade de explicar a diferença entre variáveis locais e globais) : Faça um algoritmo que use um procedimento para ler o nome de uma pessoa e outro para mudá-lo (use nome como var local) Algoritmo <EscreveNome> literal: nome Procedimento le_nome() Início Leia (nome) Fim Procedimento muda_nome() literal:nome Inicio escreva (“Vamos mudar o nome”) leia (nome) Fim Início Le_nome Escreva (nome) Muda_nome Escreva (nome) Procedimentos No exemplo 3, a variável global nome e a variável local nome representam posições de memória totalmente diferentes, logo, qualquer mudança no conteúdo da variável local, não afetará o conteúdo da variavel global. Parâmetros Parâmetros são canais pelos quais se estabelece uma comunicação bidirecional entre um subalgoritmo e o algoritmo chamador (algoritmo principal ou outro subalgoritmo). Os dados são passados pelo algoritmo chamador através de argumentos (parâmetros reais), e são recepcionados por meio de parâmetros formais. Parâmetros Formais: São os nomes simbólicos introduzidos no cabeçalho dos subalgoritmos, usados na definição dos parâmetros do mesmo. Dentro de um subalgoritmo trabalha-se com estes nomes da mesma forma como se trabalha com variáveis locais ou globais. Parâmetros Reais (ou argumentos):São aqueles que substituem os parâmetros formais quando da chamada do subalgoritmo. Parâmetros Passagem de parâmetros Por valor ("by value") O argumento da chamada (parâmetro real) é avaliado, gerando um valor que é copiado para a variável declarada na função (parâmetro formal) Qualquer alteração do parâmetro formal não é "transmitida" para o a variável do argumento O argumento da chamada (parâmetro real) pode ser uma constante, uma variável ou uma expressão: 5, v1, v1+5-v2 Parâmetros Exemplo: Algoritmo <teste> Inteiro:x Procedimento porvalor(inteiro:a) Inicio a←5 Fim Inicio x ← 10 porvalor(x) escreva (x) Fim Parâmetros por referência ("by reference") O argumento da chamada (parâmetro real) tem que ser uma variável: v1, v2 ... A variável do argumento (parâmetro real) é associada com a variável declarada no subalgoritmo (parâmetro formal) durante a execução do subalgoritmo. Qualquer alteração da variável do subalgoritmo (parâmetro formal) acontece também na variável do argumento. Parâmetros Exemplo: Algoritmo <teste> Inteiro:x Procedimento porreferencia(inteiro:&a) Inicio A←5 Fim Inicio x ← 10 porreferencia(&x) escreva (x) Fim * Na nossa matéria, o símbolo & indicará a passagem por referência, na definição do procedimento, e também na chamada do mesmo. Funções Função O conceito de funções é originário da idéia de função matemática, onde um valor é calculado a partir de outro(s) valor(es) fornecido(s) à função. Forma geral de uma função (sintaxe): Função tipo <nome>(<parâmetros-formais>) Declaração de variáveis locais da função Inicio Comandos Fim onde, tipoé o tipo do valor que será retornado lista-de-parâmetros-formais é a lista das variáveis (com seus tipos) que recepcionam as variáveis fornecidas quando da chamada da função Funções Chamada de uma função (sintaxe): nome(lista-de-parâmetros-reais) onde, lista-de-parâmetros-reais é a lista das variáveis que se corresponderão com os parâmetros formais durante a execução da função. Os parâmetros atuais devem concordar em números, ordem e tipo com os parâmetros formais. Exemplo: Funções Exemplo: Faça um algoritmo que dado um valor real x, chame uma função que retorne o quadrado de x. Algoritmo <Quad> real: x, y Função real quadrado(real:w) real: z Início z ← w*w Retorne (z) Fim Início Escreva (“Digite um número: “) Leia ( x ) y ← quadrado (x) Escreva (“ y = “ , y ) Fim Funções INSTRUÇÃO Retorne A instrução Retorne é um comando simples usado apenas nas funções e tem o efeito de parar a execução da função e enviar um valor para o algoritmo principal ou para outro subalgoritmo que o tenha chamado. Toda função deve ter em seu corpo de instruções, pelo menos, uma instrução Retorne. Sintaxe: Retorne ( <expressão> ) Exemplos: Retorne ( area ) Retorne ( pi*r*r ) Funções Faça uma função para determinar se um número inteiro é par ou não. Utilize esta função para calcular dois somatórios: um com os números pares e outro com os números ímpares, sendo dados os n números inteiros positivos. Faça um algoritmo que leia n pontos no plano e determine se os pontos estão dentro, fora ou sobre uma circunferencia de raio R e centro em (h,k). Funções e Procedimentos Faça um algoritmo que realize o cálculo dos atrasos e horas trabalhadas (mensais) de um empregado (cartão de ponto). Algoritmo <cartaodeponto> Tipo dia = registro Inteiro : em,sm, et, st Fim registro Tipo totDia = registro Inteiro: atraso,horas Fim registro dia: cartao[31] totDia: totalDia[31] Inteiro: cont,i,toth,totatr Procedimento entrada () inteiro: dia Inicio cont ←0 leia (dia) enquanto (dia>0 e dia <32) faça leia (cartao[dia].em, cartao[dia].sm,cartao[dia].et,cartao[dia].st) cont←cont+1 leia (dia) fim enquanto Fim Funções e Procedimentos Função inteiro minuto(inteiro:H) Inteiro: m Inicio m←(H div 100)*60 + H mod 100 retorne(m) Fim Função inteiro atraso(inteiro: H, periodo) Inteiro:a Inicio a ←minuto(H)-periodo retorne(a) Fim Função inteiro total (inteiro: HE,HS) Inteiro: t Inicio t ←minuto(HS)-minuto(HE) retorne(t) Fim Funções e Procedimentos Procedimento calculo() Inicio Para i de 1 até cont repita totalDia[i].atraso ← atraso(cartao[i].em,480)+atraso(cartao[i].et,840) totalDia[i].horas ← total(cartao[i].em,cartao[i].sm)+total(cartao[i].et,cartao[i].st) toth ← toth+totalDia[i].horas totatr ← totatr+totalDia[i].atraso Fim Procedimento impressão() Inicio Para i de 1 até cont repita escreva (cartao[i].em,cartao[i].sm) escreva(cartao[i].et, cartao[i].st) escreva (totalDia[i].horas div 60) escreva (totalDia[i].horas mod 60) escreva (totalDia[i].atraso div 60) escreva (totalDia[i].atraso mod 60) FimPara escreva ((toth/cont)div 60,(toth/cont)mod 60) escreva ((toth div 60, toth mod 60) escreva ((totatr/cont)div 60, (totatr/cont)mod 60) Funções e Procedimentos Inicio Entrada() Se (cont>0) então calculo() impressão() Fim se Fim Funções e Procedimentos Faça uma função que recebe a idade de uma pessoa em anos, meses e dias e retorna essa idade expressa em dias. Função inteiro idadedias(inteiro:anos, meses,dias) inteiro: diast Inicio diast←anos*365+meses*30+dias retorne(diast) Fim Funções e Procedimentos Faça uma função que verifique se um valor é perfeito ou não. Um valor é dito perfeito quando ele é igual a soma dos seus divisores excetuando ele próprio (Ex. 6´é perfeito, 6=1+2+3, que são seu divisores). A função deve retornar um valor booleano. Função logico perfeito (inteiro: num) inteiro:soma,i Inicio soma ←0 para i de 1 até num/2 repita se (mod(num,i)=0) soma←soma+i fim se fim para se (soma=num) retorne(1) senão retorne(0) Fim Funções e Procedimentos Foi realizada uma pesquisa de algumas características físicas de 50 habitantes de uma certa região. De cada habitante foram coletados os seguintes dados: sexo, cor dos olhos (azuis, verdes ou castanhos), cor dos cabelos (louros, pretos ou castanhos) e idade. Faça um procedimento que leia esses dados em um vetor de registro. O vetor de registro deve ser enviado por referência. Procedimento leia (habitante:&dados[50]) inteiro: i Inicio Para i de 1 até 50 repita leia(dados[i].sexo,dados[i].cor_olhos,dados[i].cor_cab) leia(dados[i].idade) Fim para Fim Nota: No algoritmo principal deve ser definido o tipo habitante. Funções e Procedimentos Faça um procedimento que receba o vetor de registro definido no exercício anterior, por parâmetro, e retorna também por parâmetro: a maior idade entre os habitantes e a quantidade de individuos do sexo feminino cuja idade está entre 18 e 35 (inclusive) e que tenham olhos verdes e cabelos louros. Procedimento informacoes(habitante:dados[50], &maioridade,&soma) inteiro: i Inicio soma←0 maioridade ← 0 Para i de 1 até 50 repita se (dados[i].idade>maioridade) maioridade ← dados[i].idade se (dados[i].sexo=“F” e dados[i].idade>=18 e dados[i].idade<=35 e dados[i].cor_olhos=“verdes” e dados[i].cor_cab=“louros”) soma ←soma+1 fim se Fim para Funções e Procedimentos 1. 2. 3. Faça uma função para calcular o máximo divisor comum (MDC) de dois numeros dados como parâmetros. Sabe-se que o MDC tem as seguintes propriedades : MDC(x,y)=MDC(x-y,y), se x>y MDC(x,y)=MDC(y,x) MDC(x,x)=x Exemplos MDC(24,8)=MDC(16,8)=MDC(8,8)=8 MDC(13,4)=MDC(9,4)=MDC(5,4)=MDC(1,4)=MDC(4,1)= MDC(3,1)=MDC(2,1)=MDC(1,1)=1 MDC(13,5)=MDC(8,5)=MDC(3,5)=MDC(5,3)=MDC(2,3)= MDC(3,2)=MDC(1,2)=MDC(2,1)=MDC(1,1)=1 Funções e Procedimentos Função inteiro mdc (inteiro: x,y) Inicio enquanto (x<>y) enquanto (x>y) x ←x-y enquanto (y>x) y ←y-x fim enquanto retorne(x) fim Funções e Procedimentos Um banco está informatizando seus controles de clientes e contas. No primeiro momento o banco deseja guardar as informações de até 20000 clientes. Cada cliente tem os seguintes dados: Nome, idade, endereço, número de suas contas (15 no máximo) e CGC. As contas válidas tem número diferente de 0. Cada conta possui um só cliente. As informações das contas são as seguintes: numero, cliente, tempo de cliente e saldo atual. (Se existem 2000 clientes com 15 contas no máximo então devem existir 30000 contas). (a) Definição das estruturas de dados e também das variáveis utilizadas. Supondo que as estruturas de dados já contêm as informações armazenadas, crie: (b) Uma função que retorne o número de clientes com saldo negativo em mais de uma conta. Uma função que retorne o número de clientes que abriram conta há mais de 10 anos e que tenham idade menor que 30 anos. (c) Funções e Procedimentos (a) tipo dadosconta=registro inteiro numero,CGC,tempo real: saldo fim registro tipo dadoscliente=registro literal:nome,idade,endereço inteiro:nrocontas,CGC dadosconta:contas[15] fim registro dadoscliente:clientes[20000] Funções e Procedimentos (b) Função inteiro saldonegativo(dadoscliente:clientes[],numclientes) inteiro i,cont,flag inicio cont ←0 para i de 1 até numclientes repita flag ←0 j ←1 enquanto (flag<=1 e j<=clientes[i].nrocontas) faça se (clientes[i].contas[j].saldo<0) flag←flag+1 fim se j←j+1 fim enquanto se (flag>1) cont←cont+1 fim se fim para retorne(cont) fim Funções e Procedimentos (c) Função inteiro conta2(dadoscliente:clientes[],numclientes) inteiro i,cont,flag inicio cont ←0 para i de 1 até numclientes repita flag←0, j ←1 se (clientes[i].idade<30) então enquanto (!flag e j<=clientes[i].nrocontas) faça se (clientes[i].contas[j].tempo>10) então flag ←1 fim se j←j+1 fim enquanto se (flag) cont←cont+1 fim se fim se fim para retorne(cont) Funções e Procedimentos Resolver os exercícios propostos do Capítulo 3 (Modularização) do livro de Harry Farrer. Funções e Procedimentos Para encontrar uma raiz do polinômio p( x) a0 a1x a2 x2 ... an xn , n 2, pode-se aplicar o método de Newton que consiste em refinar uma aproximação inicial x0 dessa raíz através da expressão p ( xn ) xn 1 xn p ' ( xn ) n=0,1,2,…, onde p ' ( x) é a primeira derivada de p(x). Usualmente, repete-se esse refinamento até que | xn1 xn | , 0, ou até que m iterações tenham sido executadas. Dados um polinômio p(x), uma aproximação inicial x0 da raiz de p(x), 0 é o número máximo de iterações que devem ser executadas, determine uma aproximação da raíz de p(x) pelo método de Newton. Utilize um módulo que, dado um polinômio p(x), calcule a derivada p’(x) e, para calcular p(xn) e p’(xn) em cada iteração, um módulo que calcule o valor de um polinômio em um ponto. Funções e Procedimentos Algoritmo raizes real :xanterior,x,epsilon,a[100],b[100] inteiro :i,n,numeromaximodeiteracoes,iteracoes procedimento derivada (real: a[],real:&b[],inteiro:n) inteiro:i início para i de 1 até n repita b[i]←i*a[i+1] fim função real valor_polinômio(real:a[],real:x,inteiro:n) real:valor inteiro:i Início valor ←a[1] para i de 2 até n+1 repita valor ←valor + a[i]*x**(i-1) fim para retorne(valor) Funções e Procedimentos Início /* algoritmo principal*/ leia(n) para i de 1 até n+1 repita leia (a[i]) fim para leia (xanterior) leia (epsilon) leia (nromaximodeiteracoes) derivada(a,&b,n) iteracoes ←0 x ← xanteriorvalor_polinomio(a,xanterior,n)/valor_polinomio(b,xanterior,n-1) enquanto(abs(x-xanterior)>=epsilon e iteracoes<=nromaxiteracoes) x ← xanteriorvalor_polinomio(a,xanterior,n)/valor_polinomio(b,xanterior,n-1) iteracoes ←iteracoes+1 fim enquanto Funções e Procedimentos Escreva um algoritmo que solucione o problema de preenchimento das vagas nos cursos de uma universidade. Cada aluno que prestou vestibular em uma determinada universidade, originou um registro com os seguintes campos: número de inscrição, nota geral obtida (de 0.0 a 10.0) e código do curso para o qual ele se candidatou. A universidade oferece 5 cursos com 50 vagas cada. O problema consiste em distribuir os candidatos entre os cursos, de acordo com a nota final e com a opção apresentada pelo candidato. Em caso de empate, será atendido primeiro, o candidato com menor número de inscrição. Sabe-se que o final de dados será determinado pelo campo de inscrição negativo ou por ter alcançado o número de 30000 candidatos, que é o máximo permitido pelo regulamento da universidade. Observação: os resultados dos alunos são lidos independente da sua classificação, por isso a distribuição deve também ordenar os alunos nos cursos.