Curso de ScriptVox Intermediário - Aula 11 - Prof. Oswaldo Vernet - iNCE/UFRJ

Vamos resolver o exercício anterior, já que apenas uma resposta foi enviada.

Tratemos, primeiramente, da leitura do arquivo "AGENDA.TXT", que tem a
seguinte especificação: duas linhas para cada pessoa, a primeira contendo o
nome, a seguinte contendo o telefone. A ideia é guardar os nomes numa lista
de nomes e os telefones numa lista de telefones, de modo que as duas listas
estejam emparelhadas: o nome armazenado na posição "p" da lista de nomes
corresponde ao telefone armazenado na posição "p" da lista de telefones.

A função le_arquivo é a seguinte:

* início da função le_arquivo
função le_arquivo : nome, telefone, erro
	abre #1 "AGENDA.TXT"
	nomes := []
	telefones := []
	le #1 MAIUSC nome
	checa erro
	enquanto erro = 0 
		le #1 MAIUSC telefone
		checa erro
		se erro <> 0
			escreve "O arquivo AGENDA.TXT acabou inesperadamente"
			desvia @fim
		fim se
		nomes[] := nome
		telefones[] := telefone
		le #1 MAIUSC nome
		checa erro
	fim enquanto
@fim
	fecha #1
fim função
* fim da função le_arquivo

Alguns pontos a observar na função le_arquivo: 

1. Ela não recebe parâmetros, já que o arquivo contendo a agenda foi especificado
   como "AGENDA.TXT". Se este nome pudesse variar (por exemplo, se ele fosse lido do
   teclado), poderíamos pensar em passar o nome lido como parâmetro. Mas, no caso,
   não é necessário, pois o nome é fixo.
   
2. As variáveis nome, telefone e erro são locais. Elas só precisam existir enquanto
   a função estiver ativa, lendo o arquivo. Acabada a leitura, não interessa mais
   manter essas variáveis. As variáveis nomes e telefones são globais, porque serão 
   usadas em outros pontos do script. 
   
3. A leitura do arquivo se dá normalmente, lembrando que cada pessoa ocupa duas linhas.
   Portanto, o arquivo, se estiver íntegro, terá um número par de linhas. Mesmo
   assim, por questão de segurança, quando lemos o telefone, devemos verificar se
   a leitura ocorreu com sucesso, pois, por algum erro de edição, pode ser que
   exatamente o telefone da última pessoa esteja faltando.
   
4. O nome e o telefone de uma pessoa são lidos para as variáveis nome e telefone,
   respectivamente. Somente se a leitura não apresentar problemas, o nome é anexado à
   lista nomes e o telefone é anexado à lista telefones. Observe que as duas listas
   são inicializadas como vazias imediatamente antes de a leitura começar.
   
5. O modificador MAIUSC é usado nos comandos de leitura, de forma que os dados
   guardados nas listas estarão sempre em maiúsculas.

Vamos, agora, ao programa principal. A primeira ação a ser tomada é chamar a função
le_arquivo. Concluída a leitura, chamamos a função processa_comandos, que será responsável
por interagir com o usuário e fazer as pesquisas na agenda. Portanto, nosso programa
principal fica assim:

* início do programa principal
le_arquivo ()
processa_comandos ()
termina
* fim do programa principal

Há um detalhe importante aqui. A agenda é representada, no script, por duas listas,
guardadas nas variáveis nomes e telefones. Estas variáveis são globais, porque são
necessárias tanto na função le_arquivo quanto na função processa_comandos. Apenas para destacar o
fato de elas serem globais, vamos mover a inicialização das duas variáveis, que é feita
na função le_arquivo, para o programa principal. Assim, para quem está lendo o script, 
ficará logo claro que estas variáveis são globais. Juntando as peças, nosso script, 
até agora, fica assim:

* início do script
* início do programa principal
nomes := []
telefones := []

le_arquivo ()
processa_comandos ()
termina
* fim do programa principal

