Lista 2

Transkrypt

Lista 2
Lista 2
Poniższe zadania mają na celu jedynie pomoc w szlifowaniu umiejętności logicznego myślenia, analizowania i rozwiązywania pewnych zagadnienień algorytmicznych.
Zadanie 1.
W algorytmach opartych na porównywaniu elementów (sortowanie, wybieranie największego elementu itp.) często wykorzystuje się obiekty zwane komparatorami (ang. comparator ) lub funkcjami porównującymi. Ich jedynym zadaniem jest pobranie dwóch elementów,
porównanie ich według jakieś kryterium i zwrócenie wyników porównania. W przypadku
tego zadania komparatorem będziemy nazywali funkcję, która przyjmuje dwa argumenty
tego samego typu i zwraca wartość typu int. Funkcja ta działa w ten sposób:
•jeżeli obydwa argumenty są według jakiegoś kryterium równe, to wtedy zwracana
jest wartość 0;
•jeżeli pierwszy argument jest według jakiegoś kryterium lepszy od drugiego argumentu, to zwracana jest wartość 1;
•jeżeli pierwszy argument jest według jakiegoś kryterium gorszy od drugiego argumentu, to zwracana jest wartość -1.
Załóżmy, że w przypadku tego zadania będziemy porównywać liczby całkowite. Najbardziej intuicyjnym komparatorem jest funkcja porównująca dwie liczby pod względem wartości:
int porownaj_liczby_normalnie(const int a, const int b)
{
if (a==b) return 0;
if (a>b) return 1;
return -1;
}
która zwróci 0 gdy obydwie liczby są równe pod względem wartości, 1 gdy pierwsza liczba
jest większa niż druga i -1 gdy druga jest większa niż pierwsza.
Spodziewanym wynikiem działania takiego fragmentu kodu:
cout << porownaj_liczby_normalnie(5,4);
jest wypisanie na standardowe wyjście wartości 1 (bo 5 jest większe od 4).
Problem porównywania liczb całkowitych jest prosty, ale należy się zastanowić – jak porównywać bardziej złożone elementy. Czy klasyfikując studentów układamy ich na liście
według średniej, według frekwencji, czy może stosując bardziej złożony system oceny?
Celem tego zadania jest napisanie dwóch funkcji:
•sortującej tablicę liczb całkowitych, korzystając z komparatora (dzięki czemu nie
trzeba pisać całej funkcji na nowo w przypadku zmiany kryterium oceny):
void sortuj(int (*komparator)(int,int), int* tab, const unsigned n);
przy czym algorytm sortowania jest dowolny;
•będącej “fabryką komparatorów”, tj. takiej funkcji, do której przekazujemy liczbę,
a ona zwraca wskaźnik na funkcję, będącą komparatorem:
int (*fabryka_komparatorow(const unsigned i))(const int a,const int b);
Zadanie powinno zostać zrealizowane następująco:
•należy do programu dołączyć podaną funkcję porownaj_liczby_normalnie (jest to
nasz pierwszy komparator);
•należy napisać trzy własne komparatory:
–wiecej_jedynek:“wygrywa” ta liczba, która w swojej dziesiętnej reprezentacji
ma więcej wystąpień cyfry 1. Jeżeli obydwie liczby mają tę samą liczbę jedynek
– uznajemy, że są sobie równe;
–wieksza_bezwzgledna: “wygrywa” ta liczba, której wartość bezwzględna jest
większa. W przypadku równej wartości bezwzględnej uznajemy, że obydwie
liczby są sobie równe;
–wieksza_pierwsza_cyfra: “wygrywa” ta liczba, której pierwsza cyfra (najbardziej znacząca) jest większa. Jeżeli obydwie pierwsze cyfry są takie same –
obowiązują kryteria z komparatora porownaj_liczby_normalnie.
•należy zaimplementować funkcję fabryka_komparatorow, która ma działać następująco:
–jeżeli i == 0, to zwracany jest wskaźnik na funkcję porownaj_liczby_normalnie;
–jeżeli i == 1, to zwracany jest wskaźnik na funkcję wiecej_jedynek;
–jeżeli i == 2, to zwracany jest wskaźnik na funkcję wieksza_bezwzgledna;
–jeżeli i == 3, to zwracany jest wskaźnik na funkcję wieksza_pierwsza_cyfra;
–w przeciwnym wypadku (gdy i > 3) zwracany jest wskaźnik na funkcję
porownaj_liczby_normalnie.
•należy zaimplementować funkcję sortuj, wykorzystującą wskaźnik na komparator,
tablicę (działamy na oryginale, bez tworzenia nowej) i rozmiar tablicy.
Poprawne wywołanie funkcji sortuj i fabryka_komparatorow będzie wyglądać następująco:
sortuj(fabryka_komparatorow(1), tab, n);
gdzie tab to sortowana tablica (jako wskaźnik na liczby całkowite) i n to rozmiar tablicy.
Takie wywołanie spowoduje posortowanie tablicy według komparatora wiecej_jedynek,
czyli na początku znajdzie się liczba z największą liczbą jedynek, a na końcu – liczba z
najmniejszą liczbą jedynek.
Zadanie 2.
Należy napisać program, którego zadaniem będzie liczenie porównań w warunkach pętli
zewnętrznej, wewnętrznej i “wewnętrznej wewnętrznej”, analogicznie jak przedstawiony
Państwu program na Podstawach Informatyki. Użytkownik powinien mieć możliwość wpisania fragmentu kodu do funkcji oblicz (nagłówek funkcji jest dowolny), następnie po
poprawkach i uruchomieniu programu powinien wprowadzić n, uzyskując informacje o
obliczeniach.
Do zrealizowania tego zadania będzie wymagana implementacja następujących funkcji:
•z - funkcja przyjmująca wartość logiczną i zwracająca wartość logiczną, liczącą
sprawdzanie warunku pętli zewnętrznej;
•w - funkcja przyjmująca wartość logiczną i zwracająca wartość logiczną, liczącą
sprawdzanie warunku pętli wewnętrznej;
•ww - funkcja przyjmująca wartość logiczną i zwracająca wartość logiczną, liczącą
sprawdzanie warunku pętli “wewnętrznej wewnętrznej”;
•wypisz_statystyki - funkcja nic nie zwracająca, wypisująca obliczone liczby porównań. Lista argumentów jest dowolna;
•oblicz - funkcja wymieniona powyżej, będzie wywoływana w funkcji main.
Przykładowo, dla następującego fragmentu kodu:
void oblicz()
{
int a=0;
for(unsigned i=0; z(i<n); ++i)
for(unsigned j=0; w(j<n); ++j)
for(unsigned k=0; ww(k<n); ++k)
a++;
}
int main()
{
//...
oblicz()
wypisz_statystyki();
//...
}
i podanej wartości n = 5 program powinien wypisać informacje o liczbie porównań, np:
Lz: 6, Lw: 30, Lww: 150
W pętli zewnętrznej (wykorzystującej zmienną i) wykonano 6 porównań, w pętli wewnętrznej (j) wykonano 30 porównań, a w zagnieżdżonej pętli wewnętrznej (k) wykonano
150 porównań.
Zadanie 3.
W bazach danych, z których korzystają programy wykorzystujące mechanizm logowania
użytkowników z hasłem (portale społecznościowe, komunikatory internetowe itp.) hasła
przechowywane są w postaci pewnej wartości, obliczonej na podstawie hasła. Przykładami funkcji zamieniającej hasło na postać są funkcje skrótu (zwane także funkcjami
mieszającymi lub funkcjami haszującymi ) md5 lub blake2, obliczającej wartość dla tekstu na podstawie jakiegoś algorytmu. Wartość ta jest przechowywana w bazie (hasła nie
są przechowywane jawnie – w przypadku wycieku bazy utrudnia to pozyskanie haseł).
Przy każdym logowaniu i wpisaniu tekstu w polu “Hasło” portal nie sprawdza haseł, tylko oblicza wartość dla hasła podanego przez użytkownika i porównuje z wartością z bazy
danych. Na potrzeby niniejszego zadania uznajemy, że w taki sposób przechowujemy hasła
– dzisiaj stosuje się bardziej rozbudowane algorytmy.
Na przykład w bazie istnieje użytkownik Jan, który ma hasło "abc". W bazie hasło
"abc" jest przedstawione jako "900150983cd24fb0d6963f7d28e17f72". Jan, chcąc się
zalogować, wpisuje w formularzu następujące wartości:
User: Jan
Hasło: abc
przy czym na serwer nie zostanie wysłana wartość "abc" do hasła, tylko zostanie wykonany, w uproszczeniu, następujący fragment kodu:
haslo_do_wysylki = md5(daj_haslo_z_pola_formularzu());
wyslij(daj_login_z_formularzu(),haslo_do_wysylki);
gdzie serwer porówna haslo_do_wysylki z hasłem z bazy danych.
Zadanie będzie polegać na siłowym (ang. brute force) złamaniu hasła, którego wartość
skrótu (wynik działania funkcji skrótu) dla podanego poniżej algorytmu to 1906821252.
Aby zrealizować to zadanie, należy:
•zaimplementować funkcję mieszającą zamien_na_liczbe, która przyjmuje argument typu char* lub string i zwraca wartość typu unsigned int.
Działa na podstawie pseudokodu:
Dane:
tekst (zmienna odpowiedniego typu przekazana jako argument)
Działanie:
i = 0;
wynik = 0; //bez znaku!
Dopóki i<długość(tekst) wykonuj:
tmp = wartość liczbowa z elementu tekst[i];
tmp2 = 0; //bez znaku!
Jeżeli (tmp<105) to:
tmp2 = 2 * i * tmp - 2;
W przeciwnym wypadku:
tmp2 = 76 * i * tmp - 58;
wynik = wynik + tmp2;
wynik = wynik*(tmp2-99);
Zwróć wynik;
Należy zwrócić uwagę, że zmienne wynik i tmp2 są typu bezznakowego (unsigned).
Specjalnie wykorzystywane są tutaj błędy, spowodowane przypisywaniem wartości
ujemnych do typów bezznakowych.
•zaimplementować funkcję, która generuje łańcuchy znaków (lub tablicę łańcuchów)
w taki sposób, że na początku znajduje się ciąg "a", potem "b", następnie "c", "d",
..., "y", "z", "aa", "ab", "ac", ..., "ay", "az", "ba",..., "zy", "zz", "aaa",...
•zaimplementować prosty mechanizm siłowego łamania hasła, tj. generowanie kolejnych ciągów znaków, obliczanie skrótów i sprawdzanie czy nie są równe 1906821252.
Jeżeli jakiś ciąg znaków pasuje do podanej wartości, to proszę wypisać go na ekran.
Zakodowane hasło to nazwa popularnego komputera w latach 80 i 90 XX wieku.
Zadanie 4.
Komunikacja ze stronami WWW zachodzi głównie za pomocą protokołu HTTP. W dużym
uproszczeniu — na dany serwer (serwer HTTP) jest wysyłany ciąg znaków, a serwer
w odpowiedzi generuje łańcuch, który jest następnie np. wyświetlany w formie strony
internetowej.
Na przykład, chcąc wyświetlić stronę główną http://icis.pcz.pl do serwera odpowia-
dającemu adresowi wysyłany jest następujący ciąg znaków (zwanym dalej żądaniem):
daj mi stronę: / z adresu icis.pcz.pl|
Serwer odpowiada:
200
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>IITiS</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2">
<link href="style2.css" rel="stylesheet" type="text/css">
</head>
<body>
...dalsza część strony
Pierwsza liczba w odpowiedzi — 200 — to kod odpowiedzi. Przykładowo, serwer może
wysłać odpowiedź 404 (kod błędu) w przypadku, gdy zasób określony w żądaniu (dana
podstrona) nie jest dostępny.
W tym zadaniu należy zaimplementować prosty symulator serwera uproszczonego protokołu HTTP. Symulator ten zostanie zaimplementowany w funkcji obsluz_zadanie, który
będzie przyjmować ciąg znaków i będzie zwracać ciąg znaków. W dalszej części omówienia
zadania będzie używana klasa string, ale może to też być zaimplementowane za pomocą char*. Klasa string będzie też zamieszczona w szkielecie programu (dołączonym do
zadania).
Żądanie naszego uproszczonego protokołu ma następującą budowę:
GET
<strona>
[<parametr_1>
<wartosc_parametru_1>
<parametr_2>
<wartosc_parametru_2>
...]
END
Zakładamy, że kolejne linie są oddzielone znakami nowego wiersza (’\n’), a parametry
nie zawierają tego znaku. Po ciągu END nie występuje znak nowej linii.
Wykorzystywany będzie projekt bardzo prostej strony internetowej, składającą się ze strony głównej (wtedy druga linia żądania ma wartość "/") i strony kontaktowej ("/kontakt").
Dla strony głównej wymagany jest parametr imie, którego wartość zostanie dołączona do
wyniku. W przypadku podstrony kontakt żaden parametr nie jest wymagany.
Następujące wywołanie funkcji obsluz_zadanie:
cout << obsluz_zadanie("GET\n/\nimie\nJan\nEND") << endl;
powinno spowodować wyświetlenie na ekranie:
200
Jan, witaj na stronie glownej
Pierwszym wierszem jest kod odpowiedzi (200 — wszystko działa), drugim - źródło strony, czyli imieniem (parametr imie z wartością "Jan") “doklejony” do stałego tekstu
"witaj na stronie glownej".
Dla żądania:
"GET\n/blablabla\nimie\nJan\nEND"
powinien zostać zwrócony łańcuch, zawierający tylko wartość 404 (strona nie została
odnaleziona).
Dla żądania:
"GET\n/kontakt\nimie\nJan\nEND"
powinien zostać zwrócony łańcuch
200
ul. Konstantego Mikolaja 5
Reasumując, funkcja obsluz_zadanie powinna z przekazanego argumentu (żądania) “wyciągnąć” drugą linię (zawiera ona nazwę strony), tj. ciąg znaków pomiędzy pierwszym a
drugim wystąpieniem znaku ’\n’. W zależności od nazwy strony należy następnie:
•dla nazwy strony "/kontakt" zwrócić stały łańcuch znaków (200\nul. Konstantego Mik
•dla nazwy strony "/" należy z żądania pobrać trzecią i czwartą linię. Trzecia linia
powinna mieć wartość "imie". Jeżeli tak jest, to czwarta linia jest dołączana na
początek tekstu ", witaj na stronie glownej". Jeżeli tak nie jest (inna wartość
trzeciej linii, brak czwartej linii itp.) powinien zostać zwrócony tylko ciąg znaków
"500" (oznacza ono błąd serwera);
•dla innej nazwy strony powinien zostać zwrócony ciąg znaków "404".
Na następnych dwóch stronach znajduje się szkielet programu (mający tylko ułatwić implementację, nie trzeba z niego korzystać).
#include<iostream>
using namespace std;
//...
bool czy_poprawne_zadanie(string zadanie) {
//...
}
bool czy_poprawna_strona(string zadanie) {
//...
}
string pobierz_strone(string zadanie) {
//...
}
string strona_glowna(string zadanie) {
string wynik = ", witaj na stronie glownej";
string imie = //...
return "200\n"+imie+wynik;
}
string kontakt(string zadanie) {
return "200\nul. Konstantego Mikolaja 5";
}
string bledna_strona(string zadanie) {
return "404";
}
string obsluz_zadanie(string zadanie) {
//sprawdz czy poprawny format zmiennej zadanie
if (!czy_poprawne_zadanie(zadanie))
return "400";
//sprawdz czy dobrze zostala podana strona
if (!czy_poprawna_strona(zadanie))
return bledna_strona(zadanie);
//jak tak, to pobierz nazwe strony
string strona = pobierz_strone(zadanie);
//strona glowna
if (strona=="/")
return strona_glowna(zadanie);
//strona kontakt
if (strona=="/kontakt")
return kontakt(zadanie);
//jak nic - to blad serwera
return "500";
}
//...
int main()
{
//...
cout << obsluz_zadanie("GET\n/\nimie\nJan\nEND") << endl;
cout << obsluz_zadanie("GET\n/blablabla\nimie\nJan\nEND") << endl;
cout << obsluz_zadanie("GET\n/kontakt\nimie\nJan\nEND") << endl;
//...
}
Do realizacji powyższego programu przydatne będzie zaimplementowanie kilku dodatkowych funkcji (nie jest to jednak wymagane), które realizują następujące zadania:
•znajdowanie liczby linii w łańcuchu znaków;
•dzielenie łańcucha na tablicę łańcuchów (każda komórka to osobna linia pierwotnego
łańcucha);
•pobieranie parametrów z łańcucha do dwuwymiarowej tablicy napisów. Każdy wiersz
ma dwie kolumny: nazwę parametru i jego wartość.