• Nenhum resultado encontrado

> cap1 Introdução à linguagem Scheme

N/A
N/A
Protected

Academic year: 2021

Share "> cap1 Introdução à linguagem Scheme"

Copied!
33
0
0

Texto

(1)

Introdução à linguagem Scheme

> sec1-1 Números > sec1-2 Expressões > sec1-3 Identificadores > sec1-4 Procedimentos Compostos > sec1-5 Varáveis locais > sec1-6 Estruturas de Selecção > sec1-7 Exercícios e Exemplos > sec1-8 Resumo

A linguagem Scheme, apesar de poderosa, caracteriza-se por ter uma sintaxe simples, com poucas regras, que não exige nem muito tempo de aprendizagem (do aluno) nem muito espaço (de manuais), o que já não se poderá afirmar da generalidade das

linguagens de programação. Assim, o esforço e atenção do professor e alunos incidirão,

sobretudo, na programação e não tanto na ferramenta a utilizar na programação. Neste capítulo, faz-se uma breve introdução à linguagem Scheme, colocando desde logo ao nosso alcance a escrita de procedimentos e programas simples. Depois deste capítulo, a sintaxe do Scheme resumir-se-á, fundamentalmente, a notas pontuais ao longo de outros capítulos e a referências a um dos anexo (Anexo A).

> sec1-1

Números

Os Números são dados simples disponibilizados pela linguagem Scheme, que representam o próprio valor que devolvem. Por exemplo, o número 10, quando integrado numa expressão, devolve o valor 10, como seria de esperar. O número 35E2 é a representação exponencial do valor 3500, pois equivale a 35 x 102 (o

expoente 2 é o número à direita de E), enquanto que o número 35E-3 representa o valor 0.035, por ser equivalente a 35 x 10-3.

A partir deste momento, mas de uma forma muitíssimo limitada, já seria possível dialogar com o computador, através da linguagem Scheme. Para isso, torna-se necessário pôr em funcionamento um programa que entenda a sintaxe desta linguagem, e que poderá ser um interpretador1 de Scheme. O interpretador coloca-se disponível para responder ao utilizador, pois apresenta-se num ciclo permanente de

1 Optou-se, em todos os exemplos apresentados, pela utilização do interpretador DrScheme, por se

tratar de uma excelente ferramenta de origem universitária, disponível para várias plataformas, e distribuída gratuitamente em: http://www.cs.rice.edu/CS/PLT/packages/drscheme/download.html

(2)

leitura-cálculo-escrita, que se identifica através da visualização no ecrã de algum caracter

especial2, como se indica de seguida.

>

Este caracter significa que o Scheme está preparado para ler uma expressão, fornecida pelo utilizador.

> 10

A expressão, neste caso o número 10, após a actuação da tecla return, é calculada e o respectivo valor é visualizado no ecrã.

> 10

10 Vejamos outros exemplos.

> +10 10 > 35E2 3500.0 > 35E-2 0.35

> sec1-2

Expressões

Os exemplos apresentados referiam-se apenas a Expressões Simples, mas a maioria das situações que iremos encontrar exigem Expressões Compostas.

A linguagem Scheme disponibiliza vários Procedimentos Primitivos, assim designados por fazerem parte da definição da própria linguagem, entre os quais se salientam os

operadores aritméticos:

+ adição - subtracção * multiplicação / divisão

Nesta linguagem, as expressões utilizam uma notação pré-fixa, em que o operador aparece antes dos operandos. No primeiro exemplo que se segue, o operador aritmético da adição surge em primeiro lugar, seguido dos operandos 10 e 25.

> (+ 10 25) 35 > (+ 10 25 37) 72 > (- 10 15) -5 > (- 10 15 5) -10

Estes exemplos são Expressões Compostas. Apresentam-se rodeadas por parêntesis e, dentro destes, aparece em primeiro lugar o operador, seguido dos vários operandos.

2 Sem recorrer à tradução, este caracter é designado por prompt, e vai ser representado por >. No

diálogo entre o utilizador e o interpretador, a parte que cabe a este último será representada a cheio

(3)

A regra de cálculo de uma expressão composta segue os passos seguintes: ⇒ Em primeiro lugar, calcular cada um dos operandos da expressão;

⇒ Seguidamente, aplicar o operador aos operandos calculados.

> (* 2 6 30) 360

Do cálculo dos operandos 2, 6, e 30, resultam, respectivamente, os valores 2, 6 e 30. Aplicando-lhes o operador *, o valor resultante será 360.

> (+ 12 (- 45 38)) 19

Esta expressão apresenta dois operandos que serão previamente calculados, antes de se aplicar o operador +. O primeiro operando devolve imediatamente 12, pois é um número. Mas o segundo, (- 45 48), requer um pouco mais de trabalho. Este

operando é, ele próprio, uma expressão composta exigindo, por seu turno, a aplicação da regra de cálculo de uma expressão composta: o cálculo dos seus operandos, 45 e 38, seguido da aplicação do operador, o procedimento primitivo -, de onde resulta o valor 7.

Finalmente, estando calculados os operandos da expressão inicial, 12 e 7, será aplicado o operador, o procedimento primitivo +, resultando 19.

> (Exercício 1.1)

Indicar como responderá o Scheme às expressões:

> (* (+ 34 30) 6) ? > (+ 23 (* 3 5 3) (- 22 2 34)) ? > (* (* 34 (- 4 5)) (+ 12 (/ 15 3))) ?

Quando uma expressão se torna de leitura complicada, o melhor será representá-la de uma forma mais adequada, alinhando verticalmente os operandos de cada um dos operadores da expressão3. A última expressão do Exercício 1.1 poderá então

apresentar uma forma muito mais legível.

(* (* 34 (- 4 5)) (+ 12 (/ 15 3)))

O desenvolvimento desta expressão é indicado de seguida.

(* (* (* (* 34 (* 34 -34 (- 4 -1) 5)) -578 (+ 12 (+ 12 17) (/ 15 5)) 3)))

3 Este tipo de formatação é normalmente disponibilizado quando se trabalha com o Scheme no

(4)

> sec1-3

Identificadores

É necessário, muitas vezes, definir novos entidades dando-lhes nomes. Assim é, por exemplo, quando se pretende definir novos procedimentos para além dos procedimentos primitivos ou, simplesmente, criar um identificador.

> (define lado-da-casa 16)

lado-da-casa

O identificador lado-da-casa acabou de ser definido, tendo-se-lhe associado o valor 16. Isto poderá ser verificado ao pedir o cálculo do seu valor.

> lado-da-casa 16

Acabámos de utilizar define, uma das formas especiais do Scheme, que se representa agora na forma genérica.

(define identificador expressão)

Cada forma especial, como o próprio nome indica, tem no Scheme uma regra de cálculo especial. No caso de define, a regra é a seguinte:

⇒ Começar por calcular expressão;

⇒ Depois associar o valor de expressão ao identificador.

Quando se associa um dado numérico a um nome, é porque se deseja trabalhar com nomes e não directamente com números, o que significa trabalhar a um nível um pouco mais elevado. Por exemplo, o número 16 poderá significar muita coisa e, por isso mesmo, pouco nos dirá quando aparece numa expressão. Mas, pelo contrário, se em vez de 16 surge o nome lado-da-casa, inserido num certo contexto, poder-se-á imaginar do que se trata. Isto é um exemplo simples de uma Abstracção de Dados, conceito que, pela sua enorme importância em programação, será retomado noutro capítulo.

O identificador lado-da-casa ficou ligado ou associado ao valor 16 e poderá ser incorporado em expressões.

> (+ lado-da-casa 3) 19

> (* 3 lado-da-casa -1) -48

Quanto aos identificadores, a regra de cálculo é:

⇒ Se um identificador está ligado a um valor, é devolvido esse valor; ⇒ Caso contrário, é assinalada uma mensagem de erro.

> (* 3 lado-da-moradia)

