Datoteke

Datoteke

Avtor: Andrej Pavšič

Navodilo naloge

V neki mapi imamo tekstovne datoteke z imeni n.txt, kjer je n naravno število. Iz datotek želimo pripraviti novo datoteko v skladu z navodili, ki so zapisana v datoteki z navodili. V vsaki vrstici te datoteke je zapis oblike n: seznam, kje je n naravno število, ki označuje ime datotek, seznam pa seznam vrstic, ki jih je potrebno kopirati iz dane datoteke. V datoteki navodila se seveda lahko pojavi ime datoteke večkrat, prav tako, se lahko pojavi jo določene vrstice večkrat. Program naj prebere datoteko z navodili in na zaslon izpiše, kaj bo vsebovala nova datoteka in hkrati tvori datoteko v skladu s temi navodili.



Datoteka z navodili: navodila.txt

Nova datoteka: rezultat.txt

V novi datoteki bodo naslednje vrstice.

1.txt: 2, 4, 7-12, 14-15, 19

4.txt: 1, 5-6, 11-12

5.txt: 1-3, 10

Opis problema in ideja rešitve

Naloga lahko razdelimo na dva glavna dela.

  • branje z datotek: prebrali bomo datoteko navodila in datoteke, ki so podane v datoteki navodil.
  • pisanje na datoteko: v drugem delu bomo na datoteko rezultat zapisovali vrstice, ki nam jih narekuje datoteka navodil.
  1. V datoteki z navodili je potrebno prebrati vsako vrstico posebej. Pri branju vrstice pa moramo najprej paziti, da so podatki ločeni z ustreznimi ločili – podpičje loči ime datoteke od vrstic, ki jih je potrebno zapisati, le te pa med seboj ločimo z vejicami in pomišljaji. Druga stvar, ki jo je potrebno preverjati, pa je nahajanje oz. obstoj zapisanih tekstovnih datotek v isti mapi, kjer se nahajajo navodila.
    Če so pri branju izpolnjeni vsi pogoji, lahko na konzolo izpišemo »osnovni« tekst (brez podatkov o izpisanih vrsticah), ki je podan v navodilu naloge.
  2. Ideja pri pisanju je, da vsebino vse celotne datoteke, ki je podana v eni izmed vrstic v navodilih, hranimo v tabeli, kjer vsaka vrstica zavzame svoje mesto. Tako lahko v primeru ponovitev določene vrstice to izpišemo brez problema.

Seveda se je treba na začetku programa zavarovati pred napačnim vnosom imena datoteke. V primeru napačnega vnosa je uporabniku smiselno ponuditi še eno možnost za vnos. V primeru, da se v datoteki navodil nahajajo napačna ločila, napačne datoteke pa to ni potrebno, saj mora v takem primeru uporabnik najprej sam popraviti navodila. Smiselno je le prekiniti izvajanje programa z vrženo izjemo. V kolikor je v navodilih v vrstici številka kakšna številka vrstica večja o števila vrstic datoteke ali pa je podana negativna vrednost, se mora prekiniti izvajanje programa, smiselno pa je tudi izbrisati datoteko, kamor smo imeli to namen zapisati. Uporabili bomo eno zanko po vrsticah datoteke navodil in v tej zanki še eno zanko po seznamu vrstic, ki so podane v vrsticah navodil.

Razlaga algoritma

Program public static void NovaDatoteka(string datNavodila, string novaDatoteka="rezultat.txt")
za vhodna parametra sprejme dva niza – prvi pove s katere datoteke bomo brali navodila za kreiranje datoteke, ki jo podamo v drugem nizu. Vnos prvega parametra je obvezen, drugi ima privzeto vrednost.

V programu najprej preverimo obstoj datoteke z navodili. Če ni vnesena pravilna pot do te datoteke, ponudimo možnost novega vnosa. Ko podamo nov vnos, še enkrat kličemo program NovaDatoteka z vnesenim nizom. Tako se stvar ponavlja, dokler ni vnesena tekstovna datoteka s točno lokacijo.

Po podani ustrezni datoteki najprej poiščemo mapo, v kateri se datoteka nahaja. To uporabimo pri kreiranju nove datoteke – datoteka z rezultatom se tako nahaja v isti mapi kot datoteka z navodili. Nato ustvarimo podatkovni tok za branje po datoteki navodil in podatkovni tok za pisanje na datoteko rezultat.

Preden poženemo zanko, s katero bomo pregledali vrstice navodil, deklariramo:

  • niz, v katerega bomo shranili ime datoteke, ki je zapisano v tekoči vrstici;
  • tabelo nizov, v katerih bomo hranili vrstice datoteke, ki jih bomo »prekopirali« v datoteko rezultat. Deklaracija je potrebna pred »try, catch« konstruktom, ki ga bomo uporabili v zanki za preverjanje, če se med imenom in številkam nahaja dvopičje. Ustvarimo še števec »ind«, s katerim bomo izpisali v kateri vrstici se nahaja napaka in ki bo pomagal, da se tekst s podatki o uporabljenih datotekah izpiše samo enkrat – samo ko je ind enak 1.

