Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Taulukkoon tiedoston lukeminen

Sivun loppuun

mika132 [27.01.2012 21:13:00]

#

Eli teen generoituvia nimiä peliini. Nyt ongelma on, että miten voin lukea kaikki rivit ja taulukoittaa sen? Eli tyyliin:
eli esim: nimet.dat tiedosto
Kalle
Harri
Jukka
Mikko

Kun se luetaan tallentuisi ne näin:

Nimi[0] = Kalle
Nimi[1] = Harri
Nimi[2] = Jukka
Nimi[3] = Mikko

Eli esim nimet.dat tiedostossa on neljä riviä ja kun sieltä luetaan tieto se tallentaa ne tuollaisiin muutujiin. Ja sieltä sitten niitä voisi käyttää. Toivottavasti ymmärsitte mitä tarkoitan.

jalski [28.01.2012 01:29:30]

#

Mikset vain käyttäisi vectoria tavallisen taulukon sijaan?

Tavallista taulukkoa tuohon tarkoitukseen käyttäessäsi joudut joka tapauksessa käymään tiedoston läpi ja laskemaan taulukkoa varten varattavan muistin määrän (paitsi tietysti siinä tapauksessa, että rivimäärä on aina vakio).

Voit tietenkin käydä tiedoston läpi ja lukea rivit ensin vaikka listaan, varata taulukon ja lopuksi siirtää listasta taulukkoon.

PL/I:llä homma olisi aika triviaali:

 readLines: proc (parm) options(main);
   dcl parm char (*) var;

   dcl in file;
   dcl i fixed bin;
   dcl line char (100) var controlled;
   dcl lines (*) char (100) var controlled;

   dcl (lbound, hbound, trim, allocation) builtin;

   ON UNDEFINEDFILE (in) begin;
     put skip list ('Can''t open file: ' || parm || ' for reading');
	 stop;
   end;

   ON ENDFILE (in) goto eof;


   open file(in) title (parm || ', type (text), recsize (100)') input;

   do forever;
     allocate line;
     get file (in) edit (line) (L);
   end;

  eof:
   close file(in);

   allocate lines(allocation(line));
   do i = allocation(line) to 1 by -1;
     lines(i) = line;
	 free line;
   end;

   do i = lbound(lines) to hbound(lines);
     put skip list ( 'line:'|| trim(i) || ': ' || trim(lines(i)) );
   end;
 end readLines;

Sienikasvusto [29.01.2012 23:50:18]

#

En yleensä anna suoraa koodia tällaisiin vaan kehotan siirtymään C++ tai C:n refrenssiin, mutta nyt alkoi jalskin vastaus vituttamaan sen verran, että annan suorat vastaukset.

Siitä huolimatta ensikerralla etsi googlella miten c++ luetaan tiedostoja. Tämäkään ratkaisu ei välttämättä ole täydellinen, sillä se vaan lukee joka rivin alkioihin.

jalski: Miten niin triviaali, tossa on 38 riviä kun vastaavan ja helppolukuisemman c++ esimerkin saa aikaan 31 rivillä (ilman kommentteja).

Ps. Tää on C/C++ osasto ei mikään purkka-PL/I osasto.

#include <iostream>
#include <vector>
#include <string>
#include <fstream>


int main(int argc, char **argv){
	// Jos ohjelmalle on annettu parametri niin ajetaan ohjelma muuten tulostetaan virhe :(
	if(argc == 2){
		// Alustellaan muutamat muuttujat
		std::string rivi = "";
		// nimet-taulukko (vektori)
		std::vector<std::string> nimet;

		// Tässä avataan itse tiedosto tutustu C++ refrenssiin
		std::ifstream tiedosto(argv[1]);

		// Jos tiedosto on auki niin...
		if(tiedosto.is_open()){
			// Luetaan tiedostoa niin kauan kun rivejä riittää tai ei tapahdu jotain muuta hasardia.
			while(tiedosto.good()){
				// Luetaan rivi
				std::getline(tiedosto, rivi);
				// Tunkataan rivi taulukkoon
				nimet.push_back(rivi);
			}
			// Kaikki rivit on nyt luettu joten voidaan käyttää aineistoa.
			// Esim tulostamalla ne:
			for(unsigned int i=0; i<nimet.size(); i++){
				std::cout << "nimet[" << i << "] = " << nimet[i] << std::endl;
			}
			return 0;
		}else{
			std::cout << "Tiedoston avaaminen ei onnistunut" << std::endl;
		}
		tiedosto.close();
	}else{
		std::cout << "Anna tiedostonimi" << std::endl;
		return -1;
	}
}

jalski [30.01.2012 08:31:45]

#

Sienikasvusto kirjoitti:

En yleensä anna suoraa koodia tällaisiin vaan kehotan siirtymään C++ tai C:n refrenssiin, mutta nyt alkoi jalskin vastaus vituttamaan sen verran, että annan suorat vastaukset.

