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 tegosamegotypuizwracawartośćtypuint.funkcjatadziaławtensposó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)return1; return-1; która zwróci 0 gdy obydwie liczby są równe pod względem wartości, 1 gdy pierwsza liczba jestwiększaniżdrugai-1gdydrugajestwiększaniż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,żesąsobieró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, czylinapoczątkuznajdziesięliczbaznajwiększąliczbąjedynek,anakońcu liczbaz 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++; intmain() { 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;//bezznaku! Dopóki i<długość(tekst) wykonuj: tmp = wartość liczbowa z elementu tekst[i]; tmp2=0;//bezznaku! 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) wtakisposób,żenapoczątkuznajdujesię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"; stringimie= 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);
//jaknic-tobladserwera return"500"; intmain() { 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ść.