Desintercalar linhas de registro: MODO HARD

Aug 24 2020

Diante de algumas regras lamentáveis ​​que transformaram o original em uma tarefa de classificação glorificada, estou postando uma variante mais desafiadora. Grite para Luis Mendo pela sugestão de como melhorar o desafio original.


Você herdou um servidor que executa vários aplicativos que geram o mesmo log.

Sua tarefa é desintercalar as linhas do arquivo de log por origem. Felizmente para você, a pessoa que escreveu todos os aplicativos foi boa o suficiente para deixar marcas indicando sua origem.

Histórico

Cada linha será parecida com isto:

[app_name] Something horrible happened!
  • As tags de aplicativo estão sempre entre colchetes e conterão apenas caracteres alfanuméricos e sublinhados.
  • As tags do aplicativo não estão vazias
  • Pode haver outros colchetes posteriormente em qualquer linha, mas nenhum formará uma tag válida.
  • Sempre haverá pelo menos um caractere sem espaço após uma tag.
  • O log como um todo pode estar vazio.
  • Não há limite de quantas tags de aplicativo exclusivas estarão presentes no arquivo.

Em alguns casos, uma tag de aplicativo pode estar faltando. Quando for esse o caso, a linha de registro pertence ao aplicativo registrado mais recentemente.

  • A primeira linha do log sempre começará com uma tag de aplicativo
  • Uma linha que começa com [não é necessariamente marcada. Se houver um caractere inválido entre os colchetes iniciais ou não ], a linha não será marcada.
  • Nenhuma linha vazia aparece no log

Saída Esperada

Você deve gerar vários logs totalmente separados com as tags de aplicativo removidas de cada linha de log onde estavam presentes. Você não precisa preservar o espaço em branco inicial em nenhuma linha de log.

Os registros de saída devem estar em algum tipo de mapeamento de valor-chave ou equivalente razoável. Uma lista não exaustiva de formatos de saída válidos:

  • Um arquivo com o nome da tag de cada aplicativo
    • Você pode assumir que os arquivos de saída ainda não existem no diretório de saída neste caso.
  • Um dicionário / mapa / hash / qualquer coisa que usa tags de aplicativos como chaves e uma string separada por nova linha das linhas de log como valores.
  • Uma longa string concatenada separada por linhas em branco e precedida por tags de aplicativo
  • Uma lista de listas de [chave, valor]
  • Uma string JSON com tags de aplicativo como chaves e matrizes de linhas de registro como valores
  • Um documento Markdown com tags de aplicativos como cabeçalhos e #s iniciais de qualquer linha com barras invertidas.
  • Uma função Javascript que recebe uma string como entrada e produz o log associado como uma string separada por nova linha.

Basicamente, se você não souber de qual aplicativo as linhas de log vieram, a saída é inválida.

Exemplo

Um registro inteiro pode ter a seguinte aparência:

[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

Que deve gerar três registros diferentes:

clima:

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

mercado de ações:

PI +3.14
[PI announced merger with E]

Os nomes das tags de aplicativo não são fornecidos a você com antecedência. Você deve determiná-los apenas analisando o arquivo de log.

Regras e pontuação

  • Este é o código de golfe , então o código mais curto vence.
  • Regras padrão e brechas se aplicam
  • Use qualquer formato IO conveniente, desde que cada linha de entrada seja representada como uma string, não uma tag + mensagem pré-analisada. A análise é parte desse desafio .
  • As linhas de log de saída para cada aplicativo devem aparecer na mesma ordem em que estavam no log original.
  • Você pode presumir que o registro de entrada contém apenas caracteres ASCII.

Respostas

2 water_ghosts Aug 24 2020 at 21:39

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]

Experimente online!

(Exemplo TIO expandido com entrada)

Explicação:

Python 3.8 é necessário para o :=operador. Isso pega uma lista de strings como entrada e produz uma lista de (tag, body)tuplas. Primeiro, ele usa uma correspondência Regex para obter a tag:

t:=re.match(r'\[(\w*)\]',s)or t)

Isso corresponde a qualquer sequência inicial de caracteres de palavras (alfanumérico + sublinhado) entre colchetes, com as palavras como um grupo de captura. Se a string corresponder a esta regex, tserá um matchobjeto com dois elementos: a correspondência completa e o grupo. Por exemplo, se a string for [tag] body, o matchterá os elementos [tag]e tag.

Se a string não corresponder a esta regex, re.match()retornará Nenhum. O código se torna t = None or t, o que é justo t = t, então a tag mantém o valor da linha anterior. Se a primeira linha não tivesse uma correspondência, isso causaria um erro, mas não precisamos nos preocupar com isso!

O código então constrói a tupla t[1], s.split(t[0])[-1].strip(), onde t[1]é o grupo de captura (a marca sem colchetes) e t[0]é a marca com colchetes. A divisão da string na tag completa isola o corpo, quer a tag exista ou não na string.

2 Neil Aug 24 2020 at 22:32

Retina 0.8.2 , 95 bytes

+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1
O$`(\w+).*
$1
¶
¶¶
rm`(?<=^\1.*¶)¶(.\w+].)

