Parsing einer Spieldatei

881
Jamal

Hinweis: Diese Datei liegt nicht in meiner Hand. Ich kann das Format oder den Typ der zu analysierenden Datei nicht ändern.

Hier sind einige Beispieldaten, die ich zu analysieren versuche. Dies sind Informationen für nur einen Spieler:

[0] (com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)#8
            _profileIconId = 4
            elo = 0
            eloChange = 0
            gameId = 82736631
            gameItems = (null)
            inChat = false
            leaver = false
            leaves = 1
            level = 5
            losses = 2
            profileIconId = 4
            skinName = "Vladimir"
            statistics = (mx.collections::ArrayCollection)#9
              filterFunction = (null)
              length = 26
              list = (mx.collections::ArrayList)#10
                length = 26
                source = (Array)#11
                  [0] (com.riotgames.platform.gameclient.domain::RawStatDTO)#12
                    displayName = (null)
                    statTypeName = "PHYSICAL_DAMAGE_TAKEN"
                    value = 11156
                  [1] (com.riotgames.platform.gameclient.domain::RawStatDTO)#13
                    displayName = (null)
                    statTypeName = "TOTAL_DAMAGE_TAKEN"
                    value = 20653
                  [2] (com.riotgames.platform.gameclient.domain::RawStatDTO)#14
                    displayName = (null)
                    statTypeName = "ITEM2"
                    value = 3158
                  [3] (com.riotgames.platform.gameclient.domain::RawStatDTO)#15
                    displayName = (null)
                    statTypeName = "ITEM4"
                    value = 3089
                  [4] (com.riotgames.platform.gameclient.domain::RawStatDTO)#16
                    displayName = (null)
                    statTypeName = "WIN"
                    value = 1
                  [5] (com.riotgames.platform.gameclient.domain::RawStatDTO)#17
                    displayName = (null)
                    statTypeName = "PHYSICAL_DAMAGE_DEALT_PLAYER"
                    value = 18413
                  [6] (com.riotgames.platform.gameclient.domain::RawStatDTO)#18
                    displayName = (null)
                    statTypeName = "TOTAL_HEAL"
                    value = 16877
                  [7] (com.riotgames.platform.gameclient.domain::RawStatDTO)#19
                    displayName = (null)
                    statTypeName = "ITEM0"
                    value = 3083
                  [8] (com.riotgames.platform.gameclient.domain::RawStatDTO)#20
                    displayName = (null)
                    statTypeName = "LARGEST_CRITICAL_STRIKE"
                    value = 173
                  [9] (com.riotgames.platform.gameclient.domain::RawStatDTO)#21
                    displayName = (null)
                    statTypeName = "ITEM3"
                    value = 3116
                  [10] (com.riotgames.platform.gameclient.domain::RawStatDTO)#22
                    displayName = (null)
                    statTypeName = "ITEM1"
                    value = 0
                  [11] (com.riotgames.platform.gameclient.domain::RawStatDTO)#23
                    displayName = (null)
                    statTypeName = "GOLD_EARNED"
                    value = 11123
                  [12] (com.riotgames.platform.gameclient.domain::RawStatDTO)#24
                    displayName = (null)
                    statTypeName = "ASSISTS"
                    value = 8
                  [13] (com.riotgames.platform.gameclient.domain::RawStatDTO)#25
                    displayName = (null)
                    statTypeName = "LARGEST_MULTI_KILL"
                    value = 2
                  [14] (com.riotgames.platform.gameclient.domain::RawStatDTO)#26
                    displayName = (null)
                    statTypeName = "MAGIC_DAMAGE_DEALT_PLAYER"
                    value = 103124
                  [15] (com.riotgames.platform.gameclient.domain::RawStatDTO)#27
                    displayName = (null)
                    statTypeName = "BARRACKS_KILLED"
                    value = 0
                  [16] (com.riotgames.platform.gameclient.domain::RawStatDTO)#28
                    displayName = (null)
                    statTypeName = "LARGEST_KILLING_SPREE"
                    value = 5
                  [17] (com.riotgames.platform.gameclient.domain::RawStatDTO)#29
                    displayName = (null)
                    statTypeName = "ITEM5"
                    value = 0
                  [18] (com.riotgames.platform.gameclient.domain::RawStatDTO)#30
                    displayName = (null)
                    statTypeName = "MINIONS_KILLED"
                    value = 176
                  [19] (com.riotgames.platform.gameclient.domain::RawStatDTO)#31
                    displayName = (null)
                    statTypeName = "CHAMPIONS_KILLED"
                    value = 11
                  [20] (com.riotgames.platform.gameclient.domain::RawStatDTO)#32
                    displayName = (null)
                    statTypeName = "MAGIC_DAMAGE_TAKEN"
                    value = 8581
                  [21] (com.riotgames.platform.gameclient.domain::RawStatDTO)#33
                    displayName = (null)
                    statTypeName = "NEUTRAL_MINIONS_KILLED"
                    value = 12
                  [22] (com.riotgames.platform.gameclient.domain::RawStatDTO)#34
                    displayName = (null)
                    statTypeName = "TOTAL_TIME_SPENT_DEAD"
                    value = 189
                  [23] (com.riotgames.platform.gameclient.domain::RawStatDTO)#35
                    displayName = (null)
                    statTypeName = "TURRETS_KILLED"
                    value = 0
                  [24] (com.riotgames.platform.gameclient.domain::RawStatDTO)#36
                    displayName = (null)
                    statTypeName = "NUM_DEATHS"
                    value = 5
                  [25] (com.riotgames.platform.gameclient.domain::RawStatDTO)#37
                    displayName = (null)
                    statTypeName = "TOTAL_DAMAGE_DEALT"
                    value = 121538
                uid = "B6F2F234-2E37-D979-E896-AA7614FCE9CB"
              sort = (null)
              source = (Array)#11
            summonerName = "WeFearTheSun"
            teamId = 100
            userId = 21672484
            wins = 6

