Benutzer:Stefan Knauf/Vornamenermittlungsprogramm.cpp


C++-Programm, um aus einem XML-Dump (Hilfe:Download) aller Artikel der deutschen Wikipedia aus den Einbindungen der Personendatenvorlage die Vornamen auszuzählen.

Das Programm geht davon aus, dass die Einbindungen der Personendatenvorlage das in der Vorlagendokumentation spezifizierte Format haben, nämlich:

{{Personendaten
|NAME=Name, Vorname1 Vorname2
|ALTERNATIVNAMEN=
|KURZBESCHREIBUNG=
|GEBURTSDATUM=
|GEBURTSORT=
|STERBEDATUM=
|STERBEORT=
}}

Es funktioniert aber auch, wenn die senkrechten Striche nicht am Zeilenanfang, sondern am Ende der Vorgängerzeile stehen, was bei vielen Personendatenvorlageneinbindungen auch tatsächlich der Fall ist (im Dump vom 6.9.2013 (dewiki-20130906-pages-articles-multistream.xml, komprimierte Dateigröße 3,2 GB) kommt „{{Personendaten“ 498.859-mal vor, davon 3.654-mal als „{{Personendaten|“ mit senkrechtem Strich am Ende, was sich mit dem Dateiumfassenderdurchsuchungsprogramm leicht ermitteln lässt).

Das Programm durchsucht den XML-Dump nach Zeilen, die ausschließlich aus der Zeichenfolge „{{Personendaten“ oder „{{Personendaten|“ bestehen. Wird so eine Zeile gefunden, wird ihre Nachfolgerzeile nach einem Komma durchsucht, auf das ein Leerzeichen folgt („, “). Wird ein solches Komma gefunden, wird das erste Wort dahinter als Vorname verbucht (bei mehreren Vornamen wird auch nur der erste gezählt). Als Wortende zählt dabei das nächste Leerzeichen, der nächste senkrechte Strich oder das nächste Zeilenende (das, was davon zuerst kommt). Als Wortanfang zählt dabei das erste Zeichen nach dem Leerzeichen, es sei denn, dieses Zeichen ist eines der drei Zeichen, die als Wortende gezählt werden, dann ist das gefundene Wort leer. Wenn das auf diese Weise ermittelte Wort auf ein Komma, eine Semikolon oder ein Tabulatorzeichen endet, wird ein solches Zeichen am Wortende entfernt, bis das Wort nicht mehr auf ein solches Zeichen endet. Wenn das auf diese Weise ermittelte Wort leer ist (beispielsweise wenn hinter „, “ direkt die Zeile endet) oder wenn die Zeile die Folge „, “ gar nicht enthält, dann wird diese Zeile für die Vornamenzählung ignoriert. Dafür wird eine Hinweismeldung am Bildschirm ausgegeben, die die Nummer der betreffenden Zeile enthält, und der Fall wird als „komischer Sachverhalt“ protokolliert. Viele dieser „komischen Sachverhalte“ sind völlig in Ordnung, weil bei Personen des Mittelalters wie Adam von Bremen, die nur einen Vornamen und eine Herkunftsbezeichnung tragen, kein Komma in der Namenzeile stehen sollte. Beim oben erwähnten XML-Dump vom 6.9.2013 werden 30.237 „komische Sachverhalte“ und 466.922 vom Programm als normal empfundene Personendatenvorlageneinbindungen gezählt.

Am Ende schreibt das Programm in eine Ausgabedatei eine Liste aller auf diese Weise als Vornamen ermittelten Wörter. Jedes dieser Wörter erhält in der Ausgabedatei eine eigene Zeile, die jeweils aus dem Wort, einem Tabulatorzeichen und der Anzahl, wie oft es als Vorname gezählt wurde, besteht. Zusätzlich wird noch am Bildschirm ausgegeben, aus wie vielen Personendatenvorlageneinbindungen ein Vorname gezählt wurde und wie viele „komische Sachverhalte“ das Programm vermerkt hat.

Bei den vom Programm als Vornamen gezählten Wörtern handelt es sich nur fast immer um den Vornamen. Es kommen auch Ordnungszahlen (z.B. „1.“), Bestandteile von Künstlernamen (z.B. „Big“), Titel (z.B. „Abu“) oder Abkürzungen (z.B. „P.“) vor. Die Ausgabe für den oben erwähnten XML-Dump vom 6.9.2013 ist dort zu betrachten.

Das Programm ist, einmal kompiliert, aus einer Kommandozeile aufzurufen, wobei ihm beim Aufruf zwei Parameter zu übergeben sind: Als ersten den Dateinamen des XML-Dumps (natürlich auf der lokalen Festplatte, wo er entkomprimiert vorzuliegen hat) und als zweiten Parameter den Namen, den die Ausgabedatei erhalten soll. Ein korrekter Programmausruf aus einer Kommandozeile sieht beispielsweise so aus:

Vornamenermittlungsprogramm dewiki-20130906-pages-articles-multistream.xml Ausgabe.txt



//C++

// 23.9.2013


using namespace std;

#include<iostream> //enthält die Befehle zum Schreiben auf den Bildschirm
#include<fstream> //enthält die Befehle zum Schreiben in und zum Lesen aus Dateien
#include<string> //enthält den Kram zu Strings, also zu Zeichenketten
#include<vector> //für die praktische Vektor-Klasse vector<irgendein Typ>
#include<time.h> //für time(NULL)
#include <algorithm> //für std::sort


struct Supername
 {string Name;
  long long int Anzahl;
 };

string schreibeZiffer(const long long int & z) //wandelt eine ganze Zahl von 0 bis 9 in einen String, der genau diese Zahl enthält
 {string Ziffer;
  switch (z)
   {case 1 : Ziffer = "1"; break;
    case 2 : Ziffer = "2"; break;
    case 3 : Ziffer = "3"; break;
    case 4 : Ziffer = "4"; break;
    case 5 : Ziffer = "5"; break;
    case 6 : Ziffer = "6"; break;
    case 7 : Ziffer = "7"; break;
    case 8 : Ziffer = "8"; break;
    case 9 : Ziffer = "9"; break;
    case 0 : Ziffer = "0"; break;
    default : cerr << "Der Ziffernmacher stößt auf was Unziffriges!" << endl;
   }
  return Ziffer;
 }

string schreibeZahlmitPunkten (long long int z) //wandelt eine ganze Zahl in einen String mit Tausenderpunkten
 {string Zahl=""; //für die Ausgabe
  if (z==0)
   {Zahl="0";
   }
  else
   {if (z<0)
     {Zahl="-"+schreibeZahlmitPunkten(-1*z);
     }
    else
     {int j=0;
      while (z>0)
       {if (j==3)
         {Zahl = "."+Zahl;
          j=0;
         }
        Zahl = schreibeZiffer(z%10)+Zahl;
        z/=10;
        j++;
       }
     }
   }
  return Zahl;
 }

vector<Supername> Namenermittlungsprozedur (const string & Dateiname) //liest die ersten Vornamen aus den Personendaten aus den Wikipediaartikeln aus und ermittelt die Anzahl des Vorkommens als erster Vorname
/*geht davon aus, dass die Personendaten etwa so anfangen:

{{Personendaten
|NAME=Nachname, Vorname1 Vorname2

funktioniert aber auch bei:

{{Personendaten|
NAME=Nachname, Vorname1 Vorname2

*/
//Die Prozedur liest aus der zweiten Zeile der Personendatenvorlageneinbindung das erste Wort nach ", " aus.
//Als Wortende wird dabei ein Leerzeichen, | oder Zeilenende interpretiert.
//Am Wortende stehende Kommata, Semikolönne oder Tabulatorzeichen werden entfernt, bis das Wort nicht mehr auf ein solches Zeichen endet.
//Kommt in der zweiten Zeile der Personendatenvorlage die Zeichenfolge ", " nicht vor oder ist das auf die oben beschriebene Weise
//ermittelte Wort leer, wird für diese eine Vorlageneinbindung kein Vorname gezählt. Stattdessen wird vermerkt, dass dort
//irgendwas Komisches war.
 {long long int Zeilennummer=0; //um die Zeilennummer nachzuhalten
  long long int AnzahlkomischerSachverhalte=0;
  long long int AnzahlnormalerSachverhalte=0;
  vector<Supername> Supernamensliste(0); //für die Ausgabe
  ifstream Datei;
  Datei.open(Dateiname.c_str());
  if (!Datei.good())
   {Datei.close();
    cerr << "Beim Öffnen der Datei " << Dateiname << " ist ein Fehler aufgetreten!" << endl;
   }
  else
   {while (Datei.good())
     {string Zeile;
      Zeilennummer++;
      getline(Datei, Zeile); //schreibt eine Zeile der Datei in die Variable Zeile
      if ((Zeile=="{{Personendaten")||(Zeile=="{{Personendaten|")) 
       {Zeilennummer++;
        getline(Datei, Zeile);
        string::size_type n = Zeile.find(", ", 0);
        if ((n==string::npos)||(Zeile.size()<n+3))
         {cout << "Komischer Sachverhalt in Zeile " << schreibeZahlmitPunkten(Zeilennummer) << " entdeckt." << endl;
          AnzahlkomischerSachverhalte++;
         }
        else
         {string::size_type m = Zeile.find(" ", n+2);
          string::size_type k = Zeile.find("|", n+2);
          string Vorname="";
          for (string::size_type  i=n+2; (i<Zeile.size())&&(i<m)&&(i<k); i++)
           {Vorname.push_back(Zeile[i]);
           }
          while ((Vorname.size()>0)&&((Vorname[Vorname.size()-1]==',')||(Vorname[Vorname.size()-1]==';')||(Vorname[Vorname.size()-1]=='\t'))) //entfernt möglicherweise vorhandene Kommata, Semikolönne oder Tabulatorzeichen am Wortende 
           {Vorname.erase(Vorname.size()-1);
           }
          if (Vorname.size()==0)
           {cout << "Irgendwie anders komischer Sachverhalt in Zeile " << schreibeZahlmitPunkten(Zeilennummer) << " entdeckt." << endl;
            AnzahlkomischerSachverhalte++;
           }
          else
           {int i=0;
            bool gefunden=false;
            while ((i<Supernamensliste.size())&&(!gefunden))
             {if (Supernamensliste[i].Name==Vorname)
               {gefunden=true;
                Supernamensliste[i].Anzahl++;
               }
              i++;
             }
            if (!gefunden)
             {Supername x={Vorname, 1};
              Supernamensliste.push_back(x);
             }
            AnzahlnormalerSachverhalte++;
           }
         }
       }
     }
    Datei.close();
    cout << "Die Anzahl komischer Sachverhalte betrug " << schreibeZahlmitPunkten(AnzahlkomischerSachverhalte) << ".\n";
    cout << "Die Anzahl ordnungsgemäß erkannter Personendatenvorlageneinbindungen, aus denen der erste Vorname ausgelesen wurde, betrug " << schreibeZahlmitPunkten(AnzahlnormalerSachverhalte) << "." << endl;
   }
  return Supernamensliste;
 }

bool Supernamenvergleichsfunktion (Supername x, Supername y)
 {return (x.Anzahl>y.Anzahl);
 }

void Namenshandhabungsprozedur(const string & Eingabdateiname, const string & Ausgabedateiname)
{vector<Supername> Supernamensliste=Namenermittlungsprozedur(Eingabdateiname.c_str());
 sort (Supernamensliste.begin(), Supernamensliste.end(), Supernamenvergleichsfunktion);
 ofstream Datei;
 Datei.open(Ausgabedateiname.c_str());
 if (Datei.good())
  {Datei << static_cast<char>(-17) << static_cast<char>(-69) << static_cast<char>(-65); //um anzuzeigen, dass die Ausgabedatei in UTF-8 kodiert ist
   for (int i=0; i<Supernamensliste.size(); i++)
    {Datei << Supernamensliste[i].Name << '\t' << Supernamensliste[i].Anzahl << '\n';
    }
   cout << "Die Ausgabe befindet sich nun in der Datei " << Ausgabedateiname << "." << endl;
  }
 else
  {cerr << "Es ist ein Problem beim Öffnen der Ausgabedatei " << Ausgabedateiname << " aufgetreten!" << endl;
  }
 Datei.close();
}
 
int main(int argc, char ** argv)
 {long long int Startzeit = time(NULL);
  if (argc>2)
   {Namenshandhabungsprozedur(argv[1], argv[2]);
   }
  else
   {cout << "Dem Programm müssen beim Aufruf zwei Dateinamen übergeben werden:\n";
    cout << "Als ersten den Namen der Eingabedatei und als zweiten den Namen, den die Ausgabedatei bekommen soll.\n";
    cout << "Ein korrekter Programmaufruf sieht beispielsweise so aus: " << argv[0] << " Eingabe.txt Ausgabe.txt" << endl;
   }
  time_t Laufzeit=time(NULL) - Startzeit;
  if (Laufzeit==1)
   {cout << "Das Programm hat für seinen Lauf eine Sekunde benötigt.\n";
   }
  else
   {cout << "Das Programm hat für seinen Lauf " << Laufzeit << " Sekunden benötigt.\n";
   }
  cout << '\a' << endl; //gibt einen Ton aus, um das Ende des Programms zu signalisieren
  return 0;
 }