Zahl aus Datei ab Zeichen einlesen

stevieda

Grünschnabel
Hallo zusammen,

ich habe folgendes Problem:
Ich möchte gerne aus einer Datei Zahlen in mein Programm einlesen. Die Datei ist ein ASCII-Text-File und beinhaltet neben Text auch Zahlen, die ich gerne extrahieren möchte.

Beispiel:
population size (popsize): 50
number of generations (ngen): 100
mutation probability (pmut): 0.0001
crossover probability (pcross): 0.8
score frequency (scorefreq): 1
flush frequency (flusgfreq): 10
number of bits (precision): 16
number of best genomes (nbest): 3
convergence percentage (pconv): 1.01
generations to convergence (nconv): 40
(seed): 1


Die Bezeichnungen in den Klammern entsprechen den Variablennamen in meinem Programm. So soll also z.B. der Wert, der in der Zeile steht, wo der String "popsize" auftaucht, meiner Variablen popsize zugewiesen werden.

Ich bin ziemlicher Neuling in der C++-Programmierung und stehe jetzt ein wenig auf dem Schlauch, da ich gerne eine vernünftige und kompakte Lösung hätte.

Mein bisheriger Code:
Code:
//declare variables for GA parameters
	int popsize;
	int ngen;
	float pmut;  
	float pcross;
	int scorefreq;
	int flushfreq;
	int precision;
	int nbest;
	float pconv;
	int nconv;
	unsigned int seed;

	//read variables from input file
	std::ifstream inputfile("GA_input.txt", std::ios::in);
	if(!inputfile.is_open()) return 0;
	std::string line;
	while(getline(inputfile, line)) {
		if (0 == line.length()) {
		  continue;
	}
	std::string::size_type firstIndex = line.find_first_of(":");
	std::string sPopsize = line.substr(firstIndex + 1);
	std::stringstream sstr(sPopsize);
	sstr >> popsize;
	}
	inputfile.close();

Momentan sucht mein Code nur nach einem Doppelpunkt als Trennzeichen und nimmt den Wert dahinter. Nun habe ich aber ja mehrere Variablen, nach denen ich suchen muss. Was wäre denn hier eine elegante Lösung? Ich kann die Zahlen noch beliebig anordnen (anderes Trennzeichen, andere Position, etc.). Ich hätte nur gerne zu jeder Zahl einen Kommentar in der Zeile stehen.

Habe mir schon einiges aus Foren und Hilfeseiten zusammengesucht. Aber letztendlich fehlt mir noch ein wenig um mein Problem zu lösen.

Vielen Dank für jede Form der Hilfe und Anregung.

Gruß,
Stevie
 
Moin Stevie,

so auf Anhieb fallen mir zwei Lösungsmöglichkeiten ein :

(a) Wenn es sicher ist, dass Deine Variablen immer in der gleichen Reihenfolge in der txt-Datei stehen, kannst Du sie ja auch in dieser Reihenfolge ansprechen und füllen!

(b) Oder - und das ist die deutlich bessere, weil sichere (auber auch aufwendigere) Variante - lies in jeder Zeile zuvor die entsprechende Variable ein. Da diese ja geklammert ist, kann Du genauo die substr-Fuktion verwendet, wie beim Doppelpunkt!

Gruß
Klaus
 
Hallo Klaus,

die Variablen werden immer in dieser Reihenfolge stehen. Du meinst also, dass ich die Variablen einzeln anspreche, so wie ich es in meinem Beispielcode getan habe?

int popsize;
int ngen;
float pmut;
float pcross;
int scorefreq;
int flushfreq;
int precision;
int nbest;
float pconv;
int nconv;
unsigned int seed;