Ehdotin vastaukseksi tavaran tallentamista vectoriin. Oma esimerkkisi näyttäisi tekevän juurikin näin. Jos olet mika132:n aikaisempia viestejä lukenut, niin niissä on käsitelty vectoreita ja tiedostostakin lukemista...

Ai niin, näyttäisi tuo C++ esimerkkisi olevan osapuilleen yhtä pitkä laittamani PL/I toteutuksen kanssa (ota pois turhat tyhjät rivit). Väitätkö muuten tosiaan, että oma if-lause "hirviösi" on helppolukuisempi, kuin muutama lyhyt silmukka ilman yhtään vertailua?

groovyb [30.01.2012 09:10:16]

#

On se huomattavasti helppolukuisempaa ottaen huomioon että c/c++ osastosta on kyse eikä PL/I

C# olisi

StreamReader s = new Streamreader("tiedosto.dat");

String data = s.ReadToEnd();
S.Close();

String[] names = data.Split(new string[] {"/r","/n"});

Mutta tätäkään ei kysytty.

Deffi [30.01.2012 09:12:53]

#

nyt kun tähän mentiin, niin php olisi

$nimet = file('nimet.dat');

Torgo [30.01.2012 09:37:05]

#

Tehkää nyt vielä brainfuckilla. ;)

jalski kirjoitti:

Jos olet mika132:n aikaisempia viestejä lukenut, niin niissä on käsitelty vectoreita ja tiedostostakin lukemista...

Juu. Sama kysymys toistuu tasaisin väliajoin. Tässä niistä muutama:
https://www.ohjelmointiputka.net/keskustelu/25496-tiedoston-luku-ongelma
https://www.ohjelmointiputka.net/keskustelu/21880-määrän-ja-arvojen-haku-tiedostosta
https://www.ohjelmointiputka.net/keskustelu/21686-tiedostoon-kirjoitus-lukeminen
https://www.ohjelmointiputka.net/keskustelu/19941-ulkoiset-tiedostot

neau33 [30.01.2012 11:48:11]

#

Heippa taas!

... koskapa alkuperäinen kysyjä halusi kaman taulukkoon...

//VC++ 6.0
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

bool taulukoi_nimet(char* filename);
string * Nimi; int koko;

int main()
{
	if(taulukoi_nimet("nimet.dat"))
		for(int i=0;i<=koko;i++)
			cout << Nimi[i] << endl;

	Nimi = NULL;
	return 0;
}

bool taulukoi_nimet(char* filename)
{
	ifstream file; bool palaute;
	file.open(filename, ios::in | ios::binary);

	if(file.is_open())
	{
		ostringstream oss; oss << file.rdbuf();
		string str = oss.str(); oss.clear();

		for (size_t i=0;i<str.length();i++)
			if(str.substr(i, 1) == "\n") koko++;

		Nimi = new string[koko]; file.close();
		char * temppi = (char *)str.c_str();
		char * rivi = strtok(temppi, "\n");
		int laskuri = 0; size_t found; string key("\r");

		while(rivi != NULL)
		{
			Nimi[laskuri] = ((string)rivi);
			found=Nimi[laskuri].rfind(key);
			if (found!=string::npos)
			Nimi[laskuri].replace(found, key.length(), "");
			laskuri++; palaute = true;
			rivi = strtok(NULL, "\n");
		}

		str.erase(); delete [] rivi; temppi = NULL;
	}

	return palaute;
}
//VC++ .NET
using namespace System;
using namespace System::IO;

int main(array<System::String^> ^args)
{
        cli::array<System::String^>^ Nimi;
        StreamReader ^ sr = gcnew StreamReader("nimet.dat");
        Nimi = sr->ReadToEnd()->
        Replace(char("\r"),(char)"")->Split((char)"\n");
        sr->Close(); sr = nullptr;

        for(int i=0;i<Nimi->Length;i++)
        {
            Console::WriteLine(Nimi[i]);
        }

        Console::WriteLine("Press any key to continue...");
        Console::ReadKey(true);

        delete [] Nimi; Nimi = nullptr; return 0;

}

Deffi [30.01.2012 17:43:37]

#

Scheme

(define (read-names input)
  (let ((line (read input)))
    (cond ((eof-object? line) nil)
          (else (cons line (read-names input))))))

(define p (open-input-file "nimet.dat"))
(define names (read-names p))
(close-input-port p)
> (car names)
Kalle
> (cadr names)
Harri
> (caddr names)
Jukka
> (cadddr names)
Mikko

Deffi [30.01.2012 19:10:56]

#

x86 windows assembly (fasm)

format PE CONSOLE
include 'win32a.inc'
SEEK_END        equ     2

section '.flat' code data import readable executable writeable
  library msvcrt,'msvcrt.dll'
  import msvcrt,\
         fopen,'fopen', fclose,'fclose', fread,'fread',\
         fseek,'fseek', ftell,'ftell', rewind,'rewind',\
         printf,'printf', malloc,'malloc', free,'free',\
         exit,'exit'

