Narysuj Ślad

Oryginalna treść zadania jest dostępna tutaj

Treść

Narysuj linie podążając za prostymi wskazówkami. Na wejściu otrzymujemy instrukcje rozdzielone znakiem spacji. Każda instrukcja składa się z litery (U, D, L lub R co oznacza kolejno w górę, w dół, w lewo, w prawo) oraz liczby.

Zadanie polega na narysowaniu linii podążając za wskazówkami. Rysowanie zaczyna się od lewego-górnego rogu. Nieodwiedzone pola należy przedstawić podkreślnikiem \'_\', a odwiedzone \'0\'. Pola mogą być odwiedzona dwukrotnie. Przykładowo dla:

  1. R9 D6 L9 U4 R7 D2 L5

wynikiem powinno być:

  1. 0000000000
  2. _________0
  3. 00000000_0
  4. 0______0_0
  5. 0_000000_0
  6. 0________0
  7. 0000000000

Implementacja

Spostrzeżenia

Tablica powinna być dynamiczna, aby uniknąć podwójnego przeglądania ciągu instrukcji. Potrzebujemy wyodrębnić instrukcje z pobranej wejściowej linijki tekstu, ponieważ nie mamy podane ile instrukcji łącznie będzie.

Rozwiązanie

W celu uzyskania listy instrukcji potrzebujemy funkcję, która po otrzymaniu ciągu znaków rozdzieli nam go na kolejne elementy listy. Funkcja podziel() rozdziela elementy wczytane z konsoli.

1
2
3
4
5
6
7
8
vector<string>& podziel(const string &s, char d, vector<string> &elems) {
    stringstream ss(s);
    string i;
    while (getline(ss, i, d)) {
        elems.push_back(i);
    }
    return elems;
}

Funkcja nadrzędna przyjmuje jeden argument mniej i odpowiada za przygotowanie wektora do konwersji danych.

1
2
3
4
5
vector<string> podziel(const string &s, char d) {
    vector<string> l;
    podziel(s, d, l);
    return l;
}

Potrzebujemy również funkcji, która pozwoli nam skonwertować ciąg znaków string na zmienną int. W tym celu również posłużymy się zmienną stringstream:

1
2
3
4
5
6
int naInt(string str) {
    int i;
    istringstream iss(str);
    iss >> i;
    return i;
}

Ostatnia funkcja pomocnicza będzie dotyczyć bezpośrednio tablicy w której będziemy zapisywać dane. W C++ wyjście poza granice wyznaczone przez funkcję wywoła błąd, który przerwie działanie programu, dlatego potrzebujemy funkcji, która pozwoli nam dynamicznie rozszerzać ją:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void rozszerz(vector< vector< char > > &tab, int ax, int ay) {
    if (tab[0].size() <= ax)
        for (int i = 0; i < tab.size(); i++)
            for (int j = tab[i].size(); j <= ax; j++)
                tab[i].push_back(-1);
    if (tab.size() <= ay)
        for (int i = tab.size(); i <= ay; i++) {
            tab.push_back(vector< char >(0));
            for (int j = 0; j < tab[0].size(); j++)
                tab[i].push_back(-1);
        }
}

Jej działanie sprowadza się przede wszystkie do porównania obecnej wielkości tablicy z podanymi dodatkowo parametrami. Jeśli tablica jest za mała to nastąpi jej rozszerzenie. W przypadku, gdy jest za mało kolumn to do każdego elementu listy dodanie nowej z wartością pola nieodwiedzonego. W przypadku, gdy jest za mało wierszy to zostanie dodany nowy o takiej samej ilości kolumn co wszystkie pozostałe.

W celu optymalizacji wykorzystania pamięci program będzie automatycznie poszerzał tablicę jeśli zaistnieje taka potrzeba. Do tego będzie służyć funkcja poszerz(), która podaną tablicę tab i pozycji (ax,ay) upewni się, że tablica nie jest za mała.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        static void poszerz(ref List<List<char>> tab, int ax, int ay) {
            if (tab.Count <= ay)
                for (int i = tab.Count; i <= ay; i++) {
                    tab.Add(new List<char>(0));
                    for (int j = 0; j < tab[0].Count; j++)
                        tab[i].Add('\0');
                }
            if (tab[0].Count <= ax)
                for (int i = 0; i < tab.Count; i++)
                    for (int j = tab[i].Count; j <= ax; j++)
                        tab[i].Add('\0');
        }