(?<=(^|¶¶).\w+]).
¶

Experimente online! Explicação:

+m`^(\[\w+] ).*¶(?!\[\w+])
$&$1

Identifique todas as linhas não marcadas.

O$`(\w+).*
$1

Classifique as linhas, tiradas da minha resposta ao desafio original.

¶
¶¶

Dê espaço duplo nas linhas.

rm`(?<=^\1.*¶)¶(.\w+].)

Remova as tags duplicadas e a linha vazia antes delas. Isso significa que as únicas linhas vazias restantes são aquelas que delimitam as tags separadas.

(?<=(^|¶¶).\w+]).
¶

Mova a etiqueta para sua própria linha.

2 Abigail Sep 01 2020 at 14:43

perl -Mfeature = diga -n, 47 46 bytes

(Um byte salvo, cortesia de @Dom Hastings)

$;=$1 if s/^\[(\w+)\] +//;$;{$;}.=$_}{say for%;

Experimente online!

Como é que isso funciona?

Primeiro, o efeito da -ntroca. Isso faz com que o Perl envolva o programa em um loop, que lê a entrada e executa o corpo de cada linha. Mas ele faz isso de uma maneira muito simples, ele envolve o corpo no loop antes de fazer qualquer análise, da seguinte maneira:

perl -ne 'TEXT'

se transforma em

LINE: while (defined($_ = readline ARGV)) {
    TEXT;
}

Mas isso significa que se você TEXTestiver na forma LOOP_BODY}{FINAL_STATEMENT, você terminará com o programa:

LINE: while (defined($_ = readline ARGV)) {
    LOOP_BODY
}
{
    FINAL_STATEMENT;
}

Estamos usando esse truque apenas para economizar alguns bytes em um ENDbloco.

No próprio programa, estamos usando duas variáveis ​​para fazer nossa contabilidade. $;conterá a tag atual e, no hash %;, rastreamos as linhas de cada tag. Agora, para cada linha da entrada, verificamos se ela começa com uma tag e, em caso afirmativo, retiramos da linha e lembramos da tag:

$; = $1 if          # Remember the tag if,
s/^\[(\w+)\] +//;   # we can strip of a tag

Em seguida, concatenamos a linha atual (retirada de uma tag, se presente) ao conjunto de linhas já coletadas para essa tag - se não houver tais linhas, nós efetivamente concatenamos com a string vazia:

$;{$;}.=$_   # Remember the current line

Finalmente, depois de ler todas as linhas, imprimimos o hash. Perl convenientemente nivela um hash em uma lista simples se você tratá-lo como uma lista, alternando as chaves e valores. Isso nos dá uma saída em que cada seção é separada por uma nova linha e é encabeçada pela tag.

say for%;    # Print the flattened hash
1 KevinCruijssen Aug 24 2020 at 17:18

05AB1E , 22 bytes

vyD']¡н¦DžjÃÊi‚»]).¡#н

Entrada como uma lista de linhas, saída como uma lista de listas de strings multilinhas.

Experimente online (bem impresso; sinta-se à vontade para remover o rodapé para ver o resultado real).

Explicação:

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)
1 Noodle9 Aug 24 2020 at 18:21

AWK-F] , 122 123 113 bytes

Adicionado um byte para corrigir um bug gentilmente apontado por water_ghosts .

Economizei 10 bytes graças a 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]}}

Experimente online!

1 IsmaelMiguel Aug 25 2020 at 10:23

SimpleTemplate, 142 bytes

Bem, isso não foi muito difícil.

Esta resposta é uma versão ligeiramente modificada de: Desintercalar linhas de registro

{@callexplode intoL EOL,argv.0}{@eachL}{@if_ matches"@^(\[\w+\]) ?(.+)$@"M}{@setX"#{M.1} "}{@set_ M.2}{@/}{@setS.[X]S.[X],X,_,EOL}{@/}{@echoS}

Ungolfed:

Uma vez que isso é bastante ilegível, abaixo está uma versão legível:

{@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}

Alterar:

Algumas mudanças tiveram que ser feitas para funcionar corretamente, com os novos requisitos. Abaixo está uma cópia da resposta vinculada:

{@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}

Abaixo está uma lista completa das mudanças:

  • A expressão regular foi alterada para corresponder ao conteúdo restante, sem um espaço, se presente. (Citando: "Sempre haverá pelo menos um caractere sem espaço após uma tag.")
  • Armazena o "app" com um espaço extra, para uso posterior e para normalização das linhas (que podem ou não ter um espaço logo após a "tag").
  • Armazena o conteúdo restante, sem o primeiro espaço, na variável line( _para a versão com golfe)
  • Adiciona a "tag" antes da linevariável, que costumava fazer parte da linevariável.

Como você pode ver, as mudanças não são tão significativas. Mova o código, adicione espaço extra, adicione uma variável a uma saída.


Você pode tentar isso em: http://sandbox.onlinephpfunctions.com/code/eb5380ba1826530087fd92fa71d709c0b2d6de39

user Aug 25 2020 at 00:51

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)

Experimente no Scastie (não funciona no TIO)

Uau, isso é longo.