/**************************************************************************************************
 *                                                                                                *
 *            Przyklad wykorzystania klasy hash_map, autor: Maciej Zbierski, H6ISI                *
 *                                                                                                *
 **************************************************************************************************
 * Informacje od autora: Klasa hash_map jest aktualnie zastępowana przez klasę unordered_map,     *
 * dostepną w standardzie tr1. Praktycznie jedyne różnice między tymi klasami dotyczą sposobu     *
 * zmiany liczby kubełków, w których klasa przechowuje powierzone jej informacje. Klasa hash_map  *
 * nie jest obsługiwana przez kompilatory g++ od wersji 4.0, w MS Visual C++, od wersji 7.1       *
 * umieszczona jest w namespace stdext, czyli oznaczona jako przestarzała. Poniższy przykład      *
 * ilustruje wykorzystanie kontenera hash_map i powinien być uruchamiany pod kompilatorem         *
 * MS Visual C++. Do przykładu dołączono odpowiednie deklaracje makrogeneratora umożliwiające     *
 * skompilowanie tego programu pod innymi kompilatorami (przy użyciu kontenera unordered_map ze   *
 * standardu tr1).                                                                                *
 **************************************************************************************************/

#include <iostream>
#include <string>

#ifdef _MSC_VER												// Visual C++
	#include <hash_map>	
	using namespace stdext;
#else													// inne kompilatory
	#include <tr1/unordered_map>
	#define hash_map tr1::unordered_map
#endif

using namespace std;

// funkcja pomocnicza wyswietlajaca rozmiar i zawartosc mapy haszującej
void wyswietlMiesiace(hash_map<string, int>& miesiace)
{
	// wyświetl rozmiar kontenera
	cout << "W roku mamy " << miesiace.size() << " miesiecy. Oto one:" << endl;

	// wyświetl wszystkie elementy
	for (hash_map<string, int>::const_iterator iter = miesiace.begin(); iter != miesiace.end(); ++iter)
		cout << iter->first << " ma " << iter->second << " dni." << endl;

	cout << endl;
}

/* funkcja pomocnicza wyswietlająca informacje na temat rozmieszczenia elementów w mapie haszującej, czyli
 *  - liczbę kubełków, w których znajdują się elementy 
 *  - load factor, czyli stosunek liczby elementów w hashmapie do liczby kubełków (innymi słowy: ile maksymalnie elementów będzie w każdym kubełku)
 *  - rozmiar mapy haszującej - aktualny i maksymalny */
void wyswietlInformacjeTechniczne(hash_map<string, int>& miesiace)
{
	cout << "Mapa haszujaca ma nastepujaca liczbe kubelkow: " << miesiace.bucket_count() << endl;
	cout << "Aktualny load factor to: " << miesiace.load_factor() << ", a maksymalny to " << miesiace.max_load_factor() << endl;
	cout << "Aktualny rozmiar mapy to " << miesiace.size() << ", a maksymalny to " << miesiace.max_size() << endl << endl;
}

int main()
{
	// utwórz mapę haszującą
	hash_map<string, int> miesiace;	

	// dodaj przykładowe informacje z wykorzystaniem operatora []
	miesiace["styczen"]     = 31;
	miesiace["luty"]        = 28;
	miesiace["marzec"]      = 31;
	miesiace["kwiecien"]    = 30;
	miesiace["maj"]         = 31;
	miesiace["czerwiec"]    = 30;
	miesiace["lipiec"]      = 31;
	miesiace["sierpien"]    = 31;
	miesiace["wrzesien"]    = 30;
	miesiace["pazdziernik"] = 31;
	miesiace["listopad"]    = 30;
	miesiace["grudzien"]    = 31;

	/* wyświetl listę miesięcy - należy pamiętać, że mapa haszująca nie przechowuje elementów
	 * w kolejności wstawienia, więc miesiące nie będą wypisane po kolei */
	wyswietlMiesiace(miesiace);

	// Przyklad dodawania elementów:
	// deklaracja zmiennej, w której umieszczony zostanie rezultat wykonania polecenia insert
	pair<hash_map<string, int>::iterator, bool> wynikDodania;

	// próba dodania elementu o już istniejącym kluczu, powinna się nie udać, gdyż nie jest to multi mapa
	wynikDodania = miesiace.insert(pair<string, int>("luty", 29));
	cout << "Czy dodano kolejny miesiac: " << wynikDodania.second << endl;						

	// próba dodania elementu o kluczu nieistniejącym w mapie
	wynikDodania = miesiace.insert(pair<string, int>("sesja", 14));
	cout << "Czy dodano kolejny miesiac: " << wynikDodania.second << endl << endl;

	// wyświetlenie wszystkich miesięcy włącznie z nowododanym
	wyswietlMiesiace(miesiace);

	/* Przykład zmiany już istniejących elementów. Można to zrobić na dwa sposoby:
	   1) Poprzez operator [] - elementy o kluczu nieistniejącym w mapie zostaną utworzone
	   2) Poprzez funkcję find zwracającą end(), gdy element nie znajduje się w mapie
	      lub referencję do obiektu w przeciwnym przypadku - przykład poniżej */
	hash_map<string, int>::iterator znaleziony = miesiace.find("luty");
	if (znaleziony == miesiace.end())
		cout << "Nie znaleziono miesiaca 'luty' " << endl;
	else
		znaleziony->second = 29;

	// wyświetlenie miesięcy po zmianie ilości dni w miesiącu lutym
	wyswietlMiesiace(miesiace);

	// wyświetlenie informacji technicznych na temat mapy
	wyswietlInformacjeTechniczne(miesiace);

	/* zmiana maksymalnego load factor na wartość 1.0 (maksymalnie 1 element na każdy kubełek). 
	 * Jeśli dokonuje się zmiany (na niepustej hashmapie) na load factor mniejszy od aktualnego, 
	 * należy wywołać funkcję rehash (o której poniżej) z dowolnym parametrem - liczba kubełków
	 * zostanie ustawiona na najmniejszą możliwą wartość, która spełnia warunek max_load_factor.
	 * W najlepszym wypadku należy wykonywać tę funkcję gdy jeszcze mapa jest pusta 
	 * - w celu uniknięcia wywołania kosztownej funkcji rehash() */
	miesiace.max_load_factor(1.0);

	/* funkcja rehash odpowiedzialna jest za zmianę liczby kubełków na nie mniejszą niż podana
	 * jako parametr funkcji (może być większa, jeśli wymaga tego ustawienie max_load_factor). 
	 * Jest to odperaca dość kosztowna, gdyż wymaga zmiany wewnętrznej struktury przechowywania
	 * danych. */
	miesiace.rehash(32);

	// potwierdzenie wprowadzonych zmian w strukturze mapy
	wyswietlInformacjeTechniczne(miesiace);

	// usunięcie pojedyńczego wpisu
	miesiace.erase("sesja");

	// potwierdzenie usunięcia miesiąca "sesja"
	wyswietlMiesiace(miesiace);

	// wyczyszczenie całej mapy
	miesiace.clear();

	// potwierdzenie wyczyszczenia
	wyswietlMiesiace(miesiace);
}