* início da função le_arquivo   
função le_arquivo : nome, telefone, erro
	abre #1 "AGENDA.TXT"
	le #1 MAIUSC nome
	checa erro
	enquanto erro = 0 
		le #1 MAIUSC telefone
		checa erro
		se erro <> 0
			escreve "O arquivo AGENDA.TXT acabou inesperadamente"
			desvia @fim
		fim se
		nomes[] := nome
		telefones[] := telefone
		le #1 MAIUSC nome
		checa erro
	fim enquanto
@fim
	fecha #1
fim função
* fim da função le_arquivo

* função processa_comandos
função processa_comandos
* a completar
fim função
* fim da função processa_comandos
* fim do script

O comando TERMINA, ao final do programa principal, não é necessário. Ele foi introduzido
apenas por clareza.

A função processa_comandos deverá ler o que o usuário deseja pesquisar e realizar a pesquisa,
informando os resultados encontrados. Seu "esqueleto" é o seguinte:

* início da função processa_comandos
função processa_comandos
	escreve "Dado a pesquisar: " &
	le MAIUSC dado
	enquanto dado <> ""
		pesquisa ()
		escreve "Dado a pesquisar: " &
		le MAIUSC dado
	fim enquanto
fim função
* fim da função processa_comandos

O enunciado do exercício não especificava como a interação com o usuário deveria ser
feita. Optamos então por ficar perguntando ao usuário o que ele deseja pesquisar até
que ele tecle simplesmente ênter, encerrando o uso da agenda. O dado a ser pesquisado,
lido em maiúsculas, é armazenado na variável "dado". Para cada dado lido, a função
"pesquisa" (que será a próxima a implementar) é chamada, com a finalidade de realizar
a pesquisa na agenda. 

Temos duas opções para o escopo da variável "dado": podemos deixá-la como global,
já que ela é usada na função processa_comandos e também será usada na função "pesquisa";
ou podemos declará-la como variável local à função processa_comandos. Mas aí, a função
pesquisa não teria acesso a ela, pois nenhuma função tem acesso diretamente às variáveis
locais de outra função. A solução seria pasar "dado" como parâmetro para a função
"pesquisa". Vamos implementar esta segunda ideia. A nova função processa_comandos ficaria
assim:

* início da função processa_comandos
função processa_comandos : dado
	escreve "Dado a pesquisar: " &
	le MAIUSC dado
	enquanto dado <> ""
		pesquisa (dado)
		escreve "Dado a pesquisar: " &
		le MAIUSC dado
	fim enquanto
fim função
* fim da função processa_comandos

Chegamos, então, ao ponto central do script: as consultas à agenda. A função pesquisa
é responsável por isto. Vamos desenvolvê-la por partes, como sugere o enunciado do
exercício.

A primeira versão fará uma pesquisa simples: a cadeia que o usuário digitou está armazenada na
variável "dado" e deverá ser pesquisada ou na lista dos nomes ou na lista dos telefones. 
Como vamos decidir em qual lista será feita a consulta?

É simples: telefones brasileiros, em geral, contêm apenas números. Podemos aproveitar este
fato e fazer um teste: se o que o usuário digitou começa por número, assumimos que se trata de
um telefone; caso contrário, deve tratar-se de um nome. Podemos usar o operador *=* para
testar se o primeiro caractere da cadeia armazenada na variável "dado" é um dígito da seguinte
forma:

se "0123456789" *=* dado[1]
* o primeiro caractere de dado é um dígito
senão
* o primeiro caractere de dado não é um dígito
fim se

Se o teste for verdadeiro, devemos procurar "dado" na lista de telefones; se for falso,
devemos procurar "dado" na lista de nomes. A função POS nos ajuda nesta tarefa:

se "0123456789" *=* dado[1]
	posição := POS (dado, telefones)
senão
	posição := POS (dado, nomes)
fim se