Jetzt gibt es zehn Spieler in einem Spiel, das heißt zehn von diesem Snippet oben, jeder mit einem steigenden Zähler, [1], [2], [3] und so weiter.

Wie Sie innerhalb dieser Daten sehen können, gibt es weitere Nummern, dh. ein anderer [0].

Wenn die Zahlen eindeutig sind, könnte ich nur das analysieren [0], dann das [1], dann das [2]usw.

Im Moment analysiere ich die Dateien so:

private IEnumerable<Player> GetGamePlayers(string content)
{
    List<Player> Players = new List<Player>();

    var location = content.IndexOf("com.riotgames.platform.gameclient.domain::EndOfGameStats");
    var cutContent = content.Substring(location, 65000);

    var playerOneLocation = cutContent.IndexOf("[0]");
    //var playerOneContent = cutContent.Substring(playerOneLocation, 6231);
    var playerOneContent = cutContent.Substring(playerOneLocation, 6255);
    Players.Add(ParsePlayer(playerOneContent));

    cutContent = cutContent.Substring(playerOneLocation + 6229, cutContent.Length - playerOneLocation - 6229);
    var playerTwoLocation = cutContent.IndexOf("[1]");
    var playerTwoContent = cutContent.Substring(playerTwoLocation, 6255);
    Players.Add(ParsePlayer(playerTwoContent));

    cutContent = cutContent.Substring(playerTwoLocation + 6229, cutContent.Length - playerTwoLocation - 6229);
    var playerThreeLocation = cutContent.IndexOf("[2]");
    var playerThreeContent = cutContent.Substring(playerThreeLocation, 6255);
    Players.Add(ParsePlayer(playerThreeContent));

    cutContent = cutContent.Substring(playerThreeLocation + 6229, cutContent.Length - playerThreeLocation - 6229);
    var playerFourLocation = cutContent.IndexOf("[3]");
    var playerFourContent = cutContent.Substring(playerFourLocation, 6255);
    Players.Add(ParsePlayer(playerFourContent));

    cutContent = cutContent.Substring(playerFourLocation + 6229, cutContent.Length - playerFourLocation - 6229);
    var playerFiveLocation = cutContent.IndexOf("[4]");
    var playerFiveContent = cutContent.Substring(playerFiveLocation, 6255);
    Players.Add(ParsePlayer(playerFiveContent));

    location = cutContent.IndexOf("teamPlayerParticipantStats");
    cutContent = cutContent.Substring(location, 32000);
    var playerSixLocation = cutContent.IndexOf("[0]");
    var playerSixContent = cutContent.Substring(playerSixLocation, 6255);
    Players.Add(ParsePlayer(playerSixContent));

    cutContent = cutContent.Substring(playerSixLocation + 6229, cutContent.Length - playerSixLocation - 6229);
    var playerSevenLocation = cutContent.IndexOf("[1]");
    var playerSevenContent = cutContent.Substring(playerSevenLocation, 6255);
    Players.Add(ParsePlayer(playerSevenContent));

    cutContent = cutContent.Substring(playerSevenLocation + 6229, cutContent.Length - playerSevenLocation - 6229);
    var playerEightLocation = cutContent.IndexOf("[2]");
    var playerEightContent = cutContent.Substring(playerEightLocation, 6255);
    Players.Add(ParsePlayer(playerEightContent));

    cutContent = cutContent.Substring(playerEightLocation + 6229, cutContent.Length - playerEightLocation - 6229);
    var playerNineLocation = cutContent.IndexOf("[3]");
    var playerNineContent = cutContent.Substring(playerNineLocation, 6255);
    Players.Add(ParsePlayer(playerNineContent));

    cutContent = cutContent.Substring(playerNineLocation + 6229, cutContent.Length - playerNineLocation - 6229);
    var playerTenLocation = cutContent.IndexOf("[4]");
    var playerTenContent = cutContent.Substring(playerTenLocation, 6255);
    Players.Add(ParsePlayer(playerTenContent));

    return Players;
}

Ich kann sagen, der Code ist schrecklich und es gibt viel Raum für Verbesserungen. Wie würde ich den riesigen Inhalt für jeden einzelnen Spieler in einzelne Blöcke aufteilen?

Ich brauche den String-Inhalt:

[0] (com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)#8
to
[1] (com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)#8

then:
[1] (com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)#8
to
[2] (com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)#8
Antworten
9
Nur ein untergeordneter Punkt, Sie sollten immer den spezifischsten Typ zurückgeben, während Sie den kleinsten spezifischen Parameter als benötigen. Ich würde also in diesem Fall eine Liste anstatt eines IEnumerable zurückgeben, damit Sie auf Anzahl usw. zugreifen können. Die Verwendung von IEnumerable für einen Eingabeparameter anstelle von List war jedoch ein gutes Design Homde vor 9 Jahren 0
@mko - Sie sollten keine `Liste zurückgeben`object, [FxCop] (http://msdn.microsoft.com/de-de/library/ms182142%28v=VS.100%29.aspx) wird sehr aufgeregt sein, wenn Sie dies tun. Sie sollten sich auch diesen Artikel von [Dave Donaldson] (http://arcware.net/use-collectiont-instead-of-listt-for-public-api/) und von [StackOverflow] (http: // stackoverflow) ansehen .com / questions / 387937 / warum-ist-es-für-schlecht-zum-Aussetzen-Liste (), die besprechen, warum es eine schlechte Praxis ist, eine `Liste 'zurückzugeben`. Ich bin nicht der Meinung, dass die Rückgabe des spezifischsten Typs, den Sie können, generell eine gute Praxis ist, aber List wäre kein guter Kandidat für eine Rückkehr. pstrjds vor 9 Jahren 0

1 Antwort auf die Frage

14
doppelgreener

Einige Faustregeln, die Ihren Code verbessern sollten:

  • Wenn Sie Variablen mit Namen ähnelt haben varOne, varTwo, varThree, etc. - egal, was die Zahl sollten Sie wahrscheinlich einen Array werden.
  • Wenn Sie mehrere identische Aktionen für eine Folge von Werten ausführen, sollten Sie eine Schleife verwenden.
  • Code sollte niemals wiederholt werden müssen.

Mit diesen Gedanken machen wir uns jetzt an die Arbeit.

private IEnumerable<Player> GetGamePlayers(string content)
{
    List<Player> Players = new List<Player>();

    // Get the endgame stats text
    string endgameStatsMarker = "com.riotgames.platform.gameclient.domain::EndOfGameStats"
    int endgameStatsLocation = content.IndexOf(endgameStatsMarker);
    string endgameStats = content.Substring(endgameStatsLocation);

    // Split the endgame stats text into an array of player information sections
    string[] charBeginMarker = new string[] {"(com.riotgames.platform.gameclient.domain::PlayerParticipantStatsSummary)"}
    string[] playerInfoSet = endgameStats.Split(charBeginMarker, SplitStringOptions.None);

    // don't assume there's 10; get this from the logfile
    int numPlayers = GetMaxPlayers();

    // playerDataSet[0] is before any playerInfo begins, so we skip it.
    for (int i = 1; i <= numPlayers; i++);
    {
        Players.Add(ParsePlayer(playerData));
        // The string passed to ParsePlayer will be a little different now.
        // It will contain a [#] on the end except in the case of the last player.
        // This is since we split the text at the beginning of the player field
        // and AFTER the [#].
        // You will have to ensure your ParsePlayer method will work with this
        // new input.
    }

    return Players;
}

... Nun, der größte Teil Ihres Codes wurde nach ein paar einfachen Regeln entfernt. Die Geschichte von Bill Atkinson ist hier wahrscheinlich sehr relevant. ;)

Auf eine bessere Analyse

Es gibt ein großes Problem in Ihrer Herangehensweise an das Dateiparsing, über das ich mit Ihnen sprechen muss.

Sie erraten, wie lange der Player- Datenabschnitt auf dem Erscheinungsbild des aktuellen Protokolldateiformats basiert und dieses Programm fest in das Programm einfügt. Untersuchen wir die Fehler dieser Methode. Sag mir, was passiert, wenn eines dieser Dinge passiert:

  1. Spielerdatenabschnitte werden um einen Charakter länger.
  2. Spieler-Datenabschnitte erhalten eine weitere Informationszeile.
  3. Spieler-Datenabschnitte haben ein anderes Array eingeführt.

Die Antworten (kein Blick): 1. Das Analysieren von Spielerdaten kann komisch werden. 2. Ihre Analyse wird wahrscheinlich explodieren. 3. Ihre Analyse wird auf jeden Fall in einem schrecklichen Durcheinander explodieren.

Wenn Sie die Datei korrekt analysieren, ist Ihr Programm in jedem Fall mindestens um eine Größenordnung weniger hoch, bis eine wesentliche Änderung im Protokolldateiformat vorgenommen wird.

Der Schlüssel zum Analysieren dieser Datei ist das Erkennen, dass diese Datei voll von lesbaren und vom Computer lesbaren Markups ist, die Sie gar nicht ausnutzen.

Folgendes tun Sie: Öffnen Sie die Datei. Stellen Sie fest, wo sich die Abschnitte teilen. Zählen Sie die Anzahl der Zeichen. Hardcodieren Sie dies in das Programm und extrahieren Sie diese Zeichen.

Folgendes sollten Sie tun: Öffnen Sie die Datei. Stellen Sie fest, wo sich die Abschnitte teilen. Bestimmen Sie, wie Sie festgestellt haben, wo sich die Abschnitte teilen. Schreiben Sie Code, der diese Aufteilung erkennt, und teilt den Dateiinhalt entsprechend auf.