//read variables from input file
std::ifstream inputfile("GA_input.txt", std::ios::in);
if(!inputfile.is_open()) return 0;
std::string line;
while(getline(inputfile, line)) {
if (0 == line.length()) {
continue;
}
std::string::size_type firstIndex = line.find_first_of(":");
std::string sPopsize = line.substr(firstIndex + 1);
std::stringstream sstr(sPopsize);
sstr >> popsize;

std::string::size_type firstIndex = line.find_first_of(":");
std::string sNGen = line.substr(firstIndex + 1);
std::stringstream sstr(sNGen);
sstr >> ngen;
.
.
.
}
inputfile.close();

Aber ich muss doch dann immer wieder in die nächste Zeile wechseln, oder? Gibt es da keine kompaktere Lösung? Evtl. eine Art Schleife? Wobei ich mir da auch nicht sicher bin ob das hier überhaupt möglich ist. Ein Array und eine Struktur scheiden hier wohl aus. Array aufgrund unterschieldicher Datentypen, Struktur wegen Array.

Evtl. noch eine Idee?

Gruß,
Stevie
 
Hallo Stevie,

nutze bitte die Code-Tags (der Button mit der '#' im Editor), da das Ganze so reichlich unleserlich ist !

Evtl. eine Art Schleife?
WHILE ist doch eine Schleife ! ! ! :eek:
Aber so, wie Du es jetzt umgebaut hast, würdest Du bei jedem Durchlauf den gefundenen Wert nach dem Doppelpunkt auf alle Variablen schreiben - und am Ende wären alle mit dem letzten Wert von 'seed' belegt.

Entweder stellst Du sicher, dass bei jeden Schleifendurchlauf die richtige Variable angesprochen wird, oder - und dass ist wie gesagt eleganter und vor allem sicherer - Du bestimmt in der Zeile, die Du gerade gelesen hast. Etwa so:

Code:
std::string::size_type Index_KlammerAuf = line.find_first_of("("); 
std::string::size_type Index_KlammerZu = line.find_first_of(")");
std::string strVariablenname = line.substr( Index_KlammerAuf, Index_KlammerZu );  // enthält jetzt den Namen der Variable, dem der Wert zugeordnet werden soll

Jetzt brauchst Du nur noch den Wert nach dem Doppelpunkt auslesen und der entsprechenden Variablen zuzuordnen. Natürlich musst Du Dir hierfür noch einen Mechanismus überlegen, der über den jeweils gerade ermitteltzen String die entsprechende Variable nimmt. Hier sind Vektoren, Maps etc. mögliche Ansätze!
D. h. sofern Du sie verwenden kannst, da ich Deine Entwicklungsumgebung nicht kenne!

gruß
Klaus
 
Zuletzt bearbeitet:
Hallo Klaus,

ja, natürlich ist WHILE eine Schleife :). Wollte ja auch nur sagen, dass die While-Schleife so, wie ich sie verwendet habe, nicht zu gebrauchen ist.

Also ich habe noch mal ein wenig recherchiert und mit Hilfe Deiner Anmerkungen folgenden Vorschlag:

Code:
        //declare variables for GA parameters
	int popsize;
	int ngen;
	float pmut;  
	float pcross;
	int scorefreq;
	int flushfreq;
	int precision;
	int nbest;
	float pconv;
	int nconv;
	unsigned int seed;

	std::map<std::string, int> params;
	std::map<std::string, int>::iterator MIter;
	params["popsize"] = 0;
	params["ngen"] = 1;
	params["pmut"] = 2;
	params["pcross"] = 3;
	params["scorefreq"] = 4;
	params["flushfreq"] = 5;
	params["precision"] = 6;
	params["nbest"] = 7;
	params["pconv"] = 8;
	params["nconv"] = 9;
	params["seed"] = 10;

	//read variables from input file
	std::ifstream inputfile("GA_input.txt", std::ios::in);
	if(!inputfile.is_open()) return 0;
	std::string line;
	while(getline(inputfile, line)) {
		if (0 == line.length()) {
			continue;
		}
		std::string::size_type Index_KlammerAuf = line.find_first_of("("); 
		std::string::size_type Index_KlammerZu = line.find_first_of(")");
		std::string strVariablenname = line.substr(Index_KlammerAuf + 1, Index_KlammerZu - Index_KlammerAuf - 1);
		std::string::size_type firstIndex = line.find_first_of(":");
		std::string mystring = line.substr(firstIndex + 1);
		std::stringstream sstr(mystring);
		MIter = params.find(strVariablenname);
		switch(MIter->second) {
			case 0:
				sstr >> popsize;
				break;
			case 1:
				sstr >> ngen;
				break;
			case 2:
				sstr >> pmut;
				break;
			case 3:
				sstr >> pcross;
				break;
			case 4:
				sstr >> scorefreq;
				break;
			case 5:
				sstr >> flushfreq;
				break;
			case 6:
				sstr >> precision;
				break;
			case 7:
				sstr >> nbest;
				break;
			case 8:
				sstr >> pconv;
				break;
			case 9:
				sstr >> nconv;
				break;
			case 10:
				sstr >> seed;
				break;
			default:
				return 1;
		}
	}
	inputfile.close();

Es funktioniert auf jeden Fall. Aber ist es so auch geschickt oder gibt es für dieses Problem eine elegantere Lösung? Es soll ja nicht nur funktionieren, sondern ich möchte auch in Sachen "Programmierstil" dazu lernen.
Auf jeden Fall schon einmal vielen Dank für Deine Hinweise!

Gruß,
Stevie
 
Wenn du es so machen willst:
C++:
// declare variables for GA parameters
std::map<std::string, float> params;

// read variables from input file
std::ifstream file_stream("GA_input.txt");
if (!file_stream) return false;

std::string line;
while (std::getline(file_stream, line)) 
{
    if (line.empty()) continue;

    const std::string::size_type var_begin(line.find("(") + 1);
    const std::string::size_type var_end(line.find(")", var_begin + 1));
    const std::string::size_type value_begin(line.find(":", var_end + 1) + 1);
  
    std::ostringstream ss(line.substr(value_begin));
    ss >> params[line.substr(var_begin, var_end - var_begin)];
}
sollte dann schon reichen und bischen schneller sein als deins, weil du zu weit vorne im String schon immer anfängst zu suchen ;) Außerdem nutzt du die falsche find-Funktion ;)
 
Moin Stevie,

Es funktioniert auf jeden Fall. Aber ist es so auch geschickt oder gibt es für dieses Problem eine elegantere Lösung? Es soll ja nicht nur funktionieren, sondern ich möchte auch in Sachen "Programmierstil" dazu lernen.
Das es t ist ja auf jeden Fall schon mal gut :)

Ob es geschickter / eleganter geht Nun ja, ich finde, dass ist immer eine Frage der Definition! Der Code von devDevil scheint mir auf den ersten Blick sicher effizienter programmiert zu sein, aber was heißt in diesem Zusammenhang schon "elegant"? ? ?

Mir hat man an der Uni den Grundsatz vermittelt: "im Zweifel Lesbarkeit / Wartbarkeit VOR Effizienz". Dies soll nun nicht heißen, dass man jeden Blödsinn programmieren soll und darf, aber es soll (und muss) für andere nachvollziehbar sein! Allerdings reicht hier natürlich ggf. auch eine ausreichende Kommentierung des Codes.

Ich habe hier bei uns in der Firma, als ich vor zwei Jahren anfing, mehrere Projekte mit C++ und Java übernommen - und teilsweise heute noch Probleme, gewisse Dinge schlüssig nachzuvollziehen! Das mag alles recht effizient programmiert sein, aber "Dank" weitgehend fehlender Kommentare, ist oft nur schwer nachvollziehbar, warum was gemacht wurde (selbst mit dem Debugger) .....