Se a pesquisa for bem sucedida, a variável "posição" conterá exatamente a posição
em uma das listas onde "dado" foi encontrado. Se a pesquisa falhar, "posição" terá
o valor menos um e, aí, saberemos que o dado digitado não se encontra na agenda.
Nossa função "pesquisa" fica assim:

* início da função pesquisa
função pesquisa (dado) : posição
	se "0123456789" *=* dado[1]
		posição := POS (dado, telefones)
	senão
		posição := POS (dado, nomes)
	fim se
	se posição < 0
		escreve dado " NÃO foi encontrado na agenda"
	senão
		escreve "NOME: " nomes[posição]
		escreve "TELEFONE: " telefones[posição]
	fim se
* fim da função pesquisa

A variável "posição" foi escolhida como local, já que seu valor não interessa a nenhum
outro ponto do script.

Juntando as peças até agora, o script tem o seguinte aspecto:

* início do script
* início do programa principal
nomes := []
telefones := []

le_arquivo ()
processa_comandos ()
termina
* fim do programa principal

* início da função le_arquivo   
função le_arquivo : nome, telefone, erro
	abre #1 "AGENDA.TXT"
	le #1 MAIUSC nome
	checa erro
	enquanto erro = 0 
		le #1 MAIUSC telefone
		checa erro
		se erro <> 0
			escreve "O arquivo AGENDA.TXT acabou inesperadamente"
			desvia @fim
		fim se
		nomes[] := nome
		telefones[] := telefone
		le #1 MAIUSC nome
		checa erro
	fim enquanto
@fim
	fecha #1
fim função
* fim da função le_arquivo

* início da função processa_comandos
função processa_comandos : dado
	escreve "Dado a pesquisar: " &
	le MAIUSC dado
	enquanto dado <> ""
		pesquisa (dado)
		escreve "Dado a pesquisar: " &
		le MAIUSC dado
	fim enquanto
fim função
* fim da função processa_comandos

* início da função pesquisa
função pesquisa (dado) : posição
	se "0123456789" *=* dado[1]
		posição := POS (dado, telefones)
	senão
		posição := POS (dado, nomes)
	fim se
	se posição < 0
		escreve dado " NÃO foi encontrado na agenda"
	senão
		escreve "NOME: " nomes[posição]
		escreve "TELEFONE: " telefones[posição]
	fim se
fim função
* fim da função pesquisa
* fim do script 
 
Esta primeira versão atende ao que foi pedido na primeira parte do enunciado. Em continuação,
é pedido que repetições de nomes não sejam permitidas na agenda. Isto pode ser detectado
no momento da leitura, da seguinte maneira: para cada nome e telefone lidos, antes de anexá-los
às listas, testamos se o nome já não pertence à lista de nomes. Se pertencer, a função POS 
retornará um número maior ou igual a zero e, neste caso, emitimos uma advertência e não 
fazemos as inclusões; se não pertencer, a função POS retornará menos um e os dados poderão
ser incluídos normalmente. A nova função le_arquivo fica assim:

* início da função le_arquivo   
função le_arquivo : nome, telefone, erro
	abre #1 "AGENDA.TXT"
	le #1 MAIUSC nome
	checa erro
	enquanto erro = 0 
		le #1 MAIUSC telefone
		checa erro
		se erro <> 0
			escreve "O arquivo AGENDA.TXT acabou inesperadamente"
			desvia @fim
		fim se
		se POS (nome, nomes) >= 0
			escreve "O nome " nome " está repetido no arquivo"
		senão 
			nomes[] := nome
			telefones[] := telefone
		fim se
		le #1 MAIUSC nome
		checa erro
	fim enquanto
@fim
	fecha #1
fim função
* fim da função le_arquivo

A última melhoria é um pouco mais trabalhosa. Agora, o usuário pode digitar apenas um pedaço
do nome e o script deverá informar todos os nomes (juntamente com os telefones) em que
o pedaço digitado pelo usuário aparecem.