Der Schlüssel ist also: zu verstehen, wie Sie verstehen, was Sie sehen, und dann Ihrem Programm beibringen, es auch zu verstehen!

Betrachten Sie zum Beispiel diese Regeln:

  • Ein Zeicheninformationsabschnitt beginnt bei einer bestimmten Zeichenfolge und endet beim nächsten Auftreten derselben Zeichenfolge. Sie können also nur die Zeicheninformationsabschnitte in dieser Zeichenfolge teilen.
  • Sobald Sie ein erreicht haben #, haben Sie ein spezielles Feld gefunden. Es kann ein einfacher Kommentar oder eine ID für jedes eindeutige Objekt sein, sodass Objekte aus dem Cache wiederverwendet werden können, falls zwei Objekte identisch sind. Es ist wahrscheinlich sicher, von A #bis zum Ende einer Zeile zu ignorieren, es sei denn, Sie finden eine Verwendung dafür.
  • Eine Variable steht am Anfang einer Zeile nach einem Leerzeichen. Die Menge an Whitespace ist signifikant (siehe letzter Punkt).
  • Wenn das erste Zeichen in einer Zeile gefunden wird, handelt [es sich um ein Element eines Arrays und nicht um einen Variablennamen.
  • Ein Variablenname endet am ersten Leerzeichen.
  • Der Wert einer Variablen befindet sich auf der anderen Seite =und entfernt unnötige Leerzeichen auf der anderen Seite.
  • Wenn ein Wert mit einer Ganzzahl beginnt, ist dies eine Ganzzahl. Wenn es mit Anführungszeichen beginnt, ist es eine Zeichenfolge.
  • Variablen enthalten immer eine Element-ID und dann eine Typdeklaration. Die folgenden Variablen sind wahrscheinlich die Eigenschaften eines Objekts dieses Typs.
  • Der Inhalt eines Feldelements ist immer eingerückt. Eine Erhöhung der Einrückungstiefe bedeutet, dass Sie jetzt den Inhalt des Arrays aus der oberen Zeile lesen. Eine Verringerung der Einrückungstiefe bedeutet, dass Sie den Inhalt dieses Arrays vollständig gelesen haben.

Neun Regeln, nur um zu verstehen und zu beschreiben, wie ich verstehe, was ich sehe. Es sollte ziemlich einfach sein, sie in Form von Methoden im Parser zu implementieren - dann kann Ihr Parser auf einmal 90% der Logfile-Syntax verarbeiten! An dieser Stelle fehlen nur einige wenige Dinge, z. B. das Analysieren der Typdeklaration eines Array-Elements oder das Ableiten des Wertes einer Variablen (Zeichenfolge? Integer?).

Vielen Dank für Ihre Hilfe. Seit ich diese Frage heute morgen gestellt habe, habe ich den Code erheblich geändert, und die meisten der Dinge, die Sie erwähnt haben, haben mich während des Mittagessens angestarrt. : D Ihr Vorschlag bezüglich der Verwendung von .Split ist sehr gut! Es hat meine LOC-Anzahl deutlich reduziert und es sehr verständlich gemacht. Beim Kompilieren wird jedoch ein Fehler ausgelöst, der besagt, dass die .Split () -Methode keine Zeichenfolge, sondern eine Zeichenfolge verwendet. vor 9 Jahren 0
@Sergio: Whoops! Ich habe "Split" missbraucht. Die Überladung, nach der ich gegangen bin, nimmt einen `String []` und einen `StringSplitOptions`. Kein Wunder, dass es sich beschwerte. Ich habe diese beiden Zeilen umgeschrieben. doppelgreener vor 9 Jahren 0
Vielen Dank, Jonathan. Ihre Zeit wird sehr geschätzt und ich lerne jede Menge auf dieser Seite! Überprüfen Sie in meinem Schnitt nach der endgültigen Version, nachdem Sie Ihrem .Split-Hinweis gefolgt sind. Es ist wesentlich sauberer. : D kann dir nicht genug danken. vor 9 Jahren 0