Nimi    dd      0

entry $ ; open file
        xor ebx, ebx            ; file handle
        call @f                 ; push offset ASCII 'rb' to the stack
        db 'r',0
@@:     call @f
        db 'nimet.dat',0
@@:     call [fopen]
        add esp, 8h             ; __cdecl
        test eax, eax
        xchg eax, ebx
        jz .finish

        ; obtain file size
        ccall [fseek], ebx, 0, SEEK_END
        ccall [ftell], ebx
        inc eax
        push eax
        ccall [rewind], ebx

        ; allocate memory for the file and read it
        call [malloc]
        mov edi, eax
        pop esi                 ; filesize
        ccall [fread], edi, 1, esi, ebx
        test eax, eax
        jz .finish              ; failure ;_;
        mov esi, eax
        mov byte [edi+eax], 0h  ; NULL-terminate

        ; count lines (result: edx)
        push 1                  ; shorter than mov edx, 1 ;)
        pop edx
        push edi
        mov ecx, esi
        mov al, 0ah             ; "\n"
@@:     repnz scasb
        jnz @f
        inc edx
        jmp @b
@@:     pop edi

        ; allocate memory for ptrs
        push edx
        shl edx, 2
        ccall [malloc], edx
        mov [Nimi], eax
        pop edx                 ; line count

        ; store ptrs to names and substitute newlines with NULLs
        push edx
        push edi
        mov [eax], edi
        mov esi, eax
        jmp @f
.next:  add esi, 4
        mov al, 0ah
        repnz scasb
        mov byte [edi-1], 0
        mov [esi], edi
@@:     dec edx
        jnz .next
        pop edi

        ; print dem names. note [esp] == linecount
        mov esi, [Nimi]
.namef: lodsd
        push eax
        call @f
        db '%s',0dh,0ah,0
@@:     call [printf]
        add esp, 8h
        dec dword [esp]
        jnz .namef
        pop eax

        ; clean exit
.finish:mov eax, [Nimi]         ; free pointers
        test eax, eax
        jz @f
        ccall [free], eax
@@:     test edi, edi           ; and file contents
        jz @f
        ccall [free], edi
@@:
        or ebx, ebx             ; close handle
        jz @f
        ccall [fclose], ebx
@@:     push 0
        call [exit]

koo [31.01.2012 00:28:57]

#

Mahtavatko nuo kaikki edes toimia, on sen verran villiäkin meininkiä. Jos

#include <deque>
#include <fstream>
#include <iostream>
#include <string>

using namespace std;

deque<string> lue_nimet(istream &is);

int main()
{
  ifstream tiedosto("nimet.dat");
  deque<string> nimet = lue_nimet(tiedosto);

  for (size_t i = 0; i < nimet.size(); ++i) cout << nimet[i] << "\n";

  return 0;
}

niin yksinkertainen toteutus voisi olla

#include <deque>
#include <istream>
#include <iterator>
#include <string>

deque<string> lue_nimet(istream &is)
{
  istream_iterator<string> begin(is), end;
  return deque<string>(begin, end);
}

Tuo lukee kaikki "sanat" "taulukkoon". Rivit voi lukea

deque<string> lue_nimet(istream &is)
{
  deque<string> nimet;
  string nimi;
  while (getline(is, nimi)) nimet.push_back(nimi);
  return nimet;
}

Metabolix [31.01.2012 00:53:23]

#

Ainakin neau33:n kannattaisi jättää vastaamatta, kun noinkin lyhyeen koodiin tulee noin iso määrä todella pahoja virheitä. Tuurilla koodi taitaa kuitenkin toimia, jos on 32-bittiset (4-tavuiset) osoittimet, 4-rivinen tiedosto ja Windowsin \r\n-rivinvaihdot ja jos tuon dynaamisen char-taulukon perään sattuu ylimääräinen nollatavu.

Sienikasvuston koodia selventäisi huomattavasti, jos ei olisi noin pitkiä lohkoja if-lauseissa vaan sen sijaan virheenkäsittelyssä käänteinen if-lause, jossa heitettäisiin poikkeus tai suljettaisiin ohjelma. Tulee ihan mieleen aikanaan töissä nähty kommentti: "// tässä if-lauseessa on 800 riviä eikä sillä ole else-lohkoa", vai miten olikaan.

koo teki tyypilliseen tapaansa hyvän esimerkin. Kun includet toistuvat toisessa listauksessa, ehkä myös using-lause olisi selvyyden vuoksi paikallaan.

User137 [31.01.2012 02:57:56]

#

Pascal, luokka-versio:

var nimet: TStringList;
    i: integer;
begin
  nimet:=TStringList.Create;
  nimet.LoadFromFile('nimet.dat');
  for i:=0 to nimet.Count-1 do
    writeln(nimet[i]); // Tulostetaan vielä nimet konsoliin yksi kerrallaan
  nimet.Free;
end;

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta