Witajcie 馃檪 Mam nadziej臋, 偶e przygotowujecie si臋 do matury, drodzy czytelnicy? Cofamy si臋 kolejny roczek do ty艂u. Bierzemy na warsztat zadanie „Liczba PI” znajduj膮ce si臋 na arkuszu maturalnym w roku 2016. Mamy do rozwi膮zania jedynie trzy podpunkty. Czy p贸jdzie g艂adko? Zobaczymy. No to zaczynajmy 馃檪

Je艣li chcesz przeczyta膰 om贸wienie rozwi膮za艅 innych zada艅 maturalnych z informatyki, zapraszam do odwiedzin specjalnie przygotowanej do tego celu strony. Agreguje ona nie tylko wszystkie om贸wione do tej pory tre艣ci, ale daje 艂atwy dost臋p do arkuszy maturalnych z informatyki oraz ich rozwi膮za艅.

Dobra. Wystarczy ju偶 gl臋dzenia. Bierzmy si臋 za robot臋 i rozwalmy kolejne zadanko z matury 馃檪

Zadanie 4. Liczba PI

W kartezja艅skim uk艂adzie wsp贸艂rz臋dnych na p艂aszczy藕nie narysowano kwadrat o boku d艂ugo艣ci 400 i 艣rodku symetrii w punkcie (200;200). Boki kwadratu s膮 r贸wnoleg艂e do osi uk艂adu wsp贸艂rz臋dnych. W kwadrat wpisano ko艂o. Nast臋pnie wylosowano 10 000 punkt贸w nale偶膮cych do kwadratu. Wsp贸艂rz臋dne (x,y) punkt贸w zosta艂y zapisane w pliku punkty.txt Ka偶dy punkt w osobnym wierszu. Wiersz ma posta膰 dw贸ch liczb ca艂kowitych z zakresu <0;400>, rozdzielonych pojedynczym znakiem odst臋pu.

Korzystaj膮c z powy偶szych danych oraz dost臋pnych narz臋dzi informatycznych, wykonaj zadania. Wyniki zapisz w pliku tekstowym wyniki_4.txt. Odpowied藕 do ka偶dego zadania poprzed藕 numerem zadania.

Kod uniwersalny dla wszystkich podpunkt贸w

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const int MIDDLEX = 200;
const int MIDDLEY = 200;
const int R = 200;
struct Point {
    int x;
    int y;
};

vector<Point> loadPointsFromFile(string filename) {
    vector<Point> data;
    ifstream file;
    file.open(filename);
    if(file.is_open()==false) {
        cout<<"Nie uda艂o si臋 otworzy膰 pliku "<<filename<<endl;
        return data;
    }
    for(int i = 0;i<10000;i++) {
        struct Point pp;
        file>>pp.x;
        file>>pp.y;
        data.push_back(pp);
    }
    return data;
}

Na samym pocz膮tku tworzymy sobie trzy sta艂e. Okre艣laj膮 nam one 艣rodek okr臋gu oraz jego promie艅. Nast臋pnie napisali艣my sobie strukturk臋, w kt贸rej b臋dziemy przechowywali informacje na temat pojedynczego punktu.

Funkcja odczytuj膮ca plik wygl膮da standardowo, podobnie jak w om贸wieniu zadania z 2017 i 2018 roku. Na pocz膮tku tworzymy vector, w kt贸rym b臋dziemy przechowywali odczytane dane. Otwieramy plik. Sprawdzamy, czy ta operacja sie uda艂a. Je艣li si臋 uda艂a, to odczytujemy 10000 wsp贸艂rz臋dnych z pliku. Korzystamy ze strumieni, gdy偶 w tym wypadku to one s膮 najwygodniejsze. Na samym ko艅cu funkcji zwracamy pobrane punkty.

Zadanie 4.1. (0-3)

Wypisz wsp贸艂rz臋dne tych punkt贸w, kt贸re nale偶膮 do brzegu ko艂a (okr臋gu), oraz podaj liczb臋 punkt贸w nale偶膮cych do wn臋trza ko艂a (brzeg ko艂a nie nale偶y do wn臋trza ko艂a).

Wskaz贸wka:

R贸wnanie okr臋gu o 艣rodku w punkcie S = (a,b) i promieniu r > 0 ma posta膰:

(x-a)^2+(y-b)^2=r^2

Informacja