reference to undefined identifier: lado-da-moradia

De facto, ao identificador lado-da-moradia não tinha sido previamente associado um valor, situação que foi oportunamente identificada e assinalada pelo Scheme, com uma mensagem apropriada.

(5)

> sec1-4

Procedimentos Compostos

Para além dos procedimentos primitivos, é possível criar novos procedimentos, designados por procedimentos compostos. Em vez de ligar um número a um identificador, como se fez em lado-da-casa, vamos agora associar-lhe uma tarefa específica. Assim, quando o programador pretender executar essa tarefa, não terá mais do que chamá-la através do identificador respectivo, ou seja, através do seu nome. Em relação a este assunto, devemos distinguir muito bem duas fases: A

definição do procedimento e a chamada do procedimento.

Antes das formas genéricas utilizadas na definição e na chamada de procedimentos compostos, optou-se pela apresentação de um exemplo que deverá ser analisado com muita atenção.

O procedimento quadrado, que se indica de seguida, é muito simples: Toma um valor numérico qualquer e devolve o quadrado desse valor.

(define quadrado ; nesta linha, é definido o nome do procedimento (lambda (x) ; x é o único parâmetro do procedimento

(* x x))) ; aqui é definida a tarefa associada ao procedimento

Na definição de novos procedimentos recorremos ao procedimento primitivo lambda que não devolve um valor numérico, como acontecia com os operadores aritméticos, mas devolve, isso sim, um procedimento que realiza uma tarefa bem definida. Foi assim que o identificador quadrado, através de lambda, passou a estar associado a uma tarefa específica: calcular e devolver o quadrado de um valor x.

Na definição deste procedimento, o valor a elevar ao quadrado, por poder ser um valor numérico qualquer, foi representado simbolicamente por x. Diz-se, por isso, que x é um parâmetro do procedimento quadrado, neste caso, até se trata do único parâmetro do procedimento.

Numa fase posterior, ou seja, depois do procedimento estar completamente definido, a tarefa que lhe está associada será executada quando o procedimento é chamado. A chamada do procedimento quadrado faz-se colocando entre parêntesis o nome do procedimento, seguido do valor que se pretende processar e que será associado ao parâmetro. Esse valor, associado ao parâmetro, é geralmente designado por argumento.

> (quadrado 5) 25

Na chamada (quadrado 5), o valor 5 é o argumento. Este valor será associado ao

parâmetro x, sendo devolvido (* 5 5) igual a 25, pois a tarefa executada

corresponde a (* x x).

Notar o seguinte: Na definição do procedimento quadrado, chamámos parâmetro a x, enquanto que na chamada do mesmo procedimento, chamámos argumento ao valor a associar a x.

Um procedimento, por si só, não faz nada. Trata-se apenas de uma porção de texto, mais ou menos legível. Apenas, ao ser chamado, o procedimento gera no interior do computador um processo, o qual vai requerer os recursos computacionais necessários

(6)

para a execução da tarefa que lhe está associada. O processo toma, como recursos,

espaço de memória para os dados (no último exemplo, espaço de memória

fundamentalmente para x) e toma também tempo do processador para os cálculos necessários. Podemos imaginar o processo gerado pela chamada (quadrado 5) e os

respectivos recursos computacionais, representados pelo rectângulo da figura. No final da chamada, o processo devolve o resultado calculado, 25, e morre, libertando os recursos computacionais que tinha tomado. A este assunto voltaremos em próximo capítulo, onde se procurará avaliar os recursos que cada processo requer de uma forma um pouco mais rigorosa. 5 quadrado x : 5 ( * x x) 25

Vejamos outra chamada do procedimento quadrado, com a consequente criação de um novo processo, idêntico ao anteriormente descrito.

> (quadrado (+ 1 3)) 16

Nesta chamada, o valor da expressão (+ 1 3) é associado a x, sendo devolvido o

valor de (* 4 4) igual a 16.

Depois deste exemplo, é apresentada a forma genérica para definir um procedimento:

(define nome-do-procedimento

(lambda (parâmetro-1 parâmetro-2 ...) corpo-do-procedimento ))

O procedimento primitivo lambda também é uma forma especial do Scheme, e a sua regra de cálculo é:

⇒ Os identificadores que se encontram a seguir a lambda, entre parêntesis, representam valores genérico e são designados por parâmetros do procedimento;

⇒ lambda devolve o procedimento definido pelo programador, com os parâmetros referidos e ao qual ficará associada a tarefa especificada em

corpo-do-procedimento.

Assim sendo, a forma especial define associa ao identificador nome-do-procedimento o procedimento especificado pelo programador em corpo-do-procedimento. Por exemplo, o nome quadrado ficou ligado a um procedimento, com o parâmetro x, cujo corpo é

(* x x).

(define quadrado ; quadrado é o nome do procedimento (lambda (x) ; x é o parâmetro do procedimento (* x x))) ; (* x x) é o corpo do procedimento

Depois da definição de um procedimento, segue-se a regra de cálculo da chamada.

(7)

⇒ Em primeiro, são calculados os argumentos que acompanham

nome-do-procedimento;

⇒ Seguidamente, no corpo do procedimento, os valores dos argumentos substituem os parâmetros respectivos (argumento-1 substitui parâmetro-1,

argumento-2 substitui parâmetro-2, ...);

⇒ Finalmente, é devolvido o valor da última expressão calculada no

corpo-do-procedimento.

É agora apresentada uma outra chamada do procedimento quadrado, na qual o cálculo do argumento implica uma outra chamada de quadrado.

> (quadrado (quadrado (+ 3 2))) 625

Neste caso, são criados dois processos do procedimento quadrado que, no entanto, não existem simultaneamente. O processo representado pelo rectângulo superior estará morto, quando o segundo estiver activo. A chamada em questão provocou a sequência de operações seguinte:

25 25 (+ 3 2)

(quadrado (quadrado (+ 3 2))) quadrado x : 25 ( * x x) 625 quadrado x : 5 ( * x x) > (quadrado (quadrado (+ 3 2))) (quadrado (quadrado 5)) (quadrado (* 5 5)) (quadrado 25) (* 25 25) 625 > (Exercício 1.2)

Escrever em Scheme o procedimento soma-os-dois-e-tira 5 com dois parâmetros. De acordo com o próprio nome, recebe dois valores como argumentos, calcula a soma dos dois e retira-lhe 5.

-> (soma-dois-e-tira-5 10 3) 8 > (soma-dois-e-tira-5 10 -3) 2 > (Exemplo 1.1)

Segue-se a definição do procedimento soma-dos-quadrados que toma dois valores como argumentos e devolve a soma dos quadrados desses valores.

O procedimento pedido pode ser visto com dois parâmetros, x e

y, recorrendo duas vezes ao procedimento quadrado, previamente

definido. É isto que se pretende esboçar na figura.

x x2 y y2

x y

quadrado quadrado

soma-quadrados Este tipo de figura ou diagrama de fluxo de dados tem a virtude de

mostrar a relação estabelecida entre os procedimentos utilizados e, sobretudo, põe em evidência os dados trocados entre eles. Recomenda-se a utilização destes diagramas quando a experiência de programação é pequena ou perante situações de complexidade elevada.

(8)

; Procedimento com dois parâmetros, x e y. ; Devolve a soma dos quadrados de x e y. ;

(define soma-dos-quadrados (lambda (x y)

(+ (quadrado x)

(quadrado y))))

O procedimento soma-dos-quadrados, por ter dois parâmetros receberá, em cada chamada, dois argumentos.

> (soma-dos-quadrados 1 2) 5

> (soma-dos-quadrados 5 (* 7 2)) 221

> (Exercício 1.3)

Apresentar o resultado e a sequência de operações com origem na chamada:

(soma-dos-quadrados 5 (soma-dos-quadrados 2 3))

