De-Interleave-Protokollzeilen: HARD MODE
Angesichts einiger bedauerlicher Regeln, die das Original zu einer verherrlichten Sortieraufgabe gemacht haben, veröffentliche ich eine herausfordernde Variante. Wenden Sie sich an Luis Mendo, um zu erfahren, wie Sie die ursprüngliche Herausforderung verbessern können.
Sie haben einen Server geerbt, auf dem mehrere Apps ausgeführt werden, die alle im selben Protokoll ausgegeben werden.
Ihre Aufgabe ist es, die Zeilen der Protokolldatei nach Quelle zu verschachteln. Zum Glück war die Person, die alle Apps geschrieben hat, nett genug, Tags zu hinterlassen, die ihre Quelle angeben.
Protokolle
Jede Zeile sieht ungefähr so aus:
[app_name] Something horrible happened!
- App-Tags stehen immer in eckigen Klammern und enthalten nur alphanumerische Zeichen und Unterstriche.
- App-Tags sind nicht leer
- Es kann später in einer bestimmten Zeile andere eckige Klammern geben, aber keine bildet ein gültiges Tag.
- Nach einem Tag steht immer mindestens ein Leerzeichen.
- Das gesamte Protokoll ist möglicherweise leer.
- Es gibt keine Begrenzung für die Anzahl der eindeutigen App-Tags in der Datei.
In einigen Fällen fehlt möglicherweise ein App-Tag. In diesem Fall gehört die Protokollzeile zur zuletzt protokollierten App.
- Die erste Zeile des Protokolls beginnt immer mit einem App-Tag
- Eine Zeile, die mit beginnt, muss
[nicht unbedingt markiert sein. Wenn zwischen den anfänglichen eckigen Klammern ein ungültiges Zeichen steht oder nein], wird die Zeile nicht markiert. - Im Protokoll werden keine Leerzeilen angezeigt
Erwartete Ausgabe
Sie sollten mehrere vollständig getrennte Protokolle ausgeben, wobei die App-Tags aus jeder Protokollzeile entfernt werden, in der sie vorhanden waren. Sie müssen keine führenden Leerzeichen in einer Protokollzeile beibehalten.
Ausgabeprotokolle müssen in einer Art Schlüsselwertzuordnung oder einem angemessenen Äquivalent vorliegen. Eine nicht erschöpfende Liste gültiger Ausgabeformate:
- Eine Datei, die für jede App nach dem App-Tag benannt ist
- Sie können davon ausgehen, dass die Ausgabedateien in diesem Fall noch nicht im Ausgabeverzeichnis vorhanden sind.
- Ein Wörterbuch / Map / Hash / was auch immer, das App-Tags als Schlüssel und eine durch Zeilenumbrüche getrennte Zeichenfolge der Protokollzeilen als Werte verwendet.
- Eine lange verkettete Zeichenfolge, die durch Leerzeilen getrennt ist und vor der App-Tags stehen
- Eine Liste von [Schlüssel, Wert] -Listen
- Eine JSON-Zeichenfolge mit App-Tags als Schlüssel und Arrays von Protokollzeilen als Werte
- Ein Markdown-Dokument mit App-Tags als Überschriften und führenden
#s einer Zeile, die mit Backslashes versehen sind. - Eine Javascript-Funktion, die eine Zeichenfolge als Eingabe verwendet und das zugehörige Protokoll als durch Zeilenumbrüche getrennte Zeichenfolge ausgibt.
Wenn Sie nicht wissen können, von welcher App die Protokollzeilen stammen, ist die Ausgabe grundsätzlich ungültig.
Beispiel
Ein ganzes Protokoll könnte folgendermaßen aussehen:
[weather] Current temp: 83F
[barkeep] Fish enters bar
Fish orders beer
[stockmarket] PI +3.14
[PI announced merger with E]
[barkeep] Fish leaves bar
[weather] 40% chance of rain detected
[ I have a lovely bunch of coconuts
Welches sollte drei verschiedene Protokolle ausgeben:
Wetter:
Current temp: 83F
40% chance of rain detected
[ I have a lovely bunch of coconuts
Barkeeper:
Fish enters bar
Fish orders beer
Fish leaves bar
Aktienmarkt:
PI +3.14
[PI announced merger with E]
Sie erhalten die Namen der App-Tags nicht im Voraus. Sie müssen sie nur durch Analysieren der Protokolldatei ermitteln.
Regeln und Wertung
- Dies ist Code-Golf , also gewinnt der kürzeste Code.
- Es gelten Standardregeln und Lücken
- Verwenden Sie ein beliebiges geeignetes E / A-Format, vorausgesetzt, jede Eingabezeile wird als Zeichenfolge dargestellt, nicht als vorab analysiertes Tag + Nachricht. Das Parsen ist Teil dieser Herausforderung .
- Die Ausgabeprotokollzeilen für jede App müssen in derselben Reihenfolge wie im ursprünglichen Protokoll angezeigt werden.
- Sie können davon ausgehen, dass das Eingabeprotokoll nur ASCII-Zeichen enthält.
Antworten
Python 3.8 , 95 Bytes
import re
lambda x:[((t:=re.match(r'\[(\w*)\]',s)or t)[1],s.split(t[0])[-1].strip())for s in x]
Probieren Sie es online aus!
(Erweitertes TIO-Beispiel mit Eingabe)
Erläuterung:
Für den :=Operator ist Python 3.8 erforderlich . Dies nimmt eine Liste von Zeichenfolgen als Eingabe und gibt eine Liste von (tag, body)Tupeln aus. Zunächst wird eine Regex-Übereinstimmung verwendet, um das Tag zu erhalten:
t:=re.match(r'\[(\w*)\]',s)or t)
Dies entspricht jeder anfänglichen Folge von Wortzeichen (alphanumerisch + Unterstrich) in eckigen Klammern, wobei die Wörter eine Erfassungsgruppe bilden. Wenn die Zeichenfolge mit diesem regulären Ausdruck übereinstimmt, handelt tes sich um ein matchObjekt mit zwei Elementen: der vollständigen Übereinstimmung und der Gruppe. Zum Beispiel, wenn die Zeichenfolge [tag] body, die matchhaben die Elemente [tag]und tag.
Wenn die Zeichenfolge nicht mit diesem regulären Ausdruck übereinstimmt, wird re.match()None zurückgegeben. Der Code wird t = None or t, was gerade ist t = t, so dass das Tag seinen Wert von der vorherigen Zeile behält. Wenn die erste Zeile keine Übereinstimmung hätte, würde dies einen Fehler verursachen, aber darüber müssen wir uns keine Sorgen machen!
Der Code erstellt dann das Tupel t[1], s.split(t[0])[-1].strip(), wobei t[1]sich die Erfassungsgruppe (das Tag ohne eckige Klammern) und t[0]das Tag mit eckigen Klammern befindet. Durch Aufteilen der Zeichenfolge auf das vollständige Tag wird der Hauptteil isoliert, unabhängig davon, ob das Tag tatsächlich in der Zeichenfolge vorhanden ist oder nicht.
Retina 0,8,2 , 95 Bytes
+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1
O$`(\w+).* $1
¶
¶¶
rm`(?<=^\1.*¶)¶(.\w+].)
(?<=(^|¶¶).\w+]).
¶
Probieren Sie es online aus! Erläuterung:
+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1
Kennzeichnen Sie alle nicht getaggten Zeilen.
O$`(\w+).* $1
Sortieren Sie die Zeilen aus meiner Antwort auf die ursprüngliche Herausforderung.
¶
¶¶
Doppelte Leerzeichen der Zeilen.
rm`(?<=^\1.*¶)¶(.\w+].)
Entfernen Sie doppelte Tags und die leere Zeile vor ihnen. Dies bedeutet, dass nur noch Leerzeilen übrig sind, die die einzelnen Tags begrenzen.
(?<=(^|¶¶).\w+]).
¶
Verschieben Sie das Tag in eine eigene Zeile.
perl -Mfeature = say -n,
47
46 Bytes
(Ein Byte mit freundlicher Genehmigung von @Dom Hastings gespeichert)
$;=$1 if s/^\[(\w+)\] +//;$;{$;}.=$_}{say for%;
Probieren Sie es online aus!
Wie funktioniert das?
Erstens die Wirkung des -nSchalters. Dies führt dazu, dass Perl das Programm in eine Schleife einschließt, die die Eingabe liest und den Body für jede Zeile ausführt. Dies geschieht jedoch auf sehr unkomplizierte Weise. Es umschließt den Körper vor dem Parsen wie folgt mit einer Schleife:
perl -ne 'TEXT'
wird verwandelt in
LINE: while (defined($_ = readline ARGV)) {
TEXT;
}
Aber das bedeutet, wenn Sie TEXTdie Form haben LOOP_BODY}{FINAL_STATEMENT, erhalten Sie das Programm:
LINE: while (defined($_ = readline ARGV)) {
LOOP_BODY
}
{
FINAL_STATEMENT;
}
Wir verwenden diesen Trick nur, um ein paar Bytes über einen ENDBlock zu speichern .
Im Programm selbst verwenden wir zwei Variablen für unsere Buchhaltung. $;enthält das aktuelle Tag, und im Hash %;verfolgen wir die Zeilen für jedes Tag. Jetzt prüfen wir für jede Zeile der Eingabe, ob sie mit einem Tag beginnt, und wenn ja, entfernen wir sie von der Zeile und merken uns das Tag:
$; = $1 if # Remember the tag if,
s/^\[(\w+)\] +//; # we can strip of a tag
Wir verketten dann die aktuelle Zeile (von einem Tag entfernt, falls vorhanden) mit dem Satz von Zeilen, die bereits für dieses Tag gesammelt wurden. Wenn es keine solchen Zeilen gibt, verketten wir sie effektiv mit der leeren Zeichenfolge:
$;{$;}.=$_ # Remember the current line
Nachdem wir alle Zeilen gelesen haben, drucken wir den Hash. Perl glättet einen Hash bequem zu einer einfachen Liste, wenn Sie ihn als Liste behandeln und die Schlüssel und Werte abwechseln. Dies gibt uns eine Ausgabe, bei der jeder Abschnitt durch eine neue Zeile getrennt ist und vom Tag geleitet wird.
say for%; # Print the flattened hash
05AB1E , 22 Bytes
vyD']¡н¦DžjÃÊi‚»]).¡#н
Eingabe als Liste von Zeilen, Ausgabe als Liste von Listen mehrzeiliger Zeichenfolgen.
Probieren Sie es online aus (hübsch gedruckt; Sie können die Fußzeile entfernen, um die tatsächliche Ausgabe anzuzeigen).
Erläuterung:
v # Loop `y` over each string of the (implicit) input-list:
yD # Push line `y` twice
']¡ '# Split the copy on "]"
н # Only leave the first part
¦ # Remove the leading character (the potential "[")
D # Duplicate it
žj # Push builtin string "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
à # Only keep those characters in the string we duplicated
Êi # If it is NOT equal to the string:
‚ # Pair it with the previous line
» # And join that pair with a newline delimiter
] # Close both the if-statement and loop
) # Wrap all values on the stack into a list
.¡ # Group all strings by:
# # Split the string on spaces
н # And only leave the first part (the tag)
# (after which the result is output implicitly)
AWK-F] , 122 123 113 Bytes
Es wurde ein Byte hinzugefügt, um einen Fehler zu beheben, auf den water_ghosts freundlicherweise hingewiesen hat .
10 Bytes dank Giuseppe gespart !!!
/^\[\w+\]/{a[l=$1][i++]=$2;next}{a[l][i++]=$0}END{for(k in a){print"\n",substr(k,2);for(j in a[k])print a[k][j]}}
Probieren Sie es online aus!
SimpleTemplate, 142 Bytes
Nun, das war nicht zu schwer.
Diese Antwort ist eine leicht modifizierte Version von: De-Interleave-Protokollzeilen
{@callexplode intoL EOL,argv.0}{@eachL}{@if_ matches"@^(\[\w+\]) ?(.+)$@"M}{@setX"#{M.1} "}{@set_ M.2}{@/}{@setS.[X]S.[X],X,_,EOL}{@/}{@echoS}
Ungolfed:
Da dies nicht lesbar ist, finden Sie unten eine lesbare Version:
{@call explode into lines EOL, argv.0}
{@set storage null}
{@each lines as line}
{@if line matches "@^(\[\w+\]) ?(.+)$@" match}
{@set last "#{match.1} "}
{@set line match.2}
{@/}
{@set storage.[last] storage.[last], last, line, EOL}
{@/}
{@echo storage}
Änderungen:
Einige Änderungen mussten vorgenommen werden, um mit den neuen Anforderungen ordnungsgemäß zu funktionieren. Unten finden Sie eine Kopie der verknüpften Antwort:
{@call explode into lines EOL, argv.0}
{@set storage null}
{@each lines as line}
{@if line matches "@^(\[.*\])@" match}
{@set storage.[match.1] storage.[match.1], line, EOL}
{@/}
{@/}
{@echo storage}
Unten finden Sie eine vollständige Liste der Änderungen:
- Der reguläre Ausdruck wurde geändert, um dem verbleibenden Inhalt zu entsprechen, ohne Leerzeichen, falls vorhanden. (Zitat: "Nach einem Tag wird immer mindestens ein Leerzeichen ohne Leerzeichen stehen.")
- Speichert die "App" mit einem zusätzlichen Leerzeichen zur späteren Verwendung und zum Normalisieren der Zeilen (die möglicherweise direkt nach dem "Tag" ein Leerzeichen haben oder nicht).
- Speichert den verbleibenden Inhalt ohne das erste Leerzeichen in der Variablen
line(_für die Golfversion) - Fügt das "Tag" vor der
lineVariablen hinzu, die früher Teil derlineVariablen war.
Wie Sie sehen können, sind die Änderungen nicht so bedeutend. Verschieben Sie Code, fügen Sie zusätzlichen Speicherplatz hinzu und fügen Sie einer Ausgabe eine Variable hinzu.
Sie können dies anprobieren: http://sandbox.onlinephpfunctions.com/code/eb5380ba1826530087fd92fa71d709c0b2d6de39
Scala, 127 Bytes
l=>((("",List[(String,String)]())/:l){case((p,m),s"[$t] $b")=>(t,(t,b)::m)case((p,m),b)=>(p,(p,b)::m)})._2.groupMap(_._1)(_._2)
Versuchen Sie es in Scastie (funktioniert nicht in TIO)
Wow, das ist lang.