Uma Introdução - Universidade de Coimbra

Transcrição

Uma Introdução - Universidade de Coimbra
Qual é o problema da seguinte rotina?
// Rotina que troca o valor de duas variáveis
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
Uma Introdução
Apenas funciona para inteiros. Se necessitarmos de trocar
quaisquer outros tipos de dados, temos de definir várias
versões da mesma rotina...
void swap(double& a, double& b)
void swap(unsigned&a, unsigned& b)
void swap(string&a, string& b)
...
Paulo Marques
Departamento de Eng. Informática
Universidade de Coimbra
[email protected]
3
Sobre o que é que vamos falar?
Programação utilizando genéricos (templates)
Permite criar uma família de funções, parametrizadas por
um tipo de dados abstracto.
Primeira parte (expositiva):
Meta-programação
Existe há anos em C+ + : a STL é baseada neles
Adição recente em Java (J2SE 5.0) e .NET (2.0)
Semântica de programação potencialmente complicada
(... e também de implementação das linguagens!)
Programação baseada em genéricos (templates)
Apresentação da STL
Colecções simples: “vector”, “list”, “deque”, “stack”
Colecções associativas: “map” e “multimap”
Colecções associativas múltiplas: “set” e “multiset”
Brevemente: Extensões: “hash_set” e “hash_map”
Brevemente: Algoritmos da STL
// Rotina que troca o valor de duas variáveis quaisquer
template<class T>
void myswap(T& a, T& b)
{
T tmp = a;
a = b;
int a = 10;
b = tmp;
int b = 20;
}
Segunda parte (prática):
Alguns exercícios
Caso de estudo
myswap<int>(a, b);
2
4
Problemática das tabelas de tamanho fixo
O resultado da execução...
Uma grande parte das linguagens actuais fornece ao
programador tabelas de tamanho fixo
(e.g. C, C+ + , Java, .NET)
Uma vez criadas, o seu tamanho não pode ser redefinido
No entanto, em muitas circunstâncias, o programador não sabe
quantos elementos é que a sua tabela terá de armazenar!
// Número máximo de pessoas
const unsigned MAX_SIZE = 10;
// Tabela onde armazenar a altura das pessoas
double heights[MAX_SIZE];
unsigned totalPeople = 0;
// Armazena sucessivamente pessoas
double height;
while (cin >> height)
heights[totalPeople++] = height;
Ao chegar-mos à décima primeira pessoa... BANG!
5
Tabela Dinâmica Genérica
7
Interface de DynamicArray
A solução passa por criar uma classe “Tabela Dinâmica”,
genérica, capaz de armazenar qualquer tipo de dados
template<class T>
class DynamicArray
{
private:
const static int _DEFAULT_CAPACITY = 1;
int main()
{
// Tabela dinâmica para armazenar alturas
DynamicArray<double> heights;
unsigned int _size;
unsigned int _capacity;
T* _myArray;
// Lê a altura das pessoas
double altura;
while (cin >> altura)
heights.push_back(altura);
public:
DynamicArray(unsigned int size = 0);
virtual ~DynamicArray(void);
// Calcula e mostra a média das alturas
double averageHeight = 0.0;
for (unsigned i=0; i<heights.size(); i++)
averageHeight+= heights[i];
averageHeight/= heights.size();
void push_back(T& element);
unsigned int size() const;
T& operator[](unsigned int index) const;
};
cout << averageHeight << endl;
return 0;
}
6
8
Construtor, Destrutor e size() de DynamicArray
Aviso...
A forma como os compiladores utilizam templates varia radicalmente:
é compiler specific: consultar a documentação
Uma possível abordagem relativamente segura e genérica:
// Construtor, leva como parâmetro o tamanho inicial da tabela
template<class T>
DynamicArray<T>::DynamicArray(unsigned int size) :
_size(size), _capacity(_DEFAULT_CAPACITY)
{
if (_size > 0)
_capacity = _size;
_myArray = new T[_capacity];
}
Incluir o ficheiro .h (i.e. “dynamic_array.h”)
Dependentemente do tipo de compilador, definir a macro
TEMPLATE_INCLUSIVE_MODEL na Makefile (ou projecto)
Dependentemente do compilador, incluir na compilação a implementação
do template .cpp (i.e. “dynamic_array.cpp”)
dynamic_array.h
// Destructor
template<class T>
DynamicArray<T>::~DynamicArray(void)
{
delete[] _myArray;
}
#ifndef _DYNAMIC_ARRAY_H_
#define _DYNAMIC_ARRAY_H_
dynamic_array.cpp
#include "dynamic_array.h"
template<class T>
class DynamicArray
{
...
};
// Retorna o número de elementos na tabela
template<class T>
unsigned int DynamicArray<T>::size() const
{
return _size;
}
...
#ifdef TEMPLATE_INCLUSIVE_MODEL
#include "dynamic_array.cpp"
#endif
#endif
my_program.cpp
#include "dynamic_array.h"
...
9
Principais operações: push_back() e acesso []
11
STL
STL = Standard Template Library
// Acrescenta um elemento à tabela, fazendo uma cópia do mesmo
template<class T>
void DynamicArray<T>::push_back(T& element)
{
if (_size == _capacity)
{
_capacity = _capacity * 2;
T* newArray = new T[_capacity];
for (unsigned i=0; i<_size; i++)
newArray[i] = _myArray[i];
delete[] _myArray;
_myArray = newArray;
}
_myArray[_size] = element;
_size++;
}
Biblioteca contendo algoritmos e estruturas de dados
genéricas para uso em C+ +
Baseada nas ideias sobre “programação genérica” de
Alexander Stepanov.
// Retorna uma referência para um elemento da tabela
template<class T>
T& DynamicArray<T>::operator[](unsigned int index) const
{
return _myArray[index];
}
10
Enquanto trabalhava nos Bell Labs e mais tarde na HP,
implementou o core de uma biblioteca de programação genérica
em C+ +
(1993) É convidado a apresentar as suas ideias sobre
programação genérica ao comité de ANSI/ ISO do C+ + . A resposta
é entusiástica. Em 1994 a sua proposta para inclusão da biblioteca
no standard do C+ + é aprovada.
(1994) A HP publica livremente na internet a implementação da
STL. Esta implementação, mais tarde modificada pela SGI,
constitui a base na maior parte das implementações actualmente
disponíveis.
12
HP / SGI
Componentes
Containers
Microsoft’s Visual Studio < vector>
Armazenam colecções de objectos
Algoritmos Genéricos
GNU’s G+ + < vector.tcc>
Realizam operações sobre containers
Iteradores
Permitem percorrer um determinado container
Function Objects
Realizam cálculos ou combinam dados
Adaptadores
Modificam a interface de um componente
(e.g. Stack, PriorityQueue)
Allocators
Permitem ao programador controlar explicitamente o uso de
memória [ perigoso mas poderoso!]
13
STL – Modelo Plug-and-Play
Container
Classes
Iterators
Colecções Lineares
Generic
Algorithms
insert
erase
sort
less
list
insert
erase
find
equal
merge
greater
ostream
vector< T> :
Corresponde a uma tabela dinâmica, aumentando automaticamente
de tamanho sempre que é necessário.
Function
Objects
Vector
istream
15
Acesso aleatório aos seus elementos muito rápido: O(1)
Inserir e apagar elementos do final é eficiente. Tipicamente O(1)
Relativamente lento a eliminar elementos do início e do meio.
deque< T> :
Semelhante a vector< T> mas permite inserir e apagar elementos de
forma muito eficiente do início e fim: O(1).
istream_
iterator
list< T> :
Corresponde a uma lista duplamente ligada.
ostream_
iterator
Inserir/ apagar elementos é rápido desde que se esteja no local correcto:
O(1). Mas, tal como o acesso, em geral, é O(n).
Não suporta acesso aleatórios aos elementos. No entanto também não
desperdiça espaço.
A nossa área de foco
14
16
Exemplo utilizando vector<T>
Exemplo utilizando iteradores
#include <vector>
using namespace std;
#include <vector>
#include <string>
#include <iostream>
using namespace std;
void ex_vector()
{
// Tabela dinâmica para armazenar alturas
vector<double> heights;
void ex_iterator()
{
// Tabela para armazenar palavras
vector<string> words;
// Lê a altura das pessoas
double altura;
while (cin >> altura)
heights.push_back(altura);
// Lê as palavras do stdin para o vector
string word;
while (cin >> word)
words.push_back(word);
// Calcula e mostra a média das alturas
double averageHeight = 0.0;
for (unsigned i=0; i<heights.size(); i++)
averageHeight+= heights[i];
// Imprime as palavras lidas
vector<string>::iterator it = words.begin();
while (it != words.end())
{
cout << *it << endl;
++it;
}
}
averageHeight/= heights.size();
cout << averageHeight << endl;
}
17
Iteradores
19
Exemplo utilizando iteradores reversos
Um iterador permite percorrer uma colecção seguindo uma
determinada ordem
Um iterador é uma inner class de cada colecção particular, tendo por
nome “iterator”. Para declarar um é necessário utilizar o operador de
abrangência ::
void ex_reverse_iterator()
{
// Tabela para armazenar palavras
vector<string> words;
// (...)
vector< int> ::iterator it = ...
// Imprime as palavras pela ordem inserida
cout << "Palavras pela ordem de insercao:" << endl;
Para avançar/ recuar um iterador utilizam-se os operadores ++ e --
vector<string>::iterator it = words.begin();
for (; it != words.end(); it++)
cout << "\t" << *it << endl;
it+ + avança o iterador para o próximo elemento, it-- recua-o
É ainda possível avançar mais do que um elemento: it+ = 2
Para aceder ao elemento apontado utiliza-se o operador *
// Imprime as palavras pela ordem inversa
cout << endl << "Palavras pela ordem inversa:" << endl;
* it retorna o valor actualmente apontado
vector<string>::reverse_iterator rit = words.rbegin();
for (; rit != words.rend(); rit++)
cout << "\t" << *rit << endl;
begin() retorna um iterador para o início de uma colecção
end() retorna um iterador para além do final da colecção (sentinela)
}
18
20
Resultado da execução...
Alguns métodos importantes de vector<T> (2)
void push_back(const T&)
Adiciona um elemento ao final do vector
void pop_back()
Remove o último elemento
void clear()
Apaga todos os elementos do vector
Nota: Para todos os métodos que
retornam elementos ou iteradores,
existem versões que retornam os
correspondentes constantes. E.g.
const T& vector< T> ::front() const
vector< T> ::iterator begin()
Retorna um iterador para o início do vector
vector< T> ::iterator end()
Retorna um iterador para um elemento após o final do vector (sentinela)
vector< T> ::reverse_iterator rbegin()
Retorna um iterador reverso que começa no último elemento do vector
vector< T> ::reverse_iterator rend()
Retorna um iterador reverso que aponta para antes do início do vector
void insert(iterator pos, const T& x)
Insere um elemento antes de uma posição apontada por um iterador
iterator erase(iterator pos)
Apaga um elemento apontado por um iterador
21
Alguns métodos importantes de vector<T>
23
Outros aspectos...
Os vectores armazenam cópias dos elementos, não
referências
vector()
Construtor por omissão: o vector fica vazio
vector(size_type n)
Construtor que cria um vector com n objectos, com o seu valor por
omissão (uso do construtor default)
size_type size() const
Retorna o número de elementos no vector
size_type capacity() const
Retorna a capacidade actual do vector
bool empty() const
Retorna se o vector está vazio
T& operator[] (size_type n)
Retorna uma referência para o elemento n
T& front()
Retorna referência para o primeiro elemento
T& back()
Retorna referência para o último elemento
Muitas vezes, é mais sensato armazenar referências
(ponteiros) para os objectos do que armazenar os
objectos em si.
Semântica do Java e .NET
Importante se os objectos necessitam de ser utilizados em
diversos locais!
Sempre que o vector aumenta de tamanho, é invocado o
construtor por omissão para todos os elementos da nova tabela
interna; todos os elementos são copiados; é invocado o destrutor
de cada um dos objectos antigos
PESADO!
Se à priori se souber que vão ocorrer um certo número de
inserções, é sensato reservar o espaço.
22
24
deque<T>
Exemplo deque<T> (2)
Semelhante a vector< T> mas permite inserir e apagar
elementos de forma muito eficiente do início e do final da
colecção.
void ex_deque()
{
Reparticao financas;
financas.adicionaPedido("Impostos_1");
financas.adicionaPedido("Impostos_2");
financas.adicionaPedido("Impostos_3");
Óptimo para implementar sistemas tipo produtor/ consumidor
void deque< T> ::push_front(const T& x)
Adiciona um elemento ao início
void deque< T> ::push_back(const T& x)
Adiciona um elemento ao final
T& deque< T> ::front()
Obtem o primeiro elemento
T& deque< T> ::back()
Obtem o último elemento
void deque< T> ::pop_front()
Remove o primeiro elemento
void deque< T> ::pop_back()
Remove o último elemento
string pedido;
while (financas.retiraPedido(pedido))
{
cout << "PEDIDO: " << pedido << endl;
}
}
25
Exemplo deque<T>
27
list<T>
Implementa uma lista duplamente ligada
// Simula uma repartição pública (processamento por ordem)
class Reparticao
{
private:
deque<string> _pedidos;
Suporta iteradores para a frente e para trás
Ao contrário de vector< T> e deque< T> , não suporta acesso
aleatório (operador [ ] ), apenas acesso via iteradores. Tirando isso,
o interface é semelhante a deque< T> : push_front(), push_back(),
pop_front(), pop_back(), e iteradores.
public:
// Adiciona um pedido ao fim da lista de trabalho a processar
void adicionaPedido(const string& pedido) {
_pedidos.push_back(pedido);
}
Inserir um elemento não invalida os iteradores existentes
// Caso existam pedidos, retorna o primeiro que deu entrada
bool retiraPedido(string& pedido) {
if (_pedidos.empty())
return false;
pedido = _pedidos.front();
_pedidos.pop_front();
return true;
}
};
26
28
Exemplo de list<T> (algo “retorcido”...)
Que estrutura utilizar?
#include <list>
#include <iostream>
using namespace std;
Estrutura
void ex_list() {
// Cria uma lista para conter os números de Fibonacci
list<int> fibonacci;
fibonacci.push_back(1);
fibonacci.push_back(1);
// Dada novo número é a soma de ambos os anteriores!
list<int>::iterator current = fibonacci.begin();
for (int i=0; i<10; i++)
{
int newValue = *current++;
newValue+= *current;
fibonacci.push_back(newValue);
}
// Imprime a lista resultante
current = fibonacci.begin();
while (current != fibonacci.end())
cout << *current++ << endl;
}
Condições
vector
- Acesso aleatório aos elementos
- Baixa necessidade de eliminar elementos do início/ meio,
ou caso esta exista, apenas em colecções “pequenas”
- Estrutura base de armazenamento para apoio a outras
deque
- Condições de vector mas em que é necessário introduzir
e eliminar eficientemente elementos no início e fim da
colecção
list
- Elevada necessidade de eliminar elementos do início ou
meio da colecção, especialmente em colecções “grandes”
- Baixa necessidade de utilização de elementos
intermédios
- Colecções “grandes” em que é necessário armazenar
efectivamente os elementos (não referências). Caso se
necessite armazenar referências, considerar seriamente o
uso de vector
29
Resultado da execução...
31
Colecções Associativas – Motivação I
Suponhamos que necessitamos de armazenar 100.000
pessoas em memória. Essas pessoas serão pesquisadas
por Bilhete de Identidade.
A pesquisa de cada pessoa demora 50us
JOANA FRANCISCA
10896534
R. Fernão Lop
Qual é o problema se quisermos encontrar a pessoa
com o BI Nº 10896534?
30
Esquecendo as caches, se usarmos uma estrutura linear (e.g. vector)
Tempo de acesso a uma pessoa = 50us
Em média temos de percorrer ½ tabela = 50.000 entradas
50000 entradas X 50us = 2500000us = 2.5s!!!
32
Colecções Associativas – Motivação I (2)
Colecções Associativas
map< TKey,TValue> e multimap< TKey,TValue> :
Corresponde a uma árvore binária equilibrada (tipicamente, uma
árvore red-black), permitindo armazenar elementos que são
pesquisáveis por uma chave.
Se as pessoas forem armazenadas numa árvore binária
equilibrada...
Acesso aos seus elementos muito eficiente O(logN)
Os elementos são mantidos ordenados por chave
multimap permite manter vários elementos por chave
log2(100.000)
≈ 16
set< T> e multiset< T> :
Permite armazenar um conjunto elementos em que o seus próprios
valores constituem as chaves de procura.
Ou seja... permite verificar se um elemento se encontra presente ou não
multiset permite armazenar vários elementos idênticos
hash_map< TKey, TValue> e hash_set< T> :
Semelhantes aos acima mencionados mas baseados em tabelas de
dispersão (hashtables)
Inserir, apagar e pesquisar elementos é muito rápido: O(1)
Os elementos não são mantidos ordenados
Infelizmente, (ainda) não faz parte do ANSI/ ISO standard
Tempo de acesso a uma pessoa = 50us
Em média temos de percorrer ½ árvore = 16/2 entradas = 8 entradas
8 entradas X 50us = 400us = 0.0004s!!!
33
Colecções Associativas – Motivação II
Utilização de map<TKey,TValue>
É muito simpático poder escrever...
map<TKey,TValue> tabelaAssociativa;
#include <iostream>
#include <string>
#include <map>
using namespace std;
Chave de
pesquisa
void ex_map() {
map<int,string> baseDeDados;
Objecto a
guardar
tabelaAssociativa.count(key) conta o número de
ocorrências do objecto identificado por key na colecção
tabelaAssociativa[ key] = obj; coloca na tabela associativa
o objecto obj identificando-o por key
obj = tabelaAssociativa[ key] ; retira da tabela associativa o
objecto representado por key
baseDeDados[10609100] = "Carlos Manuel";
baseDeDados[10432546] = "Joaquim Antonio";
baseDeDados[34545442] = "Silvino Costa";
int numeroBI;
while (cin >> numeroBI)
{
if (baseDeDados.count(numeroBI) == 0)
cout << "Pessoa nao encontrada" << endl;
else
cout << “Nome da pessoa: " << baseDeDados[numeroBI] << endl;
}
}
35
Se o objecto não existir na tabela, é automaticamente
colocado na tabela um novo objecto com essa chave,
sendo utilizado o construtor por omissão de TValue!
34
36
Acesso aos elementos...
Uso de typedefs
É vulgar utilizarem-se typedef’s para simplificar o código
Duas pesquisas na árvore!
(...)
if (baseDeDados.count(numeroBI) == 0)
cout << "Pessoa não encontrada." << endl;
else
cout << "Pessoa: " << baseDeDados[numeroBI] << endl;
// Definição da Base-de-dados
map<int,string> baseDeDados;
typedef pair<int,string> pessoa;
typedef map<int,string>::iterator bd_iterator;
(...)
// Coloca pessoas na BD
// (...)
Uma única pesquisa na árvore!
(...)
// Localiza a pessoa com o BI 10609129
bd_iterator it = baseDeDados.find(10609129);
if (it != baseDeDados.end())
{
pessoa bi_nome = *it++;
cout << "BI:
" << bi_nome.first << endl;
cout << "Nome: " << bi_nome.second << endl;
}
map<int,string>::iterator result = baseDeDados.find(numeroBI);
if (result == baseDeDados.end())
cout << "Pessoa não encontrada." << endl;
else
cout << "O nome da pessoa é: " << result->second << endl;
(...)
37
A operação map<TKey,TValue>::find()
39
Uso de typedefs (2)
O método find retorna um iterador que refere uma
estrutura pair< TKey,TValue>
Vejamos o conteúdo da base-de-dados...
#include <iostream>
#include <string>
#include <map>
using namespace std;
O primeiro elemento do pair é a chave do objecto
O segundo elemento é o valor do objecto
typedef map<int,string> BaseDeDados;
typedef pair<int,string> pessoa;
typedef map<int,string>::iterator BD_iterator;
Um pair é algo semelhante a:
void ex_map3() {
BaseDeDados bd;
template <class T, class Q>
struct pair {
T first;
Q second;
}
bd[10324]
bd[34434]
bd[12667]
bd[76768]
=
=
=
=
"Joana Sampaio";
"Patricio Domingues";
"Bruno Cabral";
"Catarina Reis";
BD_iterator it = bd.begin();
while (it != bd.end()) {
pessoa bi_nome = *it++;
cout << "BI:
" << bi_nome.first << endl;
cout << "Nome: " << bi_nome.second << endl << endl;
}
}
38
40
Resultado da execução...
Alguns métodos importantes de map<TKey,TValue>
map()
Construtor por omissão: o map fica vazio
map< TKey,TValue> ::iterator begin()
Retorna um iterador para o primeiro elemento
map< TKey,TValue> ::iterator end()
Retorna um iterador para além do último elemento
size_type size() const
Retorna o número de elementos no map
bool empty() const
Retorna se a tabela está vazia
map< TKey,TValue> ::iterator find(const TKey& key)
Encontra o elemento de chave key
size_type erase(const TKey& key)
Elimina o elemento de chave key
size_type erase(map< TKey,TValue> ::iterator it)
Elimina o elemento apontado por it
size_type count(const TKey& key)
Conta o número de elementos presentes com a chave key
TValue& operator[ ] (const Tkey& key)
Note-se que os elementos são
mantidos ordenados no map!
41
Alguns aspectos importantes de map<TKey,TValue>
Retorna uma referência para o elemento de chave key; caso
necessário, acrescentando um novo à tabela
43
mutimap<TKey,TValue>
Bastante semelhante a map mas permite ter vários valores para a
mesma chave.
Os elementos são mantidos ordenados no map
Tal é intrínseco à árvore red-black associada.
A classe TKey tem de suportar a noção de ordem
(i.e. o operador “< “ tem de ser válido – definido!)
typedef multimap<string,int> AgendaTelefonica;
typedef multimap<string,int>::iterator agenda_iterator;
typedef pair<string,int> entrada_agenda;
void ex_multimap() {
AgendaTelefonica telefones;
Caso se coloque na tabela um elemento que já esteja na
mesma (i.e. que tenha a mesma chave), o que lá se
encontra é substituído
telefones.insert(entrada_agenda("Paulo Marques",
telefones.insert(entrada_agenda("Paulo Marques",
telefones.insert(entrada_agenda("Rita Queiroz",
telefones.insert(entrada_agenda("Bruno Cabral",
telefones.insert(entrada_agenda("Bruno Cabral",
A classe multimap permite armazenar vários elementos para a
mesma chave
914144687));
964324546));
933409876));
918788755));
930012232));
string pessoa("Paulo Marques");
cout << pessoa << ":" << endl;
Os iteradores de map apontam sempre para tipos
pair< TKey,TValue>
agenda_iterator it = telefones.find(pessoa);
for (unsigned i=0; i<telefones.count(pessoa); i++, it++) {
cout << "\t" << it->second << endl;
}
42
}
44
Notas sobre mutimap<TKey,TValue>
multiset<T>
Semelhante a set< T> , mas os elementos podem aparecer mais do
que uma vez.
Os elementos têm de ser inseridos usando insert(), com
um pair< TKey,TValue> . Não existe operador de acesso [ ] .
void ex_multiset() {
multiset<unsigned> notasExame;
find() retorna um iterador para o primeiro elemento
encontrado
// Gera aleatoriamente um conjunto de notas (0-20)
for (unsigned i=0; i<200; i++) {
int nota = rand()%11 + rand()%11;
notasExame.insert(nota);
}
O iterador não é para percorrer todos os elementos
correspondentes à pesquisa. É apenas uma referência para o
primeiro.
Dado que multimap está garantidamente ordenado, os seguintes
podem ser acedidos avançando o iterador até um número de
vezes igual a count()
Tal como em map, o iterador refere-se a uma estrutura pair. O
primeiro elemento contém a chave, o segundo o valor
// Gera um histograma dos resultados
cout << setw(6) << "Nota" << " | " << setw(6) << "Vezes |" << endl;
for (unsigned nota=0; nota<=20; nota++) {
cout << setw(6) << nota << " | " <<
setw(6) << notasExame.count(nota) << "|";
for (unsigned j=0; j<notasExame.count(nota); j++)
cout << "#";
cout << endl;
}
45
set<T>
}
47
O resultado da execução...
Semelhante a map mas os elementos são a própria chave.
A principal utilidade é testar de um elemento se encontra presente
ou não; secundariamente, armazenar elementos (se bem que
vector ou list poderão ser melhor alternativas)
Os elementos são mantidos ordenados
(necessidade da existência do operador “< “)
void ex_set() {
// Pessoas que se encontram num edifício
set<string> edificio;
// Pessoas as entrarem e a saírem do edifício
edificio.insert("Carlos");
edificio.insert("Miguel");
edificio.erase("Carlos");
edificio.insert(“Maria");
// Verifica se "Miguel" está presente
if (edificio.find("Miguel") != edificio.end())
cout << “Miguel presente no edifício!" << endl;
else
cout << “Miguel fora do edifício." << endl;
}
46
48
Porque é que o seguinte código não é válido?
hash_map<TKey,TValue>, hash_set<T>
Ambas as classes têm o mesmo interface do que map e
set no entanto, a implementação subjacente é uma tabela
de dispersão (hashtable)
struct Pessoa
{
int
BI;
string nome;
// ######################################
Pessoa(int o_BI, const string& o_nome) :
BI(o_BI), nome(o_nome)
{
}
Não sabemos como
};
void ex_set3()
{
set<Pessoa> filaDoMercado;
O operador “< “ não
se encontra definido!!
Inserir, remover e pesquisar elementos é muito rápido
Hashtable
insert("Paulo Marques", 30)
inserir na árvore
subjacente...
hash(“Paulo Marques”) = 32748
0
32748%7 = 2
32748%7 = 2
filaDoMercado.insert( Pessoa(432532, "Jorge Manuel") );
hash(“Paulo Marques”) = 32748
}
1
2
30
3
4
5
find("Paulo Marques")
6
49
Não esquecer...
hash_map<TKey,TValue>, hash_set<T> (2)
Se vão ser utilizados tipos definidos pelo programador
como chaves em map< TKey,TValue> e set< T> , é
necessário, no mínimo, que o operador de ordem (“< “)
esteja definido.
Actualmente não fazem parte do standard
É muito provável que venham a fazer
A maior parte dos compiladores inclui-as, embora variando o
espaço de nomes onde se encontram (e.g. std::, stdext::)
struct Pessoa
{
int
BI;
string nome;
// ---------------Pessoa(int o_BI, const string& o_nome) :
BI(o_BI), nome(o_nome)
{
}
Os elementos não se encontram ordenados
Apenas o operador de igualdade é necessário (= = )
O sistema tem de saber calcular um código de dispersão
(hashcode) sobre os objectos usados como chave
bool operator<(const Pessoa& other) const
{
return BI < other.BI;
}
};
51
Também existem os análogos de multimap e multiset:
hash_multimap e hash_multiset
50
52
Um teste de performance...
Adaptadores
Para além dos Containers básicos, existe um conjunto de classes que
mascaram essas colecções e implementam um conjunto de estruturas
de dados bastante úteis.
stack< T> :
Armazena um conjunto de elementos permitindo inserir e retirar elementos
segundo uma política FIRST-IN-LAST-OUT
Tipicamente utiliza um deque< T> como estrutura subjacente
push(), pop(), top()
queue< T> :
Armazena um conjunto de elementos permitindo inserir e retirar elementos
segundo uma política FIRST-IN-FIRST-OUT
Tipicamente utiliza um deque< T> como estrutura subjacente
push(), pop(), front(), back()
priority_queue< T> :
Armazena um conjunto de elementos mantendo-os automaticamente
ordenados
Tipicamente utiliza um vector< T> como estrutura subjacente (!! Cuidado !!)
push(), pop(), top()
53
Que estrutura utilizar?
Estrutura
map /
multimap
55
Algoritmos
A STL possui um largo conjunto de algoritmos que actuam
sobre colecções
Condições
- Necessidade de armazenar elementos em que os mesmos
têm de ser rapidamente pesquisados por uma chave de
procura
- Necessidade de armazenar elementos (automaticamente)
ordenados de acordo com um certo critério
- Caso só possa existir um elemento armazenado por chave de
procura utiliza-se o map, caso contrário, o multimap
set /
multiset
- Nas condições de map/ multimap mas em que a chave de
procura é o próprio elemento
hash_map /
hash_set
- Não existe necessidade de obedecer ao standard ANSI C+ +
- Necessidade de uma maior velocidade de inserção, pesquisa
e apagamento do que em map/ multimap
- Não é necessário (ou não faz sentido) existir um
ordenamento por chave
Os algoritmos dividem-se em categorias:
Não Modificantes: for_each(), find(), find_if(), adjacent_find(),
find_first_of(), count(), count-if(), mismatch(), equal(), search(),
search_n(), find_end(), ...
Modificantes: copy(), copy_n(), copy_backwards(), swap(),
replace(), replace_if(), replace_copy(), replace_copy_if(), fill(),
fill_n(), remove(), unique(), ...
Ordenamento: sort(), binary_sort(), includes(), set_union(),
set_intersection(), min(), max(), ...
Numéricos: accumulate(), inner_product(), partial_sum(), power(),
...
Fortemente baseados no uso de iteradores
54
56
Ordenar um vector
Mas, podemos re-escrever o código...
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int random_number() {
return rand() % 100;
}
void print(vector<int>& v) {
for (unsigned i=0; i<v.size(); i++)
cout << v[i] << "\t";
cout << endl;
}
void ex_algorithms2()
{
// v irá conter 10 valores aleatórios
vector<int> v(10);
generate(v.begin(), v.end(), random_number);
void ex_algorithms() {
// Um vector não ordenado
vector<int> v;
// Imprime a tabela
for_each(v.begin(), v.end(), print_element<int>());
cout << endl;
// Acrescenta 10 números aleatórios ao vector
for (unsigned i=0; i<10; i++)
v.push_back(rand()%100);
// Ordena a tabela
sort(v.begin(), v.end());
// Imprime a tabela
for_each(v.begin(), v.end(), print_element<int>());
cout << endl;
// Mostra o vector antes e depois de ordenado
print(v);
sort(v.begin(), v.end());
print(v);
}
}
57
O resultado da execução...
59
print_element<int>
Trata-se de uma “função unária” definida por nós...
A STL define outros tipos de funções (e.g. binárias... ☺)
#include <functional>
template <class T>
struct print_element : public unary_function<T,void>
{
void operator()(T& toPrint)
{
cout << setw(5) << toPrint;
}
};
58
60
print_element<int>
Um último exemplo do uso de algoritmos...
#define N_ELEMENTS(table,type)
Trata-se de uma “função unária” definida por nós...
A STL define outros tipos (e.g. binária... ☺)
( sizeof(table) / sizeof(type) )
void ex_set2() {
// Cria duas tabelas estáticas
int tabelaA[] = { 1, 2, 3, 4 };
int tabelaB[] = { 3, 4, 5, 6 };
Tipo que devolve
// Calcula um conjunto resultante contendo a intercepção entre elas
set<int> result;
insert_iterator< set<int> > addToResult_it(result, result.begin());
Tipo do parâmetro de entrada
#include <functional>
set_intersection(tabelaA, tabelaA + N_ELEMENTS(tabelaA,int),
tabelaB, tabelaB + N_ELEMENTS(tabelaB,int),
addToResult_it);
template <class T>
struct print_element : public unary_function<T,void>
{
void operator()(T& toPrint)
{
cout << setw(5) << toPrint;
}
};
// Imprime o resultado
ostream_iterator<int> output(cout, " ");
cout << endl << "Conjunto A: \t";
copy(tabelaA, tabelaA + N_ELEMENTS(tabelaA,int), output);
cout << endl << "Conjunto B: \t";
copy(tabelaB, tabelaB + N_ELEMENTS(tabelaB,int), output);
cout << endl << "INTER(A,B): \t";
copy(result.begin(), result.end(), output);
Método invocado na função
61
E o resultado é...
}
63
Iteradores
No exemplo anterior vimos que é possível construir um
iterador para inserções: insert_iterator
Vimos também que é possível criar um iterador que envia
os dados para uma stream de output: ostream_iterator
Toda a STL é baseada em iteradores, dos quais existem
os seguintes tipos:
62
Input Iterators: Referem um objecto e podem ser incrementados
(apontados) para o próximo.
Output Iterators: Permitem escrever objectos e ser incrementados
Forward Iterator: Permitem ler e escrever, sendo apenas
incrementáveis para o próximo.
Bidirectional Iterators: Similar aos forward iterators mas permitem
avançar ou recuar.
Random Access Iterators: Permitem acessos aleatórios à colecção
subjacente (inc. aritmética de ponteiros)
64
Iteradores (2)
Uma questão importante...
Um aspecto extremamente importante quando se usa a STL (e, na
verdade, C+ + ), é não fazer cópias desnecessárias de objectos
Usandos estes conceitos de iteradores, os tipos concretos
são...
istream_iterator
ostream_iterator
reverse_iterator
reverse_bidirectional_iterator
insert_iterator
front_insert_iterator
back_insert_iterator
input_iterator
output_iterator
forward_iterator
bidirectional_iterator
random_access_iterator
Regra geral, os objectos devem ser guardados num local. Caso estes
sejam necessários noutras classes/métodos, devem de se utilizar
referências
Evita a chamada a copy-constructors e assigment operators
Cada referência ocupa apenas 4 bytes (máquinas de 32bits)
Felizmente, em geral
não é necessário
preocuparmo-nos com
estes detalhes...
65
Onde estamos nós?
OK!
Container
Classes
Vector
list
istream
insert
erase
insert
erase
Pequeno exemplo
Generic
Algorithms
sort
find
Function
Objects
Cada pessoa tem um “nome”,
uma “morada” e um “BI”
Vamos assumir que não
existem duplicados (pessoas a
viverem no mesmo local, com o
mesmo nome ou com o mesmo
BI)
less
equal
Empregados
-_nomeIndex
-_moradaIndex
-_BIIndex
+adicionaPessoa()
+procuraPorNome()
+procuraPorMorada()
+procuraPorBI()
Pessoa
*
1
+_nome
+_morada
+_bi
*
*
*
NomeIndex : map<string,Pessoa*>
1
MoradaIndex : map<nome,Pessoa*>
1
A classe tem de suportar
pesquisas de forma eficiente
por “nome”, “morada” e
“bilhete de identidade”
istream_
iterator
merge
ostream
Pretende-se desenhar uma
classe EMPREGADOS que
permite armazenar PESSOAs
Pouco aprofundado
Iterators
67
greater
ostream_
iterator
BIIndex : map<int,Pessoa*>
1
Estes três objectos
correspondem
às instâncias em
"Empregados"
As pesquisas devolvem uma
cópia da ficha (Pessoa).
[ Porquê uma cópia?]
66
68
Classe Empregados
Implementação de Empregados
Empregados::Empregados()
{
}
class Empregados
{
private:
map<string,Pessoa*> _nomeIndex;
map<string,Pessoa*> _moradaIndex;
map<int,Pessoa*>
_BIIndex;
void Empregados::adicionaPessoa(const Pessoa& pessoa)
{
Pessoa* p = new Pessoa(pessoa);
_nomeIndex[pessoa.nome]
= p;
_moradaIndex[pessoa.morada] = p;
_BIIndex[pessoa.bi]
= p;
}
public:
Empregados();
virtual ~Empregados();
void adicionaPessoa(const Pessoa& pessoa);
Empregados::~Empregados()
{
map<int,Pessoa*>::iterator it = _BIIndex.begin();
while (it != _BIIndex.end())
{
Pessoa* p = it->second;
++it;
delete p;
}
}
bool procuraPorNome(const string& nome, Pessoa& result);
bool procuraPorMorada(const string& morada, Pessoa& result);
bool procuraPorBI(int bi, Pessoa& result);
};
69
Estrutura Pessoa
struct Pessoa
{
string
string
int
71
Implementação de Empregados (2)
bool Empregados::procuraPorNome(const string& nome, Pessoa& result)
{
map<string,Pessoa*>::iterator it = _nomeIndex.find(nome);
if (it == _nomeIndex.end())
return false;
else {
result = *(it->second);
return true;
}
}
nome;
morada;
bi;
Pessoa() {}
Pessoa(const string& o_nome, const string& a_morada, int o_bi)
: nome(o_nome), morada(a_morada), bi(o_bi) {}
bool Empregados::procuraPorMorada(const string& nome, Pessoa& result)
{ // Similar ao método acima }
void print()
{
cout << "\t [ " << nome << " / " << morada << " / "
<< bi << "]" << endl;
}
};
70
bool Empregados::procuraPorBI(int bi, Pessoa& result)
{
map<int,Pessoa*>::iterator it = _BIIndex.find(bi);
if (it == _BIIndex.end())
return false;
else {
result = *(it->second);
return true;
}
}
72
Test Drive
Para aprender mais...
Effective STL: 50 Specific Ways to Improve Your
Use of the Standard Template Library, by Scott
Meyers
Addison-Wesley, June 2001
Empregados loja;
loja.adicionaPessoa(Pessoa("Maria", "Coimbra, 2", 234345));
loja.adicionaPessoa(Pessoa("Sofia", "Lisboa, 3", 565655));
loja.adicionaPessoa(Pessoa("Tania", "Porto, 5",
435675));
// ##########################################
const int N = 4;
const string nomes[N]
= { "Maria", "Carlos", "Antonio", "Tania" };
The C+ + Standard Library : A Tutorial and
Reference, by Nicolai M. Josuttis
Addison-Wesley Professional, August 1999
Pessoa resultado;
for (unsigned i=0; i<N; i++)
{
cout << "A pesquisar nome: " << nomes[i] << endl;
if (loja.procuraPorNome(nomes[i], resultado))
resultado.print();
else
cout << "\t Nao encontrado" << endl;
}
C+ + Primer, 4th Edition
by Stanley B. Lippman et. al.
Addison-Wesley Professional, Feb. 2005
“A bíblia laica do C+ + ”, com cobertura adequada da
STL
(...)
Standard Template Library Programmer's Guide
SGI Online Reference: http:/ /www.sgi.com/ tech/ stl/
73
Resultado da Execução...
75
IMPORTANT NOTICE
YOU ARE FREE TO USE THIS MATERIAL FOR YOUR
PERSONAL LERNING OR REFERENCE, DISTRIBUTE IT
AMONG COLLEGUES OR EVEN USE IT FOR TEACHING
CLASSES. YOU MAY EVEN MODIFY IT, INCLUDING MORE
INFORMATION OR CORRECTING STANDING ERRORS.
THIS RIGHT IS GIVEN TO YOU AS LONG AS YOU KEEP
THIS NOTICE AND GIVE PROPER CREDIT TO THE
AUTHOR. YOU CANNOT REMOVE THE REFERENCES TO
THE AUTHOR OR TO THE INFORMATICS ENGINEERING
DEPARTMENT OF THE UNIVERSITY OF COIMBRA.
(c) 2005 – Paulo Marques, [email protected]
74
76
Problema 2 – Anagramas
Dado um dicionário de palavras de uma língua, encontrar
todos os anagramas existentes.
Por exemplo: “barragem”, “embargar” são anagramas pois
escrevem-se exactamente com as mesmas letras.
O ficheiro “english.txt” contém um dicionário de inglês
que poderá utilizar nos seus testes.
Sessão Prática
Se desejar, poderá ser-lhe fornecido um dicionário de Português,
mas nesse caso terá de ser preocupar com os acentos...
O ficheiro também contém palavras que começam por maiúscula
Qual é o maior número de anagramas existentes?
Paulo Marques
Departamento de Eng. Informática
Universidade de Coimbra
Numa primeira fase mostre apenas os existentes
Numa segunda, mostre do maior número de anagramas para o
menor.
[email protected]
79
Problema 1 – Conta Palavras
Problema 3 – Árvore Mínima Abrangente
Implemente um programa que dado um ficheiro de texto,
conte o número de ocorrências de cada palavra nesse
ficheiro.
C
1
Assuma que o ficheiro pode ser de gigantesco (1 ou 2GB)
Assuma que o número de palavras diferentes pode ser bastante
elevado
B
Como resultado da execução deve ser enviado para o ecrã
o número de ocorrências de cada palavra (par palavra/ nº
vezes).
G
5
I
1
3
4
3
1
1
Numa primeira fase apresente o resultado por ordem alfabética de
palavras
Numa segunda fase, apresente o resultado por ordem de
ocorrência de palavras (na mais frequente para a menos)
E
F
2
D
H
J
5
1
A
10
K
Não é necessário preocupar-se com a leitura do ficheiro:
pode lê-lo do standard input:
Árvore Mínima Abrangente:
Uma árvore que passa por todos os nodos em que o custo total é mínimo.
$ ./ocorrencias < romance.txt
78
(Nota: isto não é uma árvore que minimiza o caminho/ tempo que os pacotes têm de percorrer!)
80
Árvore Mínima Abrangente
Funcionalidades do Programa
Dada a descrição de uma rede:
C
1
B
E
1
I
1
3
4
3
1
G
5
Permite modelar essa rede
Calcula a árvore mínima
abrangente
Imprime a árvore mínima
abrangente
Simula o envio de um
pacote de um nodo para
outro
F
2
D
H
J
5
1
int main()
{
Network net;
net.addConnection("A",
net.addConnection("B",
net.addConnection("D",
net.addConnection("C",
net.addConnection("D",
net.addConnection("E",
net.addConnection("F",
net.addConnection("G",
net.addConnection("F",
net.addConnection("F",
net.addConnection("H",
A
10
K
"D",
"D",
"E",
"E",
"F",
"F",
"G",
"I",
"J",
"H",
"K",
1);
1);
3);
1);
2);
4);
3);
1);
1);
5);
10);
net.calculateMinimumSpanningTree();
net.printMinimumSpanningTree();
net.sendPacket("C", "K");
return 0;
}
81
“Algoritmo de Prim”
Começa-se com uma aresta de peso mínimo
Até que não seja possível adicionar arestas
(altura em que formaria um ciclo)
Encontrar a aresta de menor peso ligado a um nodo existente na
árvore mínima actual e que não forme um ciclo se for adicionado
Adiciona-se essa aresta à árvore mínima actual
82
83