Sledi zanka po vseh vrsticah datoteke navodila. Tako za zanko spišemo »try, catch« konstrukt. V try preverimo, če lahko ime datoteke in vrstice v datoteki shranimo v predhodno deklariran niz in tabelo nizov. Vrstico razdelimo glede na podpičje. Niz na prvem mestu je ime datoteke, na drugem pa številke, ki označujejo vrstice v tej datoteki. Načeloma imena datoteke dobimo v vsakem primeru, pri shranjevanju števil v tabelo nizov pa lahko pride do težav, če ime datoteke in številke vrstic med seboj niso ločene s podpičjem. V takem primeru sprožimo izjemo, kjer uporabimo indeks ind, ki nam pove v kateri vrstici na vhodni datoteki navodil je napačen zapis. Naslednje preverjanje, ki sledi neposredno, je obstoj datoteke, katere ime smo shranili v niz. Datoteka se mora nahajati v isti mapi kot navodila. Če datoteka ne obstaja, na konzolo izpišemo v kateri vrstici imamo napačno podano ime datoteke in ustavimo izvajanje zanke. Pred tem še zbrišemo datoteko rezultati.

Če so vsi podatki v vrstici ustrezno zapisani, se pripravimo na zapisovanje v novo datoteko. Vsebino datoteke dobimo s pomočjo še enega podatkovnega toka za branje. Z metodo »ReadToEnd()« preberemo celotno vsebino datoteke. Vsebino shranimo v novo tabelo nizov, ločimo je glede na skok v novo vrsto. Znotraj zanke ustvarimo še eno zanko, tokrat zanko for, ki bo tekla po tabeli nizov, kjer hranimo številke vrstic. Številko vrstice shranimo v niz. Temu nizu odstranimo vse bele znake in ga ločimo glede na pomišljaj – v primeru da imamo podan interval vrstic potrebujemo začetno in končno vrednost tega intervala. Ker bomo pretvarjali niz v število še nimamo garancije, da je v nizu število (lahko se poleg številk nahaja tudi kak znak), zato nastavimo še en »try, catch« konstrukt, ki nam bo prestregel napake pri metodi Parse. V takem primeru vržemo izjemo, javimo kje v datoteki navodil smo naleteli na napako (s pomočjo indeksa ind in indeksa i nazadnje definirane for zanke). Če imamo stvari zapisane kot je treba, sledi zapis na novo datoteko. Nad celotno for zanko zanko je obešen še en »try, catch«, ki pa preverja, da kakšno število ni večje od števila vrstic v datoteki. V primeru, da ne naletimo na takšno večje število, sprožimo izjemo in zapišemo v kateri vrstici smo naleteli na tak primer.

Po zaključeni for zanki na konzolo izpišemo, katero datoteko oz. njene vrstice smo uspešno zapisali na novo datoteko.