EDIT: Also in Bezug auf "Eleganz" würde ich mich nie genau festlegen wollen; es kommt immer darauf an, ob es einen wirklich Grund für die Verwendung eines bestimmten Terminus oder Konzepts gibt! Ein Kollege 'liebt' Templates und macht fast alles darüber. Folge: sehr kurzer (und wohl auch effizienter) Code - nur, nachvollziehen kannst Du es (auch mangels Kommentaren) kaum - zumindest nicht auf die Schnelle .....

Zu dem "find_first_of" kann ich übrigens nichts sagen, da es mein System hier (Visual Studo mit C++ 6.0 und der STL) scheinbar gar nicht kennt. Von daher ist es immer gut, kurz zu beschreiben, mit welchem System/Compiler man arbeitet!

Gruß
Klaus
 
Zuletzt bearbeitet:
Hallo devDevil,

also das sieht ja schon einmal sehr kompakt aus. Gefällt mir sehr gut. Aber wie sieht es mit meinen Variablen aus? Meine sind je gemischt (int, float) und in der map ist float vordefiniert. Kann ich das trotzdem so machen und dann wenn ich die Variablen brauche casten?

Vielen Dank schon einmal für den Code!

Gruß,
Stevie
 
Hallo Klaus,

da hast Du natürlich völlig recht. Ich musste vor 3 Monaten in die C++-Programmierung einsteigen und wusste fast nichts über C++. Dann musste ich mich aber ziemlich schnell mit Klassen, Templates, Vererbung etc. beschäftigen und hätte mir gewünscht, dass so mancher seinen Code ein wenig mehr mit Kommentaren versieht. Insofern werde ich mir Deinen Rat zu Herzen nehmen und getreu dem Motto "im Zweifel Lesbarkeit / Wartbarkeit VOR Effizienz" weiter programmieren.

Das mit der Info bzgl. System/Compiler werde ich bei meiner nächsten Frage umsetzen ;)
Vielen Dank auch für die Hilfe und für die Hinweise!

Gruß,
Stevie
 
Hi.
Ob es geschickter / eleganter geht Nun ja, ich finde, dass ist immer eine Frage der Definition! Der Code von devDevil scheint mir auf den ersten Blick sicher effizienter programmiert zu sein, aber was heißt in diesem Zusammenhang schon "elegant"? ? ?
Für mich heißt elegant meist kurz, aber nicht kryptisch. Man sollte ruhig dem Compiler einiges an Optimierungen zutrauen.

Ich würde den Code von DevDevil schon als elegant bezeichnen. Er ist kurz und knapp, verwendet Standardmethoden und man sieht auf den ersten Blick was dort gemacht wird.
Zu dem "find_first_of" kann ich übrigens nichts sagen, da es mein System hier (Visual Studo mit C++ 6.0 und der STL) scheinbar gar nicht kennt. Von daher ist es immer gut, kurz zu beschreiben, mit welchem System/Compiler man arbeitet!
find_first_of ist eine Standardmethode der string Klasse in der STL. In der Regel sollte man davon ausgehen können, dass ein C++ Compiler dies unterstützt. Wenn man natürlich so einen altertümlichen Compiler nutzt, der noch nie ein (halbwegs konformer) C++ Compiler war, braucht man sich auch nicht wundern.

Als Vorschlag zum Einlesen der Variablen unterschiedlichen Typs:
C++:
struct data_t {
	void* value;
	enum { FLOAT, INT } type;
};

int popsize;
int ngen;
float pmut;

map<string, data_t> params;
params["popsize"].value = &popsize;
params["popsize"].type = data_t::INT;
params["ngen"].value = &ngen;
params["ngen"].type = data_t::INT;
params["pmut"].value = &pmut;
params["pmut"].type = data_t::FLOAT;

...

while (getline(...)) {
  string varname = ...;

  switch (map[varname].type) {
  case data_t::INT:
    ss >> *static_cast<int*>(map[varname].value); break;
  case data_t::FLOAT:
    ss >> *static_cast<float*>(map[varname].value); break;
  }
}
Gruß
 
Zurück