O informatyce, po swojemu, inaczej

[C++]Liczba PI – ulepszamy kod

Cześć, jak niedawno wspomniałem ten kod w zasadzie nie jest zły, bo spełnia warunki, których oczekuje od nas zadania, wszakże jednak mamy dosyć słaby kod, ponieważ działa on ściśle tylko z treścią zadania. Zróbmy dzisiaj wspólnie razem lepszy kod; kod, który zapewni nam trochę uniwersalność programu, dzięki czemu w przypadku mniejszej ilości punktów zmniejszymy zużycie pamięci, a w przypadku większej nie zgubimy ani jednego punktu. Dla niewtajemniczonych odsyłam do wpisu tutaj.

Zanim jeszcze podejmiemy się pisania zróbmy kilka założeń, w których kod będzie nadal działać:

  • w pliku źródłowym dla punkty.txt zachowane są stare reguły wpisywania punktów
  • nikt nie będzie fundować nam, no powiedzmy z po za kwadratu ( chodzi mi głównie o to, by nikt nie wymuszał na nas generowanie zmiennych dłuższych niż int w przypadku współrzędnych

Także teraz zabieramy się do zmian w kodzie.

Analizując pobieżnie kod już widzimy, że w zasadzie w jednej funkcji zaczynamy pracować na pliku, a dopiero w głównej po wszystkim kończymy z nimi pracę. Da się to uprościć. Fakt, przy tak małym programie nie odczujemy różnicy jednakże dobre nawyki należy kultywować już od samego początku. Oszczędzając zmienne mamy więcej możliwości do tworzenia kolejnych, a także mniejsze ryzyko pogubienia się z nimi. Dlatego zaczniemy od funkcji void import().

void import()
{
    plik.open( "punkty.txt", ios::in );
    for( int i = 0; i <10000; i++)
    {
        plik >> punktyx[i];
        plik >> punktyy[i];
    }

}

Tutaj zaraz po przepisaniu wszystkich tych punktów do tablic możemy w zasadzie podziękować za współpracę plikowi źródłowemu punkty.txt. Więc zwolnijmy go poprzez usunięcie i wpisanie linijki plik.close() do naszej powyższej funkcji. Dodatkowo musimy nanieść poprawki związane z dynamicznym tworzeniem tablicy. Musimy utworzyć jedną dodatkową funkcję, która nam wyliczy rozmiar tablicy.
Właśnie  w tym momencie zwolniliśmy zmienną fstream plik tuż przed tworzeniem pliku odpowiedz.txt, co daje nam możliwość posłużenia się tą samą zmienną w przypadku tworzenia pliku z odpowiedziami w funkcji void okrag_czy_kolo()
Daruję sobie przepisywanie całej funkcji, aby nie zaśmiecać wpisu za specjalnie po prostu musimy podmienić zmienną w odpowiednich linijkach kodu.

odp.open(„odpowiedz.txt”, ios::appp ) zmieniamy na plik.open („odpowiedz.txt”, ios::appp),
a odp.close() na plik.close()

dodatkowym manewrem dobrym będzie przepisanie funkcji zamykającej nasz plik z int main() do funkcji  void okrag_czy_kolo ()

Pozbyliśmy się właśnie jednej zmiennej fstream. Wiem, że w tym momencie nie czuć za bardzo tej wygody ale przy większych projektach, kiedy będziesz mieć okazję pracować lub po prostu uczestniczyć czynnie w ich tworzeniu zobaczysz jak ważnym elementem jest regulacja i kontrolowanie zasobów zużywanych przez maszynę.

Skoro już o tych zasobach pamięci mowa pora popracować nad naszymi tablicami, które niezależnie od wszystkiego przechowują 10000 slotów na współrzędne. Pewnie już zauważyłeś jak łatwo ograć nasz system importu danych ale o tym za chwilę. Teraz musimy pomyśleć nad tym jak sprawnie usunąć i dostosować wielkość tablic do stawianego przed nimi zadania, o którym już uprzednio rozmawialiśmy.

Teoretycznie można założyć, że możemy zadeklarować zmienną, która będzie widniała jako liczba, która określi wielkość tablicy, czyli coś takiego:

cin >> n 
int tab[n];

Jednak niestety jest to bardzo niewłaściwe i niezgodne ze standardami C++. Dlatego będziemy się posługiwać operatorami new i delete, aby od razu pozbyć się tablicy, która nas może uwierać.Oto prosty kod na utworzenie tablicy o nieznanym rozmiarze przed kompilacją.

cin >> n;
int * tab = new int[ n ];
// Do usunięcia tablicy stosujemy w tym wypadku
delete [ ] tab;

Problem tworzenia dynamicznych tablic w zasadzie jest już rozwiązany. Jednak skąd pobrać wartość naszej tutaj napisanej [ n ]? W zasadzie to, że musimy policzyć wiersze jest po prostu oczywiste. Niestety w bibliotece fstream nie znajdziemy funkcji, która dokładanie da nam rozwiązanie. Musimy sami ją napisać.

Najwygodniej będzie zbudować liczydło, tak jak to zwykle budujemy:

pętla + warunek + zmienna do sumowania = liczydło

W tym wypadku musimy wykazać się sprytem. Z czasem zauważysz, że C++ ma w swoich bibliotekach dużo bazowych funkcji, które to będą naszą bazą wypadową po coś lepszego.
Naszym sposobem na policzenie ile wierszy ma nasz plik jest następujący

  • dopóki nie jest to koniec pliku to nabij nam licznik o 1 więcej
  • pobierz tą linię i przejdź do następnej

Już po treści widzimy, że nie będzie to nic skomplikowanego. Kwestia teraz zapisania tego w odpowiedniej konstrukcji. Tutaj będę potrzebne nam dwie biblioteki – fstream oraz string. Fstream wykorzystujemy rzecz jasna do pracy na pliku, natomiast przyda nam się string, ponieważ zmienne typu string są znacznie wygodniejsze do przechowywania całych wierszy ( przynajmniej w tym wypadku ). Kiedy już się wkręcisz w pisanie własnych algorytmów zauważysz, że wygodniejsza jest praca ze stringami, niż z długimi i nieporęcznymi tablicami typu char.

Kod funkcji sprawdzającej ile wierszy ma nasz plik przedstawia się następująco:

fstream plik;
int ilosc;
string linia;

(...)

void ile_wierszy()
{
    plik.open( "punkty.txt", ios::in );  //otwieramy plik w trybie tylko do odczytu
    while(!plik.eof())  // dopóki, dopóty nasz kursor nie wskazuje na koniec pliku ( end of file, !*.eof )
    {
        getline(plik, linia);  // wczytaj wiersz do naszej zmiennej linia
        ilosc++;  // "przekręć nasz licznik o 1", zwiększ zmienną ilosc o 1
    }
    cout<< ilosc << "\n";  // napisz ile tego sie nazbieralo

    plik.close(); // zamknij plik, gdyż skończyliśmy z nim pracę
}

Dobrze. Teraz kiedy mamy już elastyczną tablicę i rozwiązany problem importu danych wypada to wszystko połączyć ze sobą. Tutaj w zasadzie są aż trzy możliwe drogi – stara droga z C, czyli przekazywanie przez wartość, poprzez wskaźniki i referencję.

Cały kod opracowany na wskaźnikach wraz z wyjaśnieniem w komentarzach:

#include <cstdlib>
#include <iostream>
#include <string>
#include <fstream>
#include <math.h>

using namespace std;

fstream plik; // zmienna do pracy nad plikami

long int wk; // nasz licznik do liczenia punktow wewnatrz, przeznaczona na duza liczbe punktow 400x400 kombinacji sensownych

// Deklarujemy funkcje
void import(int *a,int *punktyx,int *punktyy );
void okrag_czy_kolo(int *a, int *punktyx,int *punktyy );
int tablice( int *a);

int main()
{
    int n; // zmienna do okreslenia wielkosci tablicy

    tablice( &n); // sprawdzamy rozmiar tablic jakie będą nam potrzebne
    //Tworzenie tablic dynamicznie
    int *punktyx = new int[n];
    int *punktyy = new int[n];

    import(&n, punktyx, punktyy); // Importujemy dane z pliku
    okrag_czy_kolo( &n, punktyx, punktyy ); // Sprawdzamy, czy punkty naleza do okregu, czy do kola, a moze ni tu, ni tu?

    cout<< " Punktow wewnatrz kola: " << wk << endl << "Punkty nalezace do okregu mozesz sprawdzic w pliku odpowiedz.txt " << endl; // interaktywna zwrotka dla uzytkownika

    // Sprzątamy po sobie
    delete [] punktyx;
    delete [] punktyy;

    system("PAUSE");
    return 0;
}
int tablice( int *a)
{
    plik.open( "punkty.txt", ios::in ); // Zaczynamy pracę z plikiem z punktami w trybie 'tylko do odczytu'
    if( plik.good() == true ) // Sprawdzamy, czy udalo sie otworzyć plik na naszych warunkach
    {
    int b=0; // pomocnicza zmienna, nasz licznik do okreslenia rozmiaru tablic
    string linia; // zmienna 'śmietnik' - przechowujemy tu pobrane linie

    // pętla zliczająca ile wierszy ma plik
        while(!plik.eof()) // pętla sprawdza, czy nasz kursor nie stoi na końcu pliku
        {
        getline(plik, linia); // jeżeli nie, to pobierz linię do naszej zmiennej linia
        b++; // zwiększ zmienną b o 1, przekreć nasz licznik o 1 w górę
        }
        plik.close(); // zakończ pracę z plikiem

        return b; // zwracamy rozmiar tablicy, aby poinformować inne funkcje jaki rozmiar ma być tablic oraz jak długo mają działać pętle
    } else cout << "Dostep do pliku zostal zabroniony!" << endl; // interaktywna zwrotka dla użytkownika, gdy nie uda nam się uzyskać dostępu do pliku
}
void import(int *a,int *punktyx,int *punktyy )
{
    plik.open( "punkty.txt", ios::in ); // Zaczynamy pracę z plikiem z punktami w trybie 'tylko do odczytu'
    int pom= *a; // pomocnicza zmienna do pracy z pętlą
    for( int i = 0; i < pom; i++)
    {
        plik >> punktyx[i]; // importujemy współrzędne X
        plik >> punktyy[i]; // importujemy współrzędne Y
    }
plik.close(); // kończymy pracę z plikiem
}
void okrag_czy_kolo(int *a,int *punktyx,int *punktyy )
{
    plik.open( "odpowiedz.txt", ios::app ); // otwieramy plik w trybie edycji na początku pliku, jeżeli nie ma takiego zostanie on utworzony w miejscu,
    //gdzie znajduje się plik exe
    int pom = *a; // pomocnicza zmienna do pracy z pętlą
    long int wynik; // duza zmienna do pracy na iloczynach współrzędnych punktów
    // pętla weryfikująca punkty
    for(int i = 0; i< pom; i++)
    {
        wynik= punktyx[i]*punktyx[i] - 400*punktyx[i] + punktyy[i]*punktyy[i] - 400*punktyy[i] + 80000; // wzór na promień okręgu z zadania, lewa strona
        if(wynik<40000) // porównujemy z prawą stroną wzoru, czyli r^2, w naszym przypadku to '40 000'
        {
            wk=++wk; // punkt jest równo odległy lub mniej od środka koła, więc należy do koła i okręgu
        }
        else if(wynik==40000)
        {
        plik << punktyx[i] << " " << punktyy[i] << endl; // punkt jest równy kwadratowi promienia, więc należy do okręgu, wpisujemy go do pliku odpowiedz.txt

        }
    }
    plik.close(); // kończymy pracę z plikiem
}

Oczywiście w razie pytań, uwag, czy wątpliwości serdecznie zapraszam do komentowania. Kod źródłowy wrzucam tutaj.