Funkcja pomocnicza porownaj() będzie porównywać dwa znaki i zwracać 1 jeśli są identyczne i 0 jeśli nie. Będzie ona wykorzystywana do określania przesunięcia śladu.

1
2
3
        static int porownaj(char a, char b) {
            return (a == b) ? 1 : 0;
        }

Funkcja Główna

W głównej funkcji następuje deklaracja zmiennych, których będziemy uzywać:tab- będzie przechowywać tablicę pól, które zostały (nie)odwiedzone,ax i ay- współrzędne aktualnego położenia rysowania,r- zmienna pomocnicza mówiąca ile pól należy przesunąć w daną stronę,f- zmienna przechowująca kierunek określony w instrukcji oraz lista, która przechowuje instrukcje zawarte w wprowadzonej linijce tekstu.

1
2
3
4
5
int main() {
    vector< vector< char > > tab(1, vector<char>(1, false));
    int ax = 0, ay = 0, r;
    char f;
    string wyraz;
1
2
3
4
5
        static void Main(string[] args) {
            List<List<char>> tab = new List<List<char>>();
            int ax = 0, ay = 0, r;
            char f;
            string wyraz;

Druga część wczytuje dane i rozdziela tak, aby każda komenda była dostępna na liście jako oddzielny element.

6
7
8
    cout << "Podaj dane: ";
    getline(cin, wyraz);
    vector<string> lista = podziel(wyraz, ' ');
6
7
8
            Console.WriteLine("Podaj dane:");
            wyraz = Console.ReadLine();
            string[] lista = wyraz.Split(' ');

Część trzecia to pętla wywoływana dla każdego elementu z listy. Najpierw rozbija instrukcje na zmienne i zapisuje do f określony kierunek i do r ilość kroków. Następnie póki kroki się nie skończą zmieniane są wartości współrzędnych ax i ay. Przed przypisywaniem w danym polu wartości odwiedzenia wywołujemy funkcję rozszerz(), aby uniknąć problemów z wyjściem poza zakres tablicy.

 9
10
11
12
13
14
15
16
17
18
    for (int i = 0; i < lista.size(); i++) {
        f = lista[i][0];
        r = naInt(lista[i].substr(1, lista[i].length() - 1));
        while (r-- > 0) {
            ax += (f == 'U') * -1 + (f == 'D');
            ay += (f == 'L') * -1 + (f == 'R');
            rozszerz(tab, ay, ax);
            tab[ax][ay] = 0;
        }
    }
 9
10
11
12
13
14
15
16
17
18
            for (int i = 0; i < lista.Length; i++) {
                f = lista[i][0];
                r = Convert.ToInt32(lista[i].Substring(1, lista[i].Length - 1));
                while (r-- > 0) {
                    ax += porownaj(f, 'U') * -1 + porownaj(f, 'D');
                    ay += porownaj(f, 'L') * -1 + porownaj(f, 'R');
                    poszerz(ref tab, ay, ax);
                    tab[ax][ay] = '0';
                }
            }

Czwarta część jest to wypisywanie wyniku. To właśnie tu określamy jak dana wartość pola ma być interpretowana.

19
20
21
22
23
    for (int y = 0; y < tab.size(); y++) {
        for (int x = 0; x < tab[y].size(); x++)
            cout << ((tab[y][x] == -1) ? '_' : '0');
        cout << endl;
    }
1
2
3
4
5
            for (int y = 0; y < tab.Count; y++) {
                for (int x = 0; x < tab[y].Count; x++)
                    Console.Write(((tab[y][x] == '\0') ? '_' : '0'));
                Console.WriteLine();
            }

Kończymy pracę programu wpierw zatrzymując wykonywanie programu, aby użytkownik mógł przeczytać wyjście, a po naciśnięciu dowolnego przycisku program jest zamykany.

24
25
26
    system("PAUSE");
    return 0;
}
1
2
            Console.ReadKey();
        }