Linie dziennika bez przeplotu: TRYB TWARDY
W obliczu pewnych godnych pożałowania zasad, które zmieniły oryginał w chwalebne zadanie sortowania, publikuję trudniejszy wariant. Krzycz do Luisa Mendo za sugestię, jak ulepszyć oryginalne wyzwanie.
Odziedziczyłeś serwer, na którym działa kilka aplikacji, z których wszystkie zapisują się w tym samym dzienniku.
Twoim zadaniem jest usunięcie przeplotu wierszy pliku dziennika według źródła. Na szczęście dla ciebie osoba, która napisała wszystkie aplikacje, była na tyle miła, że zostawiła tagi wskazujące ich źródło.
Dzienniki
Każda linia będzie wyglądać mniej więcej tak:
[app_name] Something horrible happened!
- Tagi aplikacji są zawsze w nawiasach kwadratowych i zawierają tylko znaki alfanumeryczne i podkreślenia.
- Tagi aplikacji są niepuste
- Później w dowolnym wierszu mogą znajdować się inne nawiasy kwadratowe, ale żaden z nich nie utworzy prawidłowego tagu.
- Po tagu zawsze będzie co najmniej jeden znak niebędący spacją.
- Dziennik jako całość może być pusty.
- Nie ma ograniczeń co do liczby unikalnych tagów aplikacji, które będą znajdować się w pliku.
W niektórych przypadkach może brakować tagu aplikacji. W takim przypadku wiersz dziennika należy do ostatnio zarejestrowanej aplikacji.
- Pierwsza linia dziennika zawsze zaczyna się od tagu aplikacji
- Linia zaczynająca się od
[niekoniecznie jest oznaczona. Jeśli między początkowymi nawiasami kwadratowymi znajduje się nieprawidłowy znak lub nie], wiersz nie jest oznaczony. - W dzienniku nie pojawiają się puste linie
Oczekiwany wynik
Powinieneś wyprowadzić kilka w pełni oddzielnych dzienników z tagami aplikacji usuniętymi z każdego wiersza dziennika, w którym były obecne. Nie musisz zachowywać początkowych białych znaków w żadnym wierszu dziennika.
Dzienniki wyjściowe muszą mieć jakiś rodzaj odwzorowania klucz-wartość lub rozsądny odpowiednik. Niewyczerpująca lista prawidłowych formatów wyjściowych:
- Plik o nazwie zgodnej z tagiem aplikacji dla każdej aplikacji
- Możesz założyć, że w tym przypadku pliki wyjściowe nie istnieją już w katalogu wyjściowym.
- Słownik / mapa / hash / cokolwiek, co używa tagów aplikacji jako kluczy i ciągów wierszy dziennika oddzielonych znakiem nowego wiersza jako wartości.
- Długi połączony ciąg oddzielony pustymi wierszami i poprzedzony tagami aplikacji
- Lista list [klucz, wartość]
- Ciąg JSON z tagami aplikacji jako kluczami i tablicami wierszy dziennika jako wartościami
- Dokument Markdown z tagami aplikacji jako nagłówkami i początkowymi znakami
#dowolnego wiersza poprzedzonymi ukośnikami odwrotnymi. - Funkcja JavaScript, która przyjmuje ciąg znaków jako dane wejściowe i wyprowadza powiązany dziennik jako ciąg rozdzielany znakami nowej linii.
Zasadniczo, jeśli nie możesz stwierdzić, z której aplikacji pochodzą wiersze dziennika, dane wyjściowe są nieprawidłowe.
Przykład
Cały dziennik może wyglądać następująco:
[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
Który powinien wyświetlić trzy różne dzienniki:
pogoda:
Current temp: 83F
40% chance of rain detected
[ I have a lovely bunch of coconuts
barman:
Fish enters bar
Fish orders beer
Fish leaves bar
Giełda Papierów Wartościowych:
PI +3.14
[PI announced merger with E]
Nie otrzymujesz z wyprzedzeniem nazw tagów aplikacji. Musisz je określić tylko poprzez analizę pliku dziennika.
Zasady i punktacja
- To jest code-golf , więc najkrótszy kod wygrywa.
- Obowiązują standardowe zasady i luki
- Użyj dowolnego wygodnego formatu we / wy, pod warunkiem, że każdy wiersz wejściowy jest reprezentowany jako ciąg, a nie wstępnie przeanalizowany znacznik + wiadomość. Parsowanie jest częścią tego wyzwania .
- Wiersze dziennika wyjściowego dla każdej aplikacji muszą pojawiać się w tej samej kolejności, w jakiej były w oryginalnym dzienniku.
- Możesz założyć, że dziennik wejściowy zawiera tylko znaki ASCII.
Odpowiedzi
Python 3,8 , 95 bajtów
import re
lambda x:[((t:=re.match(r'\[(\w*)\]',s)or t)[1],s.split(t[0])[-1].strip())for s in x]
Wypróbuj online!
(Rozszerzony przykład TIO z wejściem)
Wyjaśnienie:
:=Operator wymaga Pythona 3.8 . Pobiera listę ciągów jako dane wejściowe i wyświetla listę (tag, body)krotek. Najpierw używa dopasowania Regex, aby pobrać tag:
t:=re.match(r'\[(\w*)\]',s)or t)
Dopasowuje każdą początkową sekwencję znaków wyrazu (alfanumeryczne + podkreślenie) ujętą w nawiasy kwadratowe, ze słowami jako grupą przechwytywania. Jeśli ciąg nie pasuje do tego wyrażenia regularnego, tbędzie matchobiektem z dwoma elementami: pełnym dopasowaniem i grupą. Na przykład, jeśli ciąg to [tag] body, matchbędzie miał elementy [tag]i tag.
Jeśli ciąg nie pasuje do tego wyrażenia regularnego, re.match()zwraca wartość Brak. Kod staje się t = None or t, czyli po prostu t = t, więc znacznik zachowuje wartość z poprzedniego wiersza. Gdyby pierwsza linia nie była zgodna, spowodowałoby to błąd, ale nie musimy się tym martwić!
Następnie kod konstruuje krotkę t[1], s.split(t[0])[-1].strip(), gdzie t[1]jest grupą przechwytywania (tag bez nawiasów kwadratowych) i t[0]jest znacznikiem z nawiasami kwadratowymi. Rozdzielenie ciągu na cały znacznik izoluje treść, niezależnie od tego, czy znacznik faktycznie istnieje w ciągu.
Retina 0.8.2 , 95 bajtów
+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1
O$`(\w+).* $1
¶
¶¶
rm`(?<=^\1.*¶)¶(.\w+].)
(?<=(^|¶¶).\w+]).
¶
Wypróbuj online! Wyjaśnienie:
+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1
Oznacz wszystkie nieoznakowane linie.
O$`(\w+).* $1
Sortuj wiersze, wzięte z mojej odpowiedzi na oryginalne wyzwanie.
¶
¶¶
Podwój linie.
rm`(?<=^\1.*¶)¶(.\w+].)
Usuń zduplikowane tagi i pusty wiersz przed nimi. Oznacza to, że jedyne puste wiersze to te, które ograniczają oddzielne tagi.
(?<=(^|¶¶).\w+]).
¶
Przenieś znacznik do osobnej linii.
perl -Mfeature = powiedz -n,
47
46 bajtów
(Zapisano jeden bajt dzięki uprzejmości @Dom Hastings)
$;=$1 if s/^\[(\w+)\] +//;$;{$;}.=$_}{say for%;
Wypróbuj online!
Jak to działa?
Po pierwsze, efekt -nprzełączenia. Powoduje to, że Perl zawija program w pętlę, która odczytuje dane wejściowe i wykonuje treść każdej linii. Ale robi to w bardzo nieskomplikowany sposób, zawija ciało w pętlę przed wykonaniem jakiejkolwiek analizy, w następujący sposób:
perl -ne 'TEXT'
zostaje zamieniony w
LINE: while (defined($_ = readline ARGV)) {
TEXT;
}
Ale to oznacza, że jeśli jesteś TEXTw formie LOOP_BODY}{FINAL_STATEMENT, kończysz z programem:
LINE: while (defined($_ = readline ARGV)) {
LOOP_BODY
}
{
FINAL_STATEMENT;
}
Używamy tej sztuczki tylko po to, aby zaoszczędzić kilka bajtów w ENDbloku.
W samym programie używamy dwóch zmiennych do prowadzenia księgowości. $;będzie zawierał bieżący tag, aw haszu %;śledzimy linie dla każdego tagu. Teraz dla każdego wiersza wejścia sprawdzamy, czy zaczyna się od znacznika, a jeśli tak, to usuwamy go z wiersza i zapamiętujemy znacznik:
$; = $1 if # Remember the tag if,
s/^\[(\w+)\] +//; # we can strip of a tag
Następnie konkatenujemy bieżącą linię (pozbawioną znacznika, jeśli jest obecna) do zestawu linii już zebranych dla tego znacznika - jeśli nie ma takich linii, skutecznie łączymy ją z pustym ciągiem:
$;{$;}.=$_ # Remember the current line
Na koniec, po przeczytaniu wszystkich linii, drukujemy hash. Perl wygodnie spłaszcza hash do prostej listy, jeśli traktujesz ją jako listę, naprzemiennie klucze i wartości. To daje nam wynik, w którym każda sekcja jest oddzielona znakiem nowej linii i zakończona tagiem.
say for%; # Print the flattened hash
05AB1E , 22 bajty
vyD']¡н¦DžjÃÊi‚»]).¡#н
Wejście jako lista linii, wyjście jako lista list ciągów wieloliniowych.
Wypróbuj online (ładnie wydrukowany; możesz usunąć stopkę, aby zobaczyć rzeczywisty wynik).
Wyjaśnienie:
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 bajtów
Dodano bajt, aby naprawić błąd uprzejmy wskazany przez water_ghosts .
Zapisano 10 bajtów dzięki Giuseppe !!!
/^\[\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]}}
Wypróbuj online!
SimpleTemplate, 142 bajty
Cóż, to nie było zbyt trudne.
Ta odpowiedź to nieco zmodyfikowana wersja: De-przeplot wierszy dziennika
{@callexplode intoL EOL,argv.0}{@eachL}{@if_ matches"@^(\[\w+\]) ?(.+)$@"M}{@setX"#{M.1} "}{@set_ M.2}{@/}{@setS.[X]S.[X],X,_,EOL}{@/}{@echoS}
Ungolfed:
Ponieważ jest to dość nieczytelne, poniżej znajduje się czytelna wersja:
{@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}
Zmiany:
Trzeba było wprowadzić pewne zmiany, aby działały poprawnie, z nowymi wymaganiami. Poniżej znajduje się kopia połączonej odpowiedzi:
{@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}
Poniżej pełna lista zmian:
- Wyrażenie regularne zostało zmienione, aby pasowało do pozostałej treści, bez spacji, jeśli występuje. (Cytując: „Po tagu zawsze będzie co najmniej jeden znak niebędący spacją”).
- Przechowuje „aplikację” z dodatkową spacją do późniejszego wykorzystania i do znormalizowania linii (które mogą mieć spację zaraz po „tagu” lub nie).
- Przechowuje pozostałą zawartość, bez pierwszej spacji, w zmiennej
line(_dla wersji z grą w golfa) - Dodaje „tag” przed
linezmienną, która wcześniej była częściąlinezmiennej.
Jak widać, zmiany nie są aż tak znaczące. Przenieś kod, dodaj dodatkową przestrzeń, dodaj zmienną do wyjścia.
Możesz spróbować tego na: http://sandbox.onlinephpfunctions.com/code/eb5380ba1826530087fd92fa71d709c0b2d6de39
Scala, 127 bajtów
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)
Wypróbuj w Scastie (nie działa w TIO)
Wow, to długo.