A função POS, agora, não nos ajuda diretamente. É preciso caminhar pela lista de nomes e,
para cada nome que esteja na lista, devemos verificar se "dado" é uma subcadeia do nome.
O seguinte trecho implementa esta ideia:

	repete n TAMANHO(nomes)
		posição := n - 1
		se POS (dado, nomes[posição]) > 0
			escreve "NOME: " nomes[posição]
			escreve "TELEFONE: " telefones[posição]
		fim se
	fim repete
		
Observe que:

1. A variável de controle "n" assume os valores 1, 2, ..., sucessivamente, até TAMANHO(nomes),
   que é a cardinalidade da lista armazenada em nomes. Porém, as posições na lista começam de 0
   e não de 1. Desta maneira, não podemos usar diretamente o valor de "n" para indexar a lista.
   Devemos usar o valor de "n" menos um, que é exatamente o valor atribuído à variável "posição".
   
2. A função POS verifica se "dado" é uma subcadeia do nome armazenado na posição "posição" da
   lista. Se for, nome e telefone são impressos na tela.
   
A nova versão da função "pesquisa" fica assim: 

* início da função pesquisa
função pesquisa (dado) : posição, n
	se "0123456789" *=* dado[1]
		posição := POS (dado, telefones)
		se posição < 0
			escreve dado " NÃO foi encontrado na agenda"
		senão
			escreve "NOME: " nomes[posição]
			escreve "TELEFONE: " telefones[posição]
		fim se
	senão
		repete n TAMANHO(nomes)
			posição := n - 1
			se POS (dado, nomes[posição]) > 0
				escreve "NOME: " nomes[posição]
				escreve "TELEFONE: " telefones[posição]
			fim se
		fim repete
	fim se
fim função
* fim da função pesquisa

e a versão final do script, agora completo, é a seguinte:

* início do script
* programa principal
nomes := []
telefones := []

le_arquivo ()
processa_comandos ()
termina

* função le_arquivo   
função le_arquivo : nome, telefone, erro
	abre #1 "AGENDA.TXT"
	le #1 MAIUSC nome
	checa erro
	enquanto erro = 0 
		le #1 MAIUSC telefone
		checa erro
		se erro <> 0
			escreve "O arquivo AGENDA.TXT acabou inesperadamente"
			desvia @fim
		fim se
		se POS (nome, nomes) >= 0
			escreve "O nome " nome " está repetido no arquivo"
		senão 
			nomes[] := nome
			telefones[] := telefone
		fim se
		le #1 MAIUSC nome
		checa erro
	fim enquanto
@fim
	fecha #1
fim função
* fim da função le_arquivo

* início da função processa_comandos
função processa_comandos : dado
	escreve "Dado a pesquisar: " &
	le MAIUSC dado
	enquanto dado <> ""
		pesquisa (dado)
		escreve "Dado a pesquisar: " &
		le MAIUSC dado
	fim enquanto
fim função
* fim da função processa_comandos

* início da função pesquisa
função pesquisa (dado) : posição, n
	se "0123456789" *=* dado[1]
		posição := POS (dado, telefones)
		se posição < 0
			escreve dado " NÃO foi encontrado na agenda"
		senão
			escreve "NOME: " nomes[posição]
			escreve "TELEFONE: " telefones[posição]
		fim se
	senão
		repete n TAMANHO(nomes)
			posição := n - 1
			se POS (dado, nomes[posição]) > 0
				escreve "NOME: " nomes[posição]
				escreve "TELEFONE: " telefones[posição]
			fim se
		fim repete
	fim se
fim função
* fim da função pesquisa
* fim do script 
  

Exercício de avaliação (enviar para scriptvox@gmail.com até o meio-dia de 27 de fevereiro de 2012)

Falta um pequeno detalhe nesta última versão do script. Quando o pedaço de nome digitado pelo
usuário não casa com nenhum nome da lista, nada é dito. Modifique a função pesquisa de modo que
uma advertência seja escrita na tela quando o pedaço de nome não for encontrado na lista de nomes. 

Bom estudo! 

Oswaldo Vernet