W pliku w艣r贸d 100 pierwszych punkt贸w 80 nale偶y do wn臋trza ko艂a.

Rozwi膮zanie

34
35
36
37
38
39
40
41
bool isOnTheCircumference(Point p) {
    if((pow(p.x-MIDDLEX,2)+pow(p.y-MIDDLEY,2))==(R*R)) return true;
    return false;
}
bool isInTheCircle(Point p) {
    if((pow(p.x-MIDDLEX,2)+pow(p.y-MIDDLEY,2))<(R*R)) return true;
    return false;
}

Jak na najprostsze zadanie przysta艂o, nic szczeg贸lnego. CKE poda艂o nam na tacy wszystkie wzory. Wystarczy tylko odpowiednio z nich skorzysta膰.

Co zrobili艣my w powy偶szym listingu? Utworzyli艣my dwie funkcje pomocnicze. Pierwsza sprawdza, czy punkt znajduje si臋 na kraw臋dzi ko艂a. Korzystamy tam z podanego w tre艣ci polecenia wzoru. Sprawdzamy, czy jest on prawdziwy. Je艣li tak, oznacza to 偶e punkt le偶y na kraw臋dzi ko艂a. Zwracamy wtedy true.

Druga funkcja jest stworzona analogicznie. Tym razem jednak warunek jest inny. Sprawdzamy, czy lewa cz臋艣膰 wzoru jest mniejsza od prawej. Dlaczego? Wynika to po prostu z matematyki. Je艣li punkt le偶y w 艣rodku ko艂a oznacza to, 偶e musi le偶e膰 w odleg艂o艣ci mniejszej od 艣rodka ni偶 promie艅 tego ko艂a. W艂a艣nie dlatego zastosowali艣my w funkcji znak mniejszo艣ci.

42
43
44
45
46
47
48
49
50
51
52
int main()
{
    vector<Point> data = loadPointsFromFile("punkty.txt");
    int inTheCircleCount = 0;
    for(Point pt : data) {
        if(isInTheCircle(pt)==true) inTheCircleCount++;
        if(isOnTheCircumference(pt)) cout<<"("<<pt.x<<","<<pt.y<<"),";
    }
    cout<<inTheCircleCount;
    return 0;
}

Wyj膮tkowo przeanalizujemy funkcj臋 main. Co tutaj si臋 znajduje? Na samym pocz膮tku oczywi艣cie wczytujemy dane z pliku. Tworzymy zmienn膮 pomocnicz膮 w kt贸rej b臋dziemy zliczali ilo艣膰 punkt贸w l臋偶膮cych wewn膮trz ko艂a. Nast臋pna cz臋艣膰 kodu to range-based for. Rozwi膮zujemy jednocze艣nie obydwa podpunkty zadania. Sprawdzamy, czy punkt znajduje si臋 wewn膮trz ko艂a. Je艣li tak – zwi臋kszamy warto艣膰 zmiennej inTheCircleCount o 1. Podobnie weryfikujemy, czy punkt nale偶y do kraw臋dzi ko艂a. Je艣li tak, wypisujemy jego wsp贸艂rz臋dne. (Oczywi艣cie, nie zapominamy o przepisaniu ich r臋cznie do pliku wyniki_4.txt, z odpowiedni膮 adnotacj膮 na temat zadania)

Kompletne rozwi膮zanie zadanie 4.1

Zadanie 4.2 (0-3)

Przy za艂o偶eniu r贸wnomiernego rozk艂adu punkt贸w w kwadracie, stosunek liczby punkt贸w nale偶膮cych do ko艂a do liczby punkt贸w nale偶膮cych do kwadratu w przybli偶eniu r贸wny jest stosunkowi pola ko艂a Pk do pola kwadratu P.

Wyznacz przybli偶on膮 warto艣膰 liczby pi, bior膮c pod uwag臋 punkty z pliku punkty.txt:

  • pierwszych 1000 punkt贸w
  • pierwszych 5000 punkt贸w
  • wszystkie punkty

Wynik zaokr膮glij do 4 miejsc po przecinku.

Wz贸r:

    \[\frac{n_{k}}{n}\approx \frac{P_{k}}{P}\]

Rozwi膮zanie:

No c贸偶. Matematyka jest kr贸low膮 nauk. Nie rozwi膮偶emy tego zadania, je艣li nie b臋dziemy potrafili przekszta艂ci膰 powy偶szego wzoru. Pami臋tasz jak liczy si臋 pole ko艂a, prawda? Ta wiedza w tym momencie naprawd臋 si臋 przyda. Je艣li za 偶adne skarby 艣wiata nie mo偶esz sobie przypomnie膰 takiego wzoru, sp贸jrz poni偶ej 馃檪

    \[P_{k}= \pi*r^2\]

Przeprowad藕my przekszta艂cenie wzor贸w. Musimy wyznaczy膰 warto艣膰 liczby PI.

    \[\frac{n_{k}}{n}\approx \frac{\pi * r^2}{P}\]


    \[n* \pi * r^2 \approx n_k * P\]


    \[\pi=\frac{n_k * P}{n * r^2}\]

Napiszmy teraz program, kt贸ry skorzysta z wyliczonego powy偶ej wzoru.

36
37
38
39
bool isBelongToTheCircle(Point p) {
    if((pow(p.x-MIDDLEX,2)+pow(p.y-MIDDLEY,2))<=(R*R)) return true;
    return false;
}

Ta funkcja powinna wyda膰 ci si臋 znajoma… TAK. To prawie ta sama, kt贸r膮 napisali艣my w odpowiedzi na poprzedni podpunkt zadania. Czym si臋 r贸偶ni? Wcze艣niej musieli艣my oddzielnie wyznaczy膰 punkty le偶膮ce na kraw臋dzi ko艂a oraz nale偶膮ce do wn臋trza ko艂a. Przy wyliczaniu warto艣ci PI uznajemy, 偶e do ko艂a nale偶膮 zar贸wno te punkty kt贸re znajduj膮 si臋 na jego obrysie jak i te, kt贸re po艂o偶one s膮 wewn膮trz. Dlatego instrukcja warunkowa zmieni艂a si臋.

40
41
42
43
44
45
46
47
48
double calculateRatio(int circlePoints,int numberOfPoints) {
    const double circleArea = M_PI*(R*R);
    const double squareArea = (2*MIDDLEX)*(2*MIDDLEY); // lub po prostu 400*400

    double numerator = circlePoints*squareArea;
    double denominator = numberOfPoints*R*R;
    double result = numerator/denominator;
    return result;
}

Funkcja calculateRatio wylicza liczb臋 pi wg wzoru, kt贸ry ustalili艣my na pocz膮tku. Jako argument przyjmujemy ilo艣膰 punkt贸w, znajduj膮cych si臋 wewn膮trz ko艂a, a tak偶e ilo艣膰 punkt贸w dla kt贸rych chcemy przeprowadzi膰 wyliczenia. Najpierw tworzymy sobie dwie sta艂e. Pierwsza z nich zawiera pole ko艂a. (W tym miejscu z niego skorzystali艣my metod膮 kopiuj-wklej). Druga sta艂a to pole kwadratu, kt贸ry jest opisany na tym kole.

Wyliczanie u艂amka rozbili艣my sobie na dwa kroki. Najpierw obliczamy licznik (zmienna numerator), a nast臋pnie mianownik. Ponownie stosujemy metod臋 copy-paste. Podstawiamy nasze wyliczone wcze艣niej zmienne pod wyznaczony wz贸r.

Zmienna result zawiera odpowied藕 do zadania (dla danej liczby punkt贸w)

49
50
51
52
53
54
55
56
double calculatePIAccuracy(vector<Point>& data,int numberOfPoints) {
    int inTheCircleCount = 0;
    for(int i = 0;i<numberOfPoints;i++) {
        if(isBelongToTheCircle(data[i])==true) inTheCircleCount++;
    }
    double result = calculateRatio(inTheCircleCount,numberOfPoints);
    return result;
}

Funkcja calculatePIAccuracy s艂u偶y do ustalenia ostatecznego wyniku. Jako argumenty przyjmujemy vector z punktami oraz liczb臋 punkt贸w, kt贸re chcemy wzi膮膰 pod uwag臋 przy wyliczaniu liczby PI.

Liczb臋 punkt贸w znajduj膮cych si臋 wewn膮trz ko艂a obliczamy podobnie, jak w poprzednim zadaniu. Zmienna pomocnicza inTheCircleCount, p臋tla for (dlaczego nie range-based? Poniewa偶 zadanie wymaga od nas przej艣cia okre艣lonej cz臋艣ci tablicy) i inkrementacja gdy warunek jest spe艂niony. Nast臋pnie korzystamy z funkcji calculateRatio, kt贸ra odwala ca艂膮 brudn膮 robot臋.

56
57
58
59
60
61
62
63
64
int main()
{
    vector<Point> data = loadPointsFromFile("punkty.txt");
    cout<<std::fixed;
    cout<<std::setprecision(4)<<calculatePIAccuracy(data,1000)<<endl;
    cout<<std::setprecision(4)<<calculatePIAccuracy(data,5000)<<endl;
    cout<<std::setprecision(4)<<calculatePIAccuracy(data,10000)<<endl;
    return 0;
}

Sp贸jrzmy na funkcj臋 main().

Mamy wy艣wietli膰 wynik z dok艂adno艣ci膮 do czterech miejsc po przecinku. S艂u偶y do tego funkcja setPrecision. Jako argument przyjmuje ilo艣膰 miejsc po przecinku, kt贸re chcemy wy艣wietli膰. U偶ywamy na pocz膮tku fixed aby zmusi膰 strumie艅 do trzymania si臋 naszych zalece艅.

Kompletne rozwi膮zanie - zadanie 4.2

Zadanie 4.3 (0-5)

B艂膮d bezwzgl臋dny przybli偶onej warto艣ci liczby pi, wyznaczonej z n punkt贸w, definiujemy nast臋puj膮co:

    \[\epsilon_n = | \pi - pi_n |\]


Pierwsza zmienna to warto艣膰 liczby pi b臋d膮ca wynikiem standardowej funkcji z narz臋dzia informatycznego, z kt贸rego korzystasz. Druga natomiast – przybli偶ona warto艣膰 liczby pi wyznaczona z n kolejnych punkt贸w, poczynaj膮c od pierwszego punktu z pliku punkty.txt

Oblicz b艂膮d bezwzgl臋dny dla n=1,2,3… 1700. Na podstawie powy偶szego zestawienia utw贸rz wykres liniowy ilustruj膮cy zmiany dok艂adno艣ci wyznaczanej liczby pi. Zadbaj o czytelno艣膰 wykresu. Warto艣ci dla n=1000 i n=1700 (zaokr膮glone do czterech liczb do przecinku) zapisz do pliku wyniki_4.txt

Rozwi膮zanie:

Mamy utworzy膰 wykres liniowy … Powinno ci w g艂owie za艣wita膰, 偶e sam C++ do tego nie wystarczy … Ale po kolei.

Najpierw wyznaczmy b艂膮d bezwzgl臋dny dla n=1,2,3 … 1700.

56
57
58
double calculateAbsoluteError(double pi) {
    return fabs(M_PI-pi);
}

Obliczenie b艂臋du bezwzgl臋dnego to po prostu podstawienie do wzoru. Nic szczeg贸lnego. Zmienna pi zawiera warto艣膰 wyliczon膮 przez nasz program. Natomiast M_PI to sta艂a z biblioteki cmath, zawieraj膮ca dok艂adn膮 warto艣膰 liczby PI. U偶ywamy funkcji fabs, gdy偶 operujemy na liczbach zmiennoprzecinkowych.

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void writeAbsoluteErrorsToFile(string filename,string filename2,vector<Point> &data) {
    ofstream file;
    ofstream file2;
    file.open(filename);
    file2.open(filename2);
    if(file.is_open()==false || file2.is_open()==false) {
        cout<<"Nie uda艂o si臋 otworzy膰 pliku"<<endl;
        return;
    }
    file<<std::fixed<<std::setprecision(4);
    file2<<std::fixed<<std::setprecision(4);
    for(int i = 1;i<=1700;i++) {
        file<<calculateAbsoluteError(calculatePIAccuracy(data,i))<<endl;
        if(i==1000 || i==1700) file2<<calculateAbsoluteError(calculatePIAccuracy(data,i))<<endl;
    }
}

Funkcja przedstawiona powy偶ej zapisuje wymagane przez polecenie informacje do pliku. Mo偶esz si臋 zastanowi膰, po co nam dwa pliki? W jednym z nich (filename) zapisywane s膮 wszystkie wygenerowane dok艂adno艣ci liczby pi, dla n z zakresu <1,1700>. Jego zawarto艣膰 b臋dzie nam potrzebna w nast臋pnym kroku do wygenerowania wykresu. Drugi plik to spe艂nienie 偶膮da艅 egzaminator贸w z CKE. Zapisujemy w nim En1000 i En1700 z dok艂adno艣ci膮 do 4 miejsc po przecinku. (Pami臋tajmy o przekopiowaniu tre艣ci tego pliku do wyniki_4.txt, z odpowiedni膮 adnotacj膮 :))

Do wyliczenia b艂臋du bezwzgl臋dnego u偶ywamy utworzonej przed chwil膮 funkcji calculateAbsoluteError. Jako argument przekazujemy funkcj臋 calculatePIAccuracy, kt贸ra zwraca wyznaczon膮 warto艣膰 liczby pi dla okre艣lonej liczby punkt贸w.

Rozwi膮zanie dzia艂a prawid艂owo i nadaje si臋 na matur臋. Jako zadanie domowe pozostawiam ci optymalizacj臋. Przypomnij sobie jak dzia艂a funkcja calculatePIAccuracy. Przeanalizuj, jak du偶o nadmiarowych oblicze艅 wykonujemy, u偶ywaj膮c jej w ten spos贸b. Zastan贸w si臋, jak mo偶na j膮 poprawi膰 aby nie marnowa膰 tyle mocy obliczeniowej.

Tworzenie wykresu

Po skopiowaniu liczb z wygenerowanego pliku i wklejeniu ich do LibreOffice Calc uka偶e nam si臋 nast臋puj膮ce okienko:

Musimy wskaza膰, w jaki spos贸b program ma obs艂u偶y膰 wklejone liczby. Nic nie musimy zmienia膰. (Excel r贸wnie偶 nie powinien mie膰 problem贸w z interpretacj膮).

Zauwa偶ymy natomiast jeden problem. Arkusz kalkulacyjny interpretuje wklejone liczby jako tekst! Dzieje si臋 tak, gdy偶 C++ przy zapisie do pliku u偶y艂 kropki do oddzielenia cz臋艣ci u艂amkowej. Natomiast programy kalkulacyjne wol膮 przecinek. Mo偶emy prosto naprawi膰 t臋 niedogodno艣膰. Klikamy Edycja->Znajd藕 i zamie艅. W okienku jako znajd藕 wpisujemy kropeczk臋, a jako zamie艅 przecinek. Klikamy zamie艅 wszystkie.

Nast臋pnym krokiem jest wygenerowanie wykresu.

Najbardziej dostosowany do naszych potrzeb b臋dzie wykres liniowy.

Na ekranie: Zakres danych nie ustawiamy nic.

Kolejny etap to dodanie serii danych. Klikamy przycisk dodaj. Nast臋pnie ustawiamy zakres warto艣ci Y. Klikamy przycisk z symbolem arkusza i plusa po prawej stronie pola edycyjnego. Wybieramy kom贸rki, w kt贸re wkleili艣my liczby (w moim wypadku to kom贸rki od A1 do A1700). Klikamy Utw贸rz.

Voila: Wykres gotowy 馃檪 Mo偶emy pobawi膰 si臋 w popraw臋 jego wygl膮du i dodanie opis贸w, legend. Taki wynik naszej pracy powinien zosta膰 bezproblemowo zaakceptowany przez CKE.

Gratulacje 馃檪

Czytaj膮c ten wpis, wykona艂e艣 kolejny krok w procesie przygotowania si臋 do matury z informatyki. Niebawem uka偶膮 si臋 kolejne edukacyjne tre艣ci. B臋dziemy rozwi膮zywali zadania z programowania z ubieg艂ych lat. Jak om贸wimy wszystkie matury z informatyki pod wzgl臋dem programowania, zajmiemy si臋 kolejnymi rodzajami zada艅.

Je艣li masz jakie艣 uwagi, zauwa偶y艂e艣 jaki艣 b艂膮d, pisz 艣mia艂o 馃檪 Jestem otwarty na konstruktywne opinie i komentarze.

Tym czasem – do zobaczenia 馃檪 Zach臋cam do lektury pozosta艂ych wpis贸w. Spis matur oraz om贸wionych do tej pory zada艅 znajduje si臋 tutaj.

Dodaj komentarz

Tw贸j adres email nie zostanie opublikowany. Pola, kt贸rych wype艂nienie jest wymagane, s膮 oznaczone symbolem *