Na definição de soma-dos-quadrados foi utilizado o procedimento quadrado, mas para isso, não seria necessário conhecer quadrado por dentro. Este procedimento pode ser visto como se fosse um bloco ou uma caixa-preta, em que entra um valor e sai o seu quadrado. Quando utilizámos quadrado, apenas nos interessou o que faz e não como o

faz. Estamos perante um exemplo simples de uma Abstracção Procedimental.

Outro aspecto interessante tem a ver com o facto de o procedimento quadrado utilizar x como nome do seu parâmetro, sem com isso impedir que soma-dos-quadrados utilizasse um parâmetro com a mesma designação x. Isto não causará qualquer confusão, pois os nomes dos parâmetros têm um significado muito bem localizado. Cada um deles apenas é reconhecido apenas no corpo do respectivo procedimento, sendo entidades distintas. Aliás, como vimos, sempre que o processo referente à chamada de um procedimento é criado, é reservado espaço próprio em memória para as entidades que irá processar, portanto, não se confundido com o espaço reservado por qualquer outro processo.

> (Exemplo 1.2)

Pretende-se desenvolver um procedimento em Scheme para auxiliar os apostadores de Totoloto4 no preenchimento de boletins.

O procedimento gera sequências aleatórias de 6 inteiros, situados entre 1 e 49 e, nesta versão, não é feita a verificação de inteiros repetidos. Assim, se na sequência gerada de 6 inteiros algum deles aparecer repetido, o que não poderá acontecer numa aposta do Totoloto, o melhor será deitar fora essa sequência e pedir outra.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

Neste procedimento, uma tarefa fácil de identificar é a geração aleatória de um inteiro entre 1 e 49, e é por ela que iremos começar. Para esta tarefa, vamos definir o procedimento designado por

roleta-1-49 que não tem parâmetros.

(9)

; Procedimento sem parâmetros.

; Devolve um inteiro entre 1 e 49, determinado aleatoriamente. ;

(define roleta-1-49 (lambda () (+ 1 (random 49))))

No corpo do procedimento roleta-1-49 é utilizada uma chamada ao procedimento

random5, (random 49), que devolve um inteiro aleatório entre 0 e 48. Adicionando-lhe 1, resulta um inteiro entre 1 e 49, como se pretendia.

> (roleta-1-49) 33

> (roleta-1-49) 8

O procedimento roleta-1-49 não tem parâmetros e por isso é chamado colocando o seu nome entre parêntesis, isolado e sem qualquer argumento. Este procedimento só pode ser utilizado para gerar números entre 1 e 49. E se se pretendesse, por exemplo, gerar números aleatórios entre 1 e 6, para simular o lançamento de um dado? Parece que seria necessário escrever um novo procedimento, provavelmente com o nome roleta-1-6.

; Procedimento sem parâmetros.

; Devolve um inteiro entre 1 e 6, determinado aleatoriamente. ;

(define roleta-1-6 (lambda ()

(+ 1 (random 6))))

Mas uma solução mais flexível teria sido definir, em vez de roleta-1-49 e roleta-1-6, por exemplo, roleta-1-n com um parâmetro n, para gerar aleatoriamente um inteiro entre 1 e n.

; Procedimento com o parâmetro n.

; Devolve um inteiro entre 1 e n, determinado aleatoriamente. ;

(define roleta-1-n (lambda (n)

(+ 1 (random n))))

Com este procedimento torna-se fácil simular lançamentos de um dado.

> (roleta-1-n 6) 5

> (roleta-1-n 6) 3

Mas também seria fácil simular a geração de números aleatório entre 1 e qualquer outro inteiro positivo. Por exemplo, para gerar um número do Totoloto:

> (roleta-1-n 49) 27

5 O procedimento random não faz parte da definição normalizada do Scheme e pode apresentar-se com

especificações diferentes para diferentes implementações. No DrScheme, random tem um argumento que deverá situar-se entre 1 e 231-1, ou seja, entre 1 e 2147483647. A chamada (random n) devolve um

(10)

Retomemos o procedimento roleta-1-49, e com ele definamos o procedimento aposta para gerar, por cada chamada, uma aposta de

Totoloto. A chamada (aposta) não inclui

argumentos, pois este procedimento vai ser definido sem parâmetros.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 > (aposta) Numero 1: 43 Numero 2: 3 Numero 3: 41 Numero 4: 32 Numero 5: 40 Numero 6: 10

Vejamos outra chamada do procedimento aposta e a

correspondente aposta gerada. 18 29 103 114 12 13 145 6 7

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 > (aposta) Numero 1: 49 Numero 2: 32 Numero 3: 6 Numero 4: 26 Numero 5: 19 Numero 6: 22

Verificar que do procedimento aposta não se espera a devolução de um valor, mas apenas um conjunto de mensagens no ecrã.

1..49 1..49 1..49 1..49 1..49 aposta role eta-1-49 rol roleta-1-49 1..49 ta-1-49 role eta-1-49 rol eta-1-49 rol ta-1-49

Uma solução possível, mas muito repetitiva, para o procedimento aposta é esboçada no diagrama de fluxo de dados e seguidamente apresentada6.

; Procedimento sem parâmetros.

; O valor devolvido pelo procedimento não tem qualquer interesse. Deste, apenas se ; espera um conjunto de mensagens no ecrã, que representa uma aposta do Totoloto. ; (define aposta (lambda () (display "Numero 1: ") (display (roleta-1-49)) (newline) (display "Numero 2: ") (display (roleta-1-49)) (newline) (display "Numero 3: ") (display (roleta-1-49)) (newline) (display "Numero 4: ") (display (roleta-1-49)) (newline) (display "Numero 5: ") (display (roleta-1-49)) (newline) (display "Numero 6: ") (display (roleta-1-49)) (newline)))

6 Por agora, serve esta solução. Atendendo ao seu carácter repetitivo, deverá ser possível encontrar

(11)

Do procedimento primitivo display, utilizado em aposta para visualizar dados, também não se espera um valor para ser posteriormente processado, mas sim um efeito lateral, ou seja, a visualização no ecrã de um valor numérico, como em (display (roleta-1-49)), ou a visualização de uma cadeia de caracteres, indicada entre aspas, como em (display "Numero 1: ").

Sendo nova-linha a tradução do procedimento primitivo newline, fica obviamente esclarecida a sua funcionalidade: A visualização passa para o início da linha seguinte. Na tentativa de encontrar uma solução mais compacta, verifica-se que a tarefa associada ao procedimento aposta poderá ser vista como a repetição de uma sub-tarefa, seis vezes. De facto, para gerar uma aposta do Totoloto é necessário gerar e visualizar seis números, situados entre 1 e 49, o que permite imaginar uma tarefa auxiliar que consistirá no seguinte:

i aposta-aux

roleta-1-49 1..49

1. Visualizar o texto "Numero ", seguido do valor associado a um parâmetro designado por i (valor de 1 a 6).

2. Visualizar o texto ": ", seguido de um número aleatório entre 1 e 49. 3. Mudar de linha

Parece assim justificar-se a definição de um procedimento auxiliar, designado por

aposta-aux, e que se ocupe desta sub-tarefa.

; Procedimento auxiliar do procedimento aposta, com o parâmetro i.

; Visualiza no ecrã uma das 6 linhas (a linha i) de uma aposta do Totoloto. ; (define aposta-aux (lambda (i) (display "Numero ") (display i) (display ": ") (display (roleta-1-49)) (newline)))

O procedimento aposta poderá agora tomar outra forma, mais compacta, designada por

aposta-melhorada, que se baseia na utilização

de aux. O procedimento aposta-melhorada chama aposta-aux 6 vezes. A primeira com o argumento 1, a segunda com o argumento 2, e assim sucessivamente até à sexta chamada com o argumento 6.

2 3 4 5 6 aposta-melhorada 1 apo aposta-aux apo sta-aux apo sta-aux apo sta-aux sta-aux apo sta-aux

O diagrama de fluxo de dados não precisa de mostrar a ligação de aposta-aux a

roleta-1-49, pois estamos apenas interessados em

saber "o que faz" aposta-aux e não "como faz". O procedimento aposta-aux surge assim como uma caixa-preta.

; versão melhorada do procedimento aposta. ;

(define aposta-melhorada (lambda ()

(12)

(aposta-aux 2) (aposta-aux 3) (aposta-aux 4) (aposta-aux 5) (aposta-aux 6))) > (Exercício 1.4)

Para pavimentar um parque desportivo utilizaram-se peças quadradas de dois tipos, umas de lado de comprimento lado1 e outras de lado de comprimento lado2.

Escrever o procedimento area com os parâmetros n1, lado1, n2 e lado2, em que n1 e

n2 representam o número de peças de cada tipo, e devolve a área pavimentada. A

chamada para uma situação de 200 peças de lado de comprimento 5 e 400 de lado de comprimento 10, seria:

> (area 200 5 400 10) 45000

Na escrita de area, sugere-se a utilização do procedimento quadrado, que recebe um valor e devolve-o elevado ao quadrado. Apresentar o diagrama de fluxo de dados que reflicta a ligação entre os procedimentos utilizados.

> (Exercício 1.5)

Escrever o procedimento area-losango-inscrito com os parâmetros lado1 e lado2, que devolve a área do losango inscrito no rectângulo, cujos lados são os parâmetros referidos.

> (area-losango-inscrito 20 10) 100

Apesar de não ser a melhor ideia, considerar como pista que a área de um losango corresponde à área de 4 triângulos rectângulos7 iguais, pelo que se sugere a

utilização de um procedimento auxiliar que calcule a área deste tipo de triângulo. Apresentar o diagrama de fluxo de dados que reflicta a ligação entre os procedimentos utilizados.

> (Exercício 1.6)

Escrever o procedimento area-sombreada com os parâmetros lado1 e lado2, que devolve a área sombreada no rectângulo, cujos lados são os parâmetros.

> (area-sombreada 20 10) 70.0

Considerar como pista que a área sombreada no rectângulo corresponde à soma da área de vários triângulos rectângulos. Apresentar o diagrama de fluxo de dados que reflicta a ligação entre os procedimentos utilizados.

(13)

> sec1-5

Variáveis locais

A definição de variáveis locais, no interior dos procedimentos, para além de facilitar a leitura desses procedimentos, pode ainda eliminar cálculos repetidos. Para clarificar este assunto, comecemos por analisar um exemplo.

Um procedimento recebe o comprimento dos lados de um triângulo e determina o seu perímetro e a percentagem de cada lado em relação ao perímetro. Para melhor se entender o que faz o referido procedimento, designado por percentagem-lado-perimetro, analisemos o seu comportamento em duas situações.

> (percentagem-lado-perimetro 10.0 15.0 20.0) perimetro do triangulo: 45.0

22.222222222222 33.333333333333 44.444444444444

Em primeiro lugar é visualizado o perímetro do triângulo, seguido das percentagens do comprimento de cada lado em relação ao perímetro.

> (percentagem-lado-perimetro 15.0 15.0 15.0) perimetro do triangulo: 45.0

33.333333333333 33.333333333333 33.333333333333

Na solução que se segue, o cálculo do perímetro, (+ a b c), é repetido quatro

vezes!...

; Procedimento com 3 parâmetro que representam os lados de um triângulo. ; O valor devolvido por este procedimento não tem qualquer interesse. ; Deste procedimento apenas se espera um conjunto de mensagens no ecrã ; correspondentes ao perímetro do triângulo e

; à percentagem de cada um dos lado em relação ao perímetro. ;

(define percentagem-lado-perimetro-1 (lambda (a b c)

(display "perimetro do triangulo: ") (display (+ a b c)) (newline) (display (* 100 (/ a (+ a b c)))) (newline) (display (* 100 (/ b (+ a b c)))) (newline) (display (* 100 (/ c (+ a b c))))))

Para evitar repetições deste tipo, bastaria definir uma variável local, no corpo do procedimento, com o valor correspondente ao cálculo repetido, pronta a ser utilizada sempre que fosse necessária. Esta hipótese é possível com let, uma outra forma especial do Scheme. A solução que se segue, percentagem-lado-perimetro-2, onde se define localmente a variável perimetro, é mais eficiente que a anterior, pois elimina o cálculo repetido do perímetro, para além de se tornar mais legível.

; Versão melhorada do procedimento percentagem-lado-perimetro-1. ; Define-se, localmente com let, a variável perimetro.

;

(define percentagem-lado-perimetro-2 (lambda (a b c)

(14)

(let ((perimetro (+ a b c)))

(display "perimetro do triangulo: ") (display perimetro) (newline) (display (* 100 (/ a perimetro))) (newline) (display (* 100 (/ b perimetro))) (newline) (display (* 100 (/ c perimetro))))))

O rectângulo assinala o corpo de let que, neste caso, representa o local onde é reconhecido o identificador perimetro.

Analisemos com cuidado a forma geral da expressão let: (let ( (nome-1 expressão-1)

(nome-2 expressão-2) ( ... ) ) corpo-de-let )

Notar duas áreas distintas nesta forma. Numa das áreas, definida entre parêntesis, são associados aos identificadores nome-1, nome-2, ..., os valores das expressões respectivas, expressão-1, expressão-2, ... . Na outra área, designada por corpo-de-let, os identificadores são reconhecidos e podem ser aí utilizados.

> (Exercício 1.7)

Analisar o procedimento experiencia-com-let que se segue:

; Procedimento para ilustrar a utilização de let ;

(define experiencia-com-let (lambda (x y)

(let ((local1 (+ x y)) (local2 (* x y))) (/ (+ local1 local2) (* local1 local2))))

Indicar como responde o Scheme nas situações que se apresentam.

> (experiencia-com-let 3 7) ??

> (experiencia-com-let -3 7) ??

perimetro

Voltando ao exemplo percentagem-lado-perimetro-2, apresenta-se uma outra solução que

introduz um segundo nível de let, dentro do corpo de let já existente. Desta forma, passa a ser possível utilizar a variável local perimetro na definição de outras variáveis, com visibilidade num espaço ainda mais restrito. Referimo-nos às variáveis perc-a,

perc-b e perc-c.

; Versão melhorada do procedimento percentagem-lado-perimetro-2. ; Define, localmente com let, a variável perimetro e também, ; num nível mais interno, as variáveis relativas às percentagens.

(15)

;

(define percentagem-lado-perimetro-3 (lambda (a b c)

(let ((perimetro (+ a b c)))

(let ((perc-a (* 100 (/ a perimetro))) (perc-b (* 100 (/ b perimetro))) (perc-c (* 100 (/ c perimetro)))) (display "perimetro do triangulo: ") (display perimetro) (newline) (display perc-a) (newline) (display perc-b) (newline) (display perc-c)))))

Já foi dito que os parâmetros de um procedimento são entidades locais, apenas reconhecidas no corpo do procedimento. Por este facto, os nomes dos parâmetros

a, b, e c do procedimento percentagem-lado-perimetro-3, podem ser re-utilizados noutros

procedimentos, representando entidades que, em princípio, não terão nada a ver com os lados de um triângulo. Com let também se criam entidades locais, mas estas com um campo de acção ainda mais restrito que o dos parâmetros do procedimento. As entidades criadas com let só são reconhecidas no corpo desta forma especial e não na totalidade do corpo do procedimento. Por exemplo, em

percentagem-lado-perimetro-3, os parâmetros a, b, e c são reconhecidos e podem ser utilizados em todo o

corpo do procedimento. No entanto, perimetro apenas é reconhecido no espaço do primeiro let, representado pelo rectângulo maior, e perc-a, perc-b, e perc-c no espaço do segundo let, ainda mais limitado, correspondente ao rectângulo menor.

> (Exercício 1.8)

A conversão de uma temperatura em graus Fahrenheit para graus Centígrados pode ser realizada através da fórmula Tc = (F - 32) x 5/9. Por outro lado, para converter uma temperatura em graus Centígrados para graus Kelvin bastará somar 273.16.

Escrever o procedimento converte-Fahrenheit com o parâmetro tf que representa uma temperatura em graus Fahrenheit e responde como se indica:

> (converte-Fahrenheit 14) Fahrenheit: 14

Centigrados: -10 Kelvin: 263.16

perc-a, perc-b, perc-c perimetro

> (Exemplo 1.3)

A fórmula de Briggs8 permite calcular, a partir do comprimento dos lados a, b

e c de um triângulo, os seus três ângulos internos α, β e γ. Na figura, sp é o semi-perímetro do triângulo, ou seja (a + b + c)/2.

Escrever em Scheme o procedimento designado por briggs que recebe os três lados de um triângulo e visualiza os seus três ângulos internos.

8 Henry Briggs foi um matemático inglês (1561-1631) e o primeiro professor de geometria no

(16)

c a c sp a sp * ) ( * ) ( 2 sinβ = − − c b c sp b sp * ) ( * ) ( 2 sinα= − − b a* 2 sinγ = (spa)*(spb) > (briggs 10.0 10.0 10.0)

O triangulo com os lados de comprimento: 10.0, 10.0, 10.0 tem os seguintes angulos: 60.00000000000001, 60.00000000000001,

60.000000000000019

> (briggs 10.0 15.0 15.0)

O triangulo com os lados de comprimento: 10.0, 15.0, 15.0 tem os seguintes angulos: 38.94244126898138, 70.52877936550931, 70.52877936550931

O procedimento briggs traz algumas novidades, com a introdução de funções trigonométricas. Por exemplo, uma destas funções é asin10 que aceita o valor de um seno e devolve o arco respectivo. Trabalha em radianos e não em graus, justificando por isso a conversão de radianos para graus, realizada por radians->degrees11.

No primeiro let, são definidos o semi-perímetro sp e os produtos designados por ab

= a * b, ac = a * c e bc = b * c. Depois, no corpo do primeiro let, e já com sp definido,

num segundo let, são definidos spa = sp - a, spb = sp - b e spc = sp - c. Finalmente, num terceiro let, são definidos os três ângulos, segundo a fórmula de Briggs.

; Procedimento com 3 parâmetro que representam os lados de um triângulo. ; O valor devolvido por este procedimento não tem qualquer interesse. ; Deste procedimento apenas se espera um conjunto de mensagens no ecrã ; que descrevem os ângulos internos do triângulo.

;

(define briggs (lambda (a b c)

(let ((sp (/ (+ a b c) ; definição de sp (semi-perímetro) e 2)) ; dos produtos

(ab (* a b)) ; ab = a*b (ac (* a c)) ; ac = a*c (bc (* b c))) ; bc = b*c

(let ((spa (- sp a)) ; sp, depois de definido, é utilizado na (spb (- sp b)) ; definição de outras variáveis locais (spc (- sp c)))

(let ((ang1 (radians->degrees (* 2

(asin (sqrt (/ (* spb spc) bc)))))) (ang2 (radians->degrees (* 2

(asin (sqrt (/ (* spa spc) ac)))))) (ang3 (radians->degrees (* 2

(asin (sqrt (/ (* spa spb) ab)))))))

(display "O triangulo com os lados de comprimento: ") (display a)

(display ", ") (display b)

9 Deveria ser: 60.0, 60.0, e 60.0, para que da soma dos 3 ângulos internos resultasse 180º. Assim não

acontece devido a arredondamentos nos cálculos.

10 asin significa arco cujo seno é…

11 Nem todas as implementações de Scheme disponibilizam radians->degrees e degrees->radians, o mesmo

(17)

(display ", ") (display c) (newline)

(display "tem os seguintes angulos: ") (display ang1) (display ", ") (display ang2) (display ", ") (display ang3) (newline)))))) ;

; Procedimento que converte radianos em graus ; (define radians->degrees (lambda (ang) (/ (* ang 180) pi))) ; (define pi 3.141592653589793) > (Exercício 1.9)

A área de um triângulo pode determinar-se a partir do comprimento dos seus lados a, b, e c, com a fórmula que se apresenta, em que sp é o semi-perímetro do triângulo.

Escrever o procedimento area-triangulo que tem como parâmetros os três lados de um triângulo e devolve a sua área. Sugere-se a definição de uma variável local sp para evitar a repetição do cálculo do semi-perímetro. ) ( * ) ( * ) ( * sp a sp b sp c sp tri area− = − − − > (Exercício 1.10)

Para determinar o dia da semana de uma data do calendário utiliza-se um algoritmo que se vai descrever. Antes disso, considere-se:

• m o mês do ano, em que Março é o mês 1, Abril 2, até Dezembro que é o mês 10. Janeiro e Fevereiro são considerados os meses 11 e 12 do ano anterior12.

• d o dia do mês. • a o ano do século. • s o século anterior13.

Por exemplo, para 4 de Julho de 1989 seria m=5, d=4, a=89, s=19. Por outro lado, para 4 de Janeiro do mesmo ano seria m=11, d=4, a=88, s=19.

Vejamos agora os passos do algoritmo. Seja: • mint a parte inteira de (13m-1)/5. • aint a parte inteira de a/4.

• sint a parte inteira de s/4. • x = mint+aint+sint+d+a-2s. • dia o resto da divisão inteira x/7.

dia é a resposta, de acordo com a identificação seguinte: dia=0 é o Domingo,

dia=1 é 2ª-feira, e assim sucessivamente até dia=6 que corresponde a Sábado.

12 Esta identificação dos meses, perfeitamente anormal, é apenas utilizada dentro do algoritmo. 13 Mais uma identificação anormal, para utilizar no algoritmo.

(18)

Escrever o programa dia-da-semana que, em relação a uma data, pede o dia, mês e ano e responde com o respectivo dia da semana. Na chamada que se segue, a data em causa é 18 de Agosto de 2001. > (dia-da-semana) dia: 18 mes: 8 ano: 2001 O dia da semana e' 6 > (Exemplo 1.4)

Pretende-se definir o procedimento circulo, sem parâmetros, que principia por pedir o raio de um círculo. O procedimento termina, visualizando no ecrã a área co círculo e o respectivo perímetro.

> (circulo) raio:

Este é o primeiro exemplo em que o utilizador é convidado a fornecer dados através do teclado. A mensagem raio: indica que o procedimento espera a resposta do utilizador, neste caso, o valor do raio do círculo, seguido da actuação da tecla return. Seja 10 o valor fornecido… e a resposta é dada de imediato.

read area-cir circulo r perimetro r r area perimetro-cir perimetro = 2 x pi x r area= pi x r2 r > (circulo) raio: 10

Area do circulo e': 314.1592653589793 Perimetro do circulo e': 62.83185307179586

Na figura é esboçado o diagrama de fluxo de dados do procedimento circulo, em que se põe em evidência mais três procedimentos. Dois deles são procedimentos auxiliares que acompanham o procedimento circulo. O restante, read, trata-se de um procedimento fornecido pelo Scheme.

; Procedimento sem parâmetros.

; Pede o valor do raio de um círculo, a fornecer através do teclado. ; O valor devolvido por este procedimento não tem qualquer interesse. ; Deste procedimento apenas se espera um conjunto de mensagens no ecrã ; que definem a área e o perímetro do círculo.

;

(define circulo (lambda ()

(display "raio: ") (let ((raio (read)))

(display "Area do circulo e': ") (display (area-cir raio))

(newline)

(display "Perimetro do circulo e': ") (display (perimetro-cir raio))))) ;

(19)

; Procedimento auxiliar. Recebe o raio de um círculo e devolve a sua área. ;

(define area-cir

(lambda (r) ; area do círculo = pi * r * r (* pi r r)))

;

; Procedimento auxiliar. Recebe o raio de um círculo e ; devolve o seu perímetro.

;

(define perimetro-cir

(lambda (r) ; perimetro do círculo = 2 * pi * r (* 2 pi r)))

;

(define pi 3.141592653589793)

Na implementação do procedimento circulo, o procedimento read foi utilizado no contexto de uma construção let, (let ((raio (read))). Aqui, read surge como um procedimento primitivo sem parâmetros que espera um valor a ser fornecido através do teclado. Este tipo de situação é geralmente antecedida da visualização de uma mensagem, neste caso (display "raio: "), informando o utilizador que deve

fornecer um determinado dado, através do teclado.

A definição de uma variável local com o valor devolvido por read é uma prática usual. Se assim não fosse este valor ou era imediatamente utilizado (por exemplo, numa expressão) ou perder-se-ia, não podendo ser utilizado uma segunda vez.

> sec1-6

Estruturas de Selecção

São frequentes as situações que exigem a tomada de decisões onde, perante várias hipóteses, tem que se optar por uma delas. Por exemplo, se se pretende simular o sorteio de 6 números do Totoloto, em vez de repetir 6 vezes a escrita de um certo conjunto de instruções, aliás como foi feito no Exemplo 1.1, teria sido preferível programar algo do tipo:

• Sortear mais um número;

• Se ainda não foram sorteados 6 números, repete-se a acção anterior. Caso contrário, acabou de ser apurada uma aposta, não sendo necessário sortear mais números.

Mas vejamos outro exemplo, que simula o lançamento de um projéctil no espaço. Se o vector vec representar a velocidade inicial do projéctil lançado da origem dos eixos e ang o ângulo de lançamento, pretende-se que o projéctil não seja lançado para “trás”. Então, ang deverá ser menor que 90º. Para se considerar esta condição, poderíamos escrever:

(if (< ang 90)

(faz-o-lançamento ...)

(display "projectil mal orientado"))

Nesta expressão if, surge uma expressão de relação, (< ang 90), cujo resultado pode

(20)

⇒ #t, se ang for menor que 90 ⇒ #f, se ang for maior ou igual que 90.

As entidades com valores #t e #f são designadas de booleanas e as expressões de que resultam booleanos são designadas de predicados. Por exemplo, (< 30 40) é um

predicado com valor #t, pois 30 é menor que 40, e (< 30 30) é um predicado com

valor #f, pois 30 não é menor que 30.

Identifica-se assim mais uma forma especial do Scheme, if:

(if expressão-predicado expressão-consequente expressão-alternativa)

A regra de cálculo da forma especial if é a seguinte: ⇒ Calcular a expressão-predicado;

⇒ Se resultar #t, é calculada a expressão-consequente; ⇒ Se resultar #f, é calculada a expressão-alternativa.

Os procedimentos de relação disponibilizadas pelo Scheme são: > (maior) , < (menor), =

(igual), >= (maior ou igual), e <= (menor ou igual).

> (if (> 5 12) 5 14 (* 2 7)) > (if (<= 5 12) 5 5 (* 2 7))

Retomando o exemplo do lançamento do projéctil. Pretende-se agora garantir que o lançamento só deverá ocorrer se o ângulo se situar entre 20 e 70º.

Para se considerar esta nova condição, podemos escrever:

(if (and

(> ang 20) (< ang 70))

(faz-o-lançamento ...)

(display "projectil mal orientado"))

A expressão-predicado é, neste exemplo, uma expressão

lógica que, como acontecia com as expressões de relação,

também origina resultados booleanos.

Os procedimentos lógicos são especialmente indicados para definir condições compostas.

(if (and

(>= altura 160) (<= altura 180)) (display "Altura média")

(display "Ou muito alto ou muito baixo!!!"))

(21)

Faz parte da cultura Scheme terminar com ? o nome dos procedimentos que são predicados, ou seja, que devolvem valores booleanos. O próprio Scheme dá o exemplo, com os predicados que disponibiliza: negative?, positive?, zero?, even?, odd?,

boolean?, symbol?, procedure?, number?, integer?, real? e outros.

Nesta altura, recomenda-se uma consulta ao Anexo A, com o objectivo de se verificar a funcionalidade de cada um destes predicados, bem como dos operadores lógicos e de relação.

> (Exercício 1.11)

Modificar a solução apresentada para o procedimento circulo do Exemplo 1.4, que verifica se o utilizador fornece um valor numérico ao responder com o comprimento do raio. O diálogo passará a revestir a seguinte forma:

> (circulo)

raio (indicar um valor numérico positivo): dez Erro: Deve indicar um valor numérico positivo. > (circulo)

raio (indicar um valor numérico positivo): -10.5 Erro: Deve indicar um valor numérico positivo. > (circulo)

raio (indicar um valor numérico positivo): 10 Area do circulo e': 314.1592653589793

Perimetro do circulo e': 62.83185307179586

> (Exercício 1.12)

Relembrar o exemplo 1.9 e considerar que os comprimentos a, b e c fornecidos como argumentos podem não corresponder aos lados de um triângulo, bastando que qualquer um deles seja igual ou superior à soma dos outros dois.

Escrever o procedimento area-triangulo-melhorada que devolve 0, em vez da área, sempre que detecta este tipo de situação. Sugere-se a utilização de um procedimento auxiliar para validar se os comprimentos fornecidos correspondem ou não aos lados de um triângulo.

Apresentar o diagrama de fluxo de dados que reflicta a ligação entre os procedimentos utilizados.

Retomando, mais uma vez, o exemplo do lançamento do projéctil, pretende-se agora garantir os seguintes tipos de lançamento:

• Se ang maior que 70º ou menor ou igual que 10º, não haverá lançamento;

• Se ang menor ou igual que 70º e maior que 50º, haverá um lançamento a grande altitude;

• Se ang menor ou igual que 50º e maior que 30º, haverá um lançamento a média altitude;

• Se ang maior ou igual que 30º e maior que 10º, haverá um lançamento a baixa altitude.

(22)

Para ter em conta todas estas condições, definiu-se o procedimento projectil. Verificar que neste a expressão-alternativa do primeiro if é, ela própria, um novo if…

(define projectil (lambda (ang) (if (or

(> ang 70) (<= ang 10))

(display "projectil mal orientado") (if (> ang 50)

(display "lancamento a grande altitude") (if (> ang 30)

(display "lancamento a media altitude") (display "lancamento a baixa altitude"))))))

> (projectil 10)

projectil mal orientado > (projectil 40)

lancamento a media altitude > (projectil 70)

lancamento a grande altitude > (projectil 120)

projectil mal orientado

Não se poderá dizer que o procedimento projectil, baseado numa sequência de if, seja muito legível. Entende-se o que faz, mas em situações como esta, é preferível utilizar a forma especial cond:

(define projectil-melhorado (lambda (ang)

(cond ((or

(> ang 70) (<= ang 10))

(display "projectil mal orientado")) ((> ang 50)

(display "lancamento a grande altitude")) ((> ang 30)

(display "lancamento a media altitude")) (else

(display "lancamento a baixa altitude")))))

Identificando a forma especial do Scheme cond:

(cond (predicado-1 exp1-1 exp1-2 ...) ; cláusula 1 (predicado-2 exp2-1 exp2-2 ...) ; cláusula 2 ...

(else exp-else-1 exp-else-2 ...) ) ; cláusula else A regra de cálculo da forma especial cond é a seguinte:

⇒ Calcular os predicados, começando por predicado-1, até encontrar um predicado com valor #t;

⇒ Logo que se encontre um predicado #t, calcular as expressões correspondentes;

⇒ Se não se encontrar qualquer predicado com valor #t, calcular as expressões correspondentes a else.

⇒ Nota: A cláusula else é opcional. Se não existir e se do cálculo de todos os predicados resultar #f, nenhuma das expressões de cond será calculada.

(23)

Sobre as formas especiais if e cond poder-se-á dizer que if é de evitar em situações com mais de duas opções, por se tornar de mais difícil leitura. E mesmo com duas opções, é também de evitar if quando a expressão-consequente ou a expressão-alternativa integram mais do que uma expressão, situação que obriga à utilização de begin.

(if expressão-predicado (cond (expressão-predicado (begin expressão-consequente-1 expressão-consequente-1 expressão-consequente-2 expressão-consequente-2 ... ... expressão-consequente-m) expressão-consequente-m) (begin (else expressão-alternativa-1 expressão-alternativa-1 expressão-alternativa-2 expressão-alternativa-2 ... ... expressão-alternativa-n) ) expressão-alternativa-n) )

begin é mais uma das formas especiais do Scheme, que se apresenta da seguinte

maneira: (begin expressão-1 expressão-2 ... expressão-n). begin garante que

estas expressões são calculadas em sequência, desde a primeira até à última, e devolve o valor da última expressão. Este tipo de sequência está implícito no corpo dos procedimentos quando contém mais do que uma expressão e também nas cláusulas de cond.

> (Exemplo 1.5)

Apresentam-se duas versões de um procedimento que recebe dois valores e visualiza a relação de grandeza entre eles.

Na versão if recorre-se a if, e utiliza-se cond, na versão

comparador-com-cond. Ambos respondem da mesma maneira e diferem apenas no grau de legibilidade

que oferecem. (define comparador-com-if (lambda (x y) (if (> x y) (begin (display x)

(display " e' maior que ") (display y))

(if (> y x) (begin

(display y)

(display " e' maior que ") (display x))

(begin

(display "ambos iguais a ") (display x)))))) > (comparador-com-if 2 3) 3 e' maior que 2 > (comparador-com-if 3 2) 3 e' maior que 2 > (comparador-com-if 3 3) ambos iguais a 3

(24)

(define comparador-com-cond (lambda (x y)

(cond ((> x y) (display x)

(display " e' maior que ") (display y))

((> y x) (display y)

(display " e' maior que ") (display x))

(else

(display "ambos iguais a ") (display x)))))

> (Exercício 1.13)

O procedimento rectangulo-maior tem como parâmetros lado-a1, lado-a2, lado-b1, e lado-b2, que correspondem ao comprimento dos lados de 2 rectângulo: Os dois primeiros do rectângulo A e os restantes do rectângulo B. Escrever este procedimento em Scheme que calcula a área de cada um dos rectângulos, compara-as e responde da seguinte maneira:

> (rectangulo-maior 10 20 15 5) Rectangulo A: 200

Rectangulo B: 75

O rectangulo A e' maior 125 unidades. > (rectangulo-maior 10 20 15 18) Rectangulo A: 200

Rectangulo B: 270

O rectangulo B e' maior 70 unidades. > (rectangulo-maior 10 20 40 5) Rectangulo A: 200

Rectangulo B: 200

Os rectangulos apresentam igual area.

> (Exercício 1.14)

Idêntico ao exercício anterior, mas agora o procedimento não tem parâmetros. O comprimento de cada lado é fornecido através do teclado. Imaginar um diálogo apropriado e escrever o procedimento. Apresentar o diagrama de fluxo de dados que reflicta a ligação entre os procedimentos utilizados.

> sec1-7

Exercícios e Exemplos

Nesta secção são apresentados exercícios e exemplos para consolidação da matéria considerada no decorrer do capítulo. Recomenda-se o estudo dos exemplos e a resolução de alguns dos exercícios, em frente do computador, atitude que é fundamental tomar desde o início do processo de aprendizagem da programação. Estudar os exemplos, introduzir-lhes alterações, inventar e resolver novos exercícios são tarefas que deverão fazer parte dos planos de quem pretende dominar a arte de programar bem.

(25)

Apesar de ainda muito limitados em termos do conhecimento das potencialidades do Scheme, é já possível resolver alguns problemas interessantes.

> (Exemplo 1.6)

Os números decimais ímpares terminam em 1, 3, 5, 7 e 9. Vamos agora inventar uma nova classe de números, os ímpares-curvos (!!!) como sendo os números ímpares que terminam em 3, 5 e 9 (dígitos que, no respectivo desenho, usam segmentos curvos!).

Considere o procedimento impar-curvo? que determina se um número é ou não ímpar-curvo. > (impar-curvo? 2345) #t > (impar-curvo? 2346) #f > (impar-curvo? 2347) #f

Escrever em Scheme o procedimento impar-curvo?

A solução que se apresenta começa por definir localmente a variável

digito-menos-significativo, que se obtém calculando o resto da divisão inteira do número dado por

1014. Seguidamente, deduz-se se o número em questão é ou não impar-curvo, testando

se digito-menos-significativo é 3, 5 ou 9. (define impar-curvo?

(lambda (num)

(let ((digito-menos-signif (remainder num 10))) (cond ((= digito-menos-signif 3) #t)

((= digito-menos-signif 5) #t) ((= digito-menos-signif 9) #t) (else #f)))))

Uma outra solução é conseguida através de uma condição composta, que utiliza o operador lógico or.

(define impar-curvo? (lambda (num)

(let ((digito-menos-signif (remainder num 10))) (or

(= digito-menos-signif 3) (= digito-menos-signif 5) (= digito-menos-signif 9)))))

> (Exemplo 1.7)

Supor que já se dispõe do procedimento quantos-positivos, com dois parâmetros. Este procedimento analisa os seus parâmetros e fornece como resultado 2, 1 ou 0, conforme o número de parâmetros positivos.

> (quantos-positivos 1 2) 2 > (quantos-positivos 1 -2) 1 > (quantos-positivos -1 -1) 0

14 O resto da divisão inteira x/y pode calcular-se com o procedimento primitivo remainder, fazendo (remainder x y). A este propósito, recomenda-se a consulta do Anexo A, em particular o que se refere a

(26)

Por seu turno, o procedimento soma-positivos também analisa os seus dois parâmetros: a soma b soma-positivos quantos-positivos a 1, 2, 0 b

• Se ambos forem positivos, devolve a soma dos dois; • Se apenas um for positivo, devolve o valor desse positivo; • Se ambos forem negativos, devolve o valor 0.

Escrever em Scheme o procedimento soma-positivos, utilizando o procedimento quantos-positivos, suposto já existente.

Optou-se por criar localmente a variável numero-de-positivos, que vai conter o número de positivos em jogo, através de uma chamada ao procedimento

quantos-positivos. O diagrama de fluxo de dados mostra a ligação existente

entre os procedimentos utilizados. (define soma-positivos

(lambda (a b)

(let ((numero-de-positivos (quantos-positivos a b))) (cond ((= numero-de-positivos 2) (+ a b))

((zero? numero-de-positivos) 0) ((positive? a) a)

(else b)))))

Por seu lado, o procedimento quantos-positivos baseia-se nos seguintes testes: x e y são ambos positivos; x e y são ambos negativos; se não for nenhum destes casos, então um dos argumentos será positivo.

(define quantos-positivos (lambda (x y) (cond ((and (positive? x) (positive? y)) 2) ((and (negative? x) (negative? y)) 0) (else 1)))) > (soma-positivos 3 2) 5 > (soma-positivos 3 -2) 3 > (soma-positivos -3 2) 2 > (soma-positivos -3 -2) 0 > (Exemplo 1.8)

Uma bola é largada de uma altura h sobre uma superfície lisa, ficando a saltar durante algum tempo. Supor que ao saltar, a bola toca a superfície sempre no mesmo ponto.

A distância percorrida pela bola é a soma dos movimentos descendentes e ascendentes. Em cada salto, a bola sobe a uma altura que é calculada multiplicando h, altura do salto anterior, por um factor k, em que 0<k<1, designado por

coeficiente de amortecimento.

Vamos supor que o procedimento salto, com os parâmetros

h e k, devolve a altura do salto da bola quando esta é

(27)

Escrever em Scheme o procedimento distancia-1, que toma os valores h e k, e devolve a distância percorrida pela bola desde o momento que é largada da altura h até ao final do primeiro salto, ou seja, a distância correspondente a uma descida seguida de uma subida. Este procedimento deverá utilizar o procedimento salto, como se vê no diagrama de fluxo de dados. h distancia k distancia-1 salto h altura k (define distancia-1 (lambda (h k) (+ h (salto h k)))) ; (define salto (lambda (h k) (* h k))) > (distancia-1 2 .5) 3.0 > (distancia-1 3 .2) 3.6 > (Exercício 1.15)

Tomando como referência o exemplo anterior, escrever agora o procedimento distancia-2, que toma os valores h e r, e devolve a distância percorrida pela bola desde o momento que é largada da altura h até ao final do segundo salto, ou seja, a bola desce, sobe, torna a descer e a subir.

> (distancia-2 2 .5) 4.5

> (distancia-2 3 .2) 4.32

> (Exercício 1.16)

Um percurso rodoviário é composto por uma parte em piso horizontal, mas também apresenta uma subida e uma descida. De uma viatura é conhecido o consumo médio, aos 100 Km, quando se desloca em percurso horizontal.

Em relação a este consumo, a mesma viatura, em subida, gasta mais 30% e, em descida, menos 10%.

Escrever em Scheme o procedimento consumo-total que tem como parâmetros consumo, horiz, sub e desc, que representam, respectivamente, o consumo médio da viatura em percurso horizontal, o número de Km de percurso horizontal, o número de Km em subida e, finalmente, o número de Km em descida. Este procedimento deve devolver a quantidade em litros gasta pela viatura no percurso definido pelos parâmetros respectivos.

subida descida

> (Exercício 1.17)

Tomando como base o exercício anterior, pretende-se agora escrever o procedimento gasolina-suficiente que tem como parâmetros gas-no-tanque

(28)

(que representa a quantidade de combustível que a viatura tem no tanque), e ainda os parâmetros do procedimento consumo-total. O procedimento pretendido deve responder, conforme o caso, com uma das seguintes mensagens:

• Nao e' suficiente. Faltam x litros. • E' suficiente. Sobram x litros. • Gasolina 'a justa.

Esboçar o diagrama de fluxo de dados que coloque em evidência a ligação entre os procedimentos utilizados.

> (Exemplo 1.9)

Para este exercício vamos supor que a Terra é uma esfera perfeita, com um raio de 6378 Km. Sendo assim, o perímetro da Terra, quando se percorre o Equador (latitude 0º) será de 2 x pi x 6378000 metros. Pretende-se conhecer o perímetro da Terra, percorrendo um paralelo ao Equador, identificado pela respectiva latitude (entre 0 e 90º, no Hemisfério Norte). Se para a latitude 0º o perímetro coincide com o comprimento do Equador, para a latitude 90º, que coincide com o pólo Norte, o perímetro será 0 metros.

66.50º

Círculo Polar Árctico

Equador

Mas qual será o perímetro do trópico de Câncer, sabendo que se encontra à latitude 23.5º N? E o perímetro do Círculo Polar Árctico a 66.5º N? E o perímetro de outro paralelo qualquer?

Pretende-se definir o procedimento perimetro-paralelo que responde da seguinte forma:

> (perimetro-paralelo 0) 40074155.889191404 metros > (perimetro-paralelo 66.5) 15979532.348780245 metros > (perimetro-paralelo 90) 2.4537532962986115e-009 metros > (perimetro-paralelo 89.5) 349708.5439343981 metros

Na resolução deste problema, como se pode ver pelo diagrama de fluxo de dados, principiámos pelo mais geral, com o procedimento perimetro-paralelo que trata apenas

da mensagem da resposta.

Este procedimento recorre ao procedimento peri-paralelo que calcula o perímetro de um paralelo para uma certa latitude.

latitude perimetro-paralelo latitude perimetro peri-paralelo latitude raio raio-paralelo

Observando um pouco mais em profundidade, concluiremos que, por sua vez, peri-paralelo recorre

ao procedimento raio-paralelo que devolve o raio do paralelo para uma latitude.

A figura mostra como, a partir do raio da Terra e da latitude se pode calcular o raio do paralelo: Tendo em conta o triângulo rectângulo desenhado na figura,

(29)

sabe-se que um cateto, raio, é igual à hipotenusa, neste caso coincidindo com o raio da terra, multiplicada pelo coseno do ângulo adjacente, lat.

(define raio-terra 6378000) ; raio da Terra em metros ;

; visualiza o resultado no formato indicado ;

(define perimetro-paralelo

(lambda (latitude) (display (peri-paralelo latitude)) (display " metros")

(newline))) ;

; calcula o perímetro do paralelo cuja latitude é o argumento ; (define peri-paralelo (lambda (latitude) (* 2 pi (raio-paralelo latitude)))) ;

; calcula o raio do paralelo cuja latitude é o argumento ;

(define raio-paralelo (lambda (lat) (* raio-terra

(cos (degrees->radians lat))))) ;

; Procedimento que converte graus em radianos ; (define degrees->radians (lambda (ang) (/ (* pi ang) 180))) ; (define pi 3.141592653589793) ; valor de pi > (Exercício 1.18)

No contexto do exemplo anterior, supor que se colocava um arame de 40074155.889191404 metros e com ele se fazia um anel para colocar à volta da Terra, sobre o Equador. O anel ficava justo à Terra, portanto à distância zero, em todos os seus pontos. Imagine agora que cortava o anel e que lhe acrescentava um metro. O anel era novamente colocado à volta da Terra, no Equador, mas já não ficava completamente justo. Se o anel fosse colocado concêntrico com o Equador, a que distância ficaria o anel da Terra?

Se uma experiência idêntica fosse feita num paralelo, muito junto ao Pólo Norte, com um arame igual ao seu perímetro, ao qual se acrescentaria também um metro. Neste caso, a que distância ficaria o anel da Terra?

O procedimento distancia-terra com dois parâmetros, um associado à latitude e outro ao comprimento de arame a acrescentar ao anel. Este procedimento determina a distância a que fica o anel da Terra, quando se lhe acrescenta aquele comprimento de arame.

raio-ext raio-int

Sugere-se a seguinte pista:

• Para a latitude dada, determinar o raio do respectivo paralelo, seja raio-int;

Referências

Documentos relacionados

Almanya'da olduğu gibi, burada da bu terimin hiçbir ayrım gütmeden, modern eğilimleri simgeleyen tüm sanatçılar için geçerli olduğu anlaşılıyor.. SSCB'de ilk halk

Biocontaminantes em amostras de água Água deionisada Água deinoisada Comentários &gt; 0,24 e &lt; 3 &gt; 3 e &lt; 30 Endotoxinas EU/m 3 12 35 Número total de viáveis UFC/m

É perceptível, desta forma, o constante aumento do aprofundamento dos personagens: os “príncipes” têm agora não só nome e falas, mas personalidades bem desenvolvidas,

UNIVERSIDADE FEDERAL DO ESPÍRITO SANTO CENTRO DE EDUCAÇÃO FÍSICA E DESPORTOS. 42 Ramon Matheus dos

a) Seleciona um par de cromossomos genitores dentro da população atual, com a probabilidade de seleção sendo diretamente proporcional à sua adaptação. O mesmo

No sentido de reverter tal situação, a realização deste trabalho elaborado na disciplina de Prática enquanto Componente Curricular V (PeCC V), buscou proporcionar as

A filosofia, do ponto de vista do desenvolvi- mento integral das pessoas, trabalha temas como o estudo do pensamento e das diversas formas de pensar, a importância da raciona-

Mesmo nas variadas provas da vida podemos nos alegrar efusivamente, porque Deus é quem está no controle da nossa vida e ele trabalha em todas as circunstâncias