Koda v C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Datoteke
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("ime dat: ");
            string d = Console.ReadLine();
            Console.WriteLine();
            NovaDatoteka(d);
            Console.ReadKey();
        }


        public static void NovaDatoteka(string datNavodila, string novaDatoteka="rezultat.txt")
        {
            // OBSTOJ DATOTEKE Z NAVODILI - datNavodila
            if (File.Exists(datNavodila) == false)
            {
                Console.WriteLine("Datoteka ne obstaja ali ima napačno podano pot");
                Console.WriteLine();
                Console.Write("ime dat: "); // če datoteka ne obstaja, ponudimo možnost novega vnosa
                string d = Console.ReadLine();
                NovaDatoteka(d); // in program poženemo ponovno
            }

            else
            {
                string pot = Directory.GetParent(datNavodila).ToString(); // lokacija (parent folder) datoteke navodil na disku
                novaDatoteka = pot + "\\" + novaDatoteka;
                // podatkovni tok za branje iz datoteke navodil
                StreamReader branjeNavodil = new StreamReader(datNavodila);
                // podatkovni tok za pisanje na novo datoteko
                StreamWriter pisanjeNaNovo = new StreamWriter(novaDatoteka);


                string imeDat; string[] seznamVrstic;

                int ind = 1;
                int vr = 0; int vr2 = 0;

                string vrstica = branjeNavodil.ReadLine();
                while (vrstica != null)
                {
                    #region BRANJE
                    try // preverimo, če so podatki v datoteki navodil ustrezno zapisani - podpičja, vejice
                    {
                        // po vrsticah
                        imeDat = vrstica.Split(':')[0].Trim(); // ime datoteke se nahaja levo od dvopičja
                        seznamVrstic = vrstica.Split(':')[1].Split(','); // seznam vrstic pa desno od podpičja, ločimo glede na vejice
                    }
                    catch (Exception e)
                    { throw new Exception("Podatki v datoteki navodil niso pravilo vnešeni. Napaka v "+ind+". vrstici.\n\nPrimer: 1.txt: 2, 4, 7-12, 14-15"+e); }

                    string imeDat2 = pot + "\\" + imeDat;

                    // preverjamo obstoj datotek, ki so navedene v datoteki navodila
                    if (File.Exists(imeDat2.ToString()) == false)
                    {
                        Console.WriteLine("Datoteka "+imeDat+" zapisana v "+ind+". vrstici datoteke navodila ne obstaja");
                        pisanjeNaNovo.Close();
                        File.Delete(novaDatoteka);
                        break; // končamo izvajanje zanke
                    }

                    StreamReader beriDat = new StreamReader(imeDat.ToString()); // podatkovni tok za "kopiranje" teksta iz datotek
                    string[] vsebinaDatoteke = beriDat.ReadToEnd().Split('\n');
                    #endregion

                    if (ind == 1) // samo enkrat - v prvem koraku zanke
                    { // če uspešno preberemo datoteko, potem izpišemo:
                        Console.WriteLine("Datoteka z navodili: " + datNavodila);
                        Console.WriteLine("Nova datoteka: " + novaDatoteka);
                        Console.WriteLine("V novi datoteki bodo naslednje vrstice.");
                    }

                    #region PISANJE
                    // po "seznamu vrstic", ki se nahaja desno od dvopičja
                    for (int i = 0; i < seznamVrstic.Length; i++)
                    {
                        string stVrstice = seznamVrstic[i].Trim(); // pri vsakem elementu v tabeli se znebimo vseh "white space" znakov
                        string[] st = stVrstice.Split('-');

                        try
                        {
                            if (st.Length == 1)
                            { vr = int.Parse(stVrstice); }
                            else
                            {
                                vr = int.Parse(st[1]);
                                vr2 = int.Parse(st[0]);
                            }
                        }
                        catch (Exception)
                        {
                            pisanjeNaNovo.Close();
                            File.Delete(novaDatoteka);
                            throw new Exception("Napačen zapis števila v datoteki navodil v " + ind + ". vrstici na " + (i + 1) + ". tem mestu.");
                        }

                        try
                        {
                            // če imamo podano samo eno vrstico
                            if (st.Length == 1)
                            {
                                pisanjeNaNovo.WriteLine(vsebinaDatoteke[vr - 1]);
                            }

                            // če imamo zapisan interval vrstic, podan z začetno in končno vrstico, ločen s pomišljajem
                            else
                            {
                                int stZaporednihIzpisov = vr - vr2;
                                while (stZaporednihIzpisov >= 0)
                                {
                                    pisanjeNaNovo.WriteLine(vsebinaDatoteke[vr - stZaporednihIzpisov]);
                                    stZaporednihIzpisov--;
                                }
                            }
                        }
                        catch (Exception ee)
                        {
                            pisanjeNaNovo.Close();
                            File.Delete(novaDatoteka);
                            throw new Exception("Številka vrstice v " + ind + ". vrstici na "+(i+1)+". mestu datoteke navodil je preveliko." + ee);
                        }
                    }


                    //še izpis na konzolo:
                    Console.WriteLine(vrstica);
                    beriDat.Close();

                    vrstica = branjeNavodil.ReadLine(); // skok v novo vrstico
                    ind += 1;
                    #endregion
                }
                branjeNavodil.Close();
                pisanjeNaNovo.Close();
            }
        }
    }
}

Testni primeri

Potrebno je testirati, kako program deluje, če naleti na napako pri branju datoteke navodil ali če pri pisanju naleti na neustrezne podatke. Pred branjem datoteke pa moramo preveriti, če vnesena datoteka sploh obstaja. Preverimo z if stavkom. V primeru, da ni vnesena pravilna pot, se ponudi možnost ponovnega vnosa.

Če vnesena datoteka obstaja, potem pri branju najprej preverimo, če je vsebina pravilno napisana – če ostaja dvopičje med imeni datotek in številkami vrstic. Če to ni izpolnjeno, prestrežemo s catch, datoteko je potrebno popraviti. Drugo preverjanje je preverjanje obstoja datotek, ki so podane v navodilih. V glavni while zanki gremo čez vse napisane vrstice, v vsaki vrstici pa z if stavkom preverimo obstoj – predpostavka je, da se vse datoteke nahajajo v isti mapi kot datoteka navodil. V kolikor naletimo na datoteko, ki ne obstaja, vstavimo izvajanje glavne zanke. Na konzolo še izpišemo v kateri vrstici se nahaja napaka, da lahko to z lahkoto popravimo.

Sledi zapis na datoteko, pri katerem s try/catch najprej preverimo, da se vsi nizi »pretvorijo« (int.Parse()) v števila. Nato pa še z enim try/catch nastavkom preverimo, da slučajno ni kakšno število večje od števila vrstic dane datoteke.

Če je zadoščeno vsem pogojem, se program nemoteno izvede do konca.

0%
0%