17
\$\begingroup\$

You've inherited a server that runs several apps which all output to the same log.

Your task is to de-interleave the lines of the log file by source. Fortunately, each line begins with a tag that indicates which app it is from.

Logs

Each line will look something like this:

[app_name] Something horrible happened!
  • App tags are always between square brackets and will contain only alphanumeric characters and underscores.
  • All lines will have an app tag at the beginning. There will not be preceding whitespace or any other characters.
  • There will always be at least one space after the app tag
  • App tags are nonempty
  • There may be other square brackets later on any given line.
  • There may or may not be a message after the tag
  • The log may be empty
  • There is no limit to how many unique app tags will be present in the file.

Example

An entire log might look like this:

[weather] Current temp: 83F
[barkeep] Fish enters bar
[barkeep] Fish orders beer
[stockmarket] PI +3.14
[barkeep] Fish leaves bar
[weather] 40% chance of rain detected

Which should output three different logs:

[weather] Current temp: 83F
[weather] 40% chance of rain detected
[barkeep] Fish enters bar
[barkeep] Fish orders beer
[barkeep] Fish leaves bar
[stockmarket] PI +3.14

You are not given the names of the app tags ahead of time. You must determine them only by analyzing the log file.

Rules and Scoring

  • This is , so shortest code wins.
  • Standard rules and loopholes apply
  • Use any convenient IO format, provided that each input line is represented as a string, not a pre-parsed tag + message. Non-exhaustive list of allowed outputs:
    • Several files named after each tag
    • Several lists of strings
    • One concatenated list of strings containing lines grouped by tag with or without a separator (the separator must not begin with a tag)
    • Same as above, but to stdout or a file.
  • The order in which separated logs are output is irrelevant, however the log lines within each group must preserve the order they were found in the original file
\$\endgroup\$

21 Answers 21

9
\$\begingroup\$

Bash, 4 11 bytes

Added 7 bytes to fix a bug kindly pointed out by Shaggy.

sort -sk1,1

Try it online!

Performs a stable sort (the s command line argument) based on the first field (k1,1) separated by whitespace.

| improve this answer | |
\$\endgroup\$
7
\$\begingroup\$

R, 50 46 bytes

function(r)split(r,substr(r,1,regexpr("]",r)))

Try it online!

Outputs as a list with each element named with the [tag]. Each list element maintains order within its tag. Returns an empty named list named list() for empty input.

-2 bytes each thanks to Robin Ryder and Dominic van Essen!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Why do you need the \\ ? \$\endgroup\$ – Robin Ryder Aug 18 at 13:46
  • \$\begingroup\$ Nice. Did you use readlines() for a particular reason? It seems to be longer than function. \$\endgroup\$ – Dominic van Essen Aug 18 at 14:14
  • \$\begingroup\$ @RobinRyder I don't, I knew I needed it for using [ in my regex but totally missed that a loose ] doesn't have the same limitations. Thanks! \$\endgroup\$ – Giuseppe Aug 18 at 14:22
  • \$\begingroup\$ @DominicvanEssen No clue. Possibly because I was lazy and didn't want to type quotes around the test cases. Thanks for the golf! \$\endgroup\$ – Giuseppe Aug 18 at 14:23
5
\$\begingroup\$

Pyth, 3 bytes

ohc

Try it online!

The input format is a list of strings:

["[weather] Current temp: 83F","[barkeep] Fish enters bar","[barkeep] Fish orders beer","[stockmarket] PI +3.14","[barkeep] Fish leaves bar","[weather] 40% chance of rain detected"]

How the code works:

  • o: Order by

  • h: The first element of

  • c: Each string split on spaces

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ @JonathanAllan Confirmed now \$\endgroup\$ – isaacg Aug 19 at 17:10
4
\$\begingroup\$

Python, 44 bytes

lambda a:sorted(a,key=lambda l:l.split()[0])

Try it online!

Loose I/O allows us to take, and result in, a list of lines. Since we do not have to separate the groups, the problem is reduced to performing a stable sort of the lines on the prefix of each line up to the first space, split() will split at some other white-space characters too but none can be present in the application tag portion.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ 44 bytes \$\endgroup\$ – Noodle9 Aug 18 at 0:21
  • \$\begingroup\$ @Noodle9 We can't yet assume there will be a space but thanks! \$\endgroup\$ – Jonathan Allan Aug 18 at 0:25
  • \$\begingroup\$ Oh, but the OP says: App tags are always between square brackets and will contain only alphanumeric characters and underscores - so I figure there can't be spaces. \$\endgroup\$ – Noodle9 Aug 18 at 0:28
  • \$\begingroup\$ Yeah there won't be spaces in the brackets, but we'd need to guarantee one directly afterwards. \$\endgroup\$ – Jonathan Allan Aug 18 at 0:30
  • \$\begingroup\$ Ah! Was reading the examples as gospel. T_T \$\endgroup\$ – Noodle9 Aug 18 at 0:32
3
\$\begingroup\$

APL (Dyalog Extended), 10 bytes (SBCS)

Anonymous tacit prefix function. Takes a list of character lists as argument. Returns a matrix of character lists, with one log in each row.

⊢⊢⌸⍨≠⊃⍤⊆¨⊢

Try it online!

 on the argument,

 use the non-spaces to…

⊆¨ partition each list into a list of lists (removes spaces, keeps runs of non-spaces),

⊃⍤ then keep the first [of each] (i.e. the tags),

⊢⌸⍨ use those as keys to group…

 the argument

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

vim, 13 11 bytes

:sor/\w\+/r

bugfix and byte save thanks to @Dingus!

Try it online!

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Scala, 26 bytes

_.sortBy(_.split("]")(0))

Returns a List[String] with no separator in between, but it is sorted by the tag.

Try it in Scastie


Returns a Map[String,List[String]], 26 bytes

_ groupBy(_.split("]")(0))

Takes a list of strings and returns a Map[List[String]] where the keys are the tags and the values are the logs associated with that tag.

Try it in Scastie


Previous solution, 66 bytes

_ groupBy{case s"[$t]$r"=>t}map(_._2 mkString "\n")mkString "\n"*2

Try it in Scastie (for whatever reason, s doesn't work in TIO)

Each app's logs are separated by 2 newlines (I might be able to save 2 bytes if it just had to be the one newline character). The input is a list of strings, and the output is one big string.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

05AB1E, 3 bytes

Σ#¬

Input and Output is a list of logs.

Explanation:

Σ#¬
Σ          Sort by:
 #         Split (each log) by spaces
  ¬        Head (which is the tagname)

This also keeps the order of the logs, as required.

Try it online!

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Retina 0.8.2, 14 13 bytes

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

Try it online! Explanation: Since no output group separator is required, the lines are simply sorted by app tag, which is achieved by capturing the match on \w+ and specifying $1 as the sort key. Sort in Retina is stable, so that lines with the same prefix will retain their order. Edit: Saved 1 byte thanks to @FryAmTheEggman for pointing out an easier way to match the app tag. Note that although the match doesn't include the leading [, all of the lines start with [, so that doesn't affect the result of the sort.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I think based on how retina works you shouldn't need the ]. You should be able to use a greedy \w instead? \$\endgroup\$ – FryAmTheEggman Aug 17 at 23:50
  • \$\begingroup\$ @FryAmTheEggman Thanks, I just copied that approach from some other answer rather than actually reading the question carefully... \$\endgroup\$ – Neil Aug 18 at 9:47
2
\$\begingroup\$

AWK, 62 58 bytes

Saved 4 bytes thanks to Dominic van Essen!!!

{a[$1][i++]=$0}END{for(k in a)for(j in a[k])print a[k][j]}

Try it online!

Stores all lines in a 2D associative array a. The first key is the first field (separated by whitespace). So all lines that begin with the same field are stored together. The second key is an increasing integer index. The most verbose part is the END action which prints the contents of a grouped by first field in order of appearance.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ 58 bytes since no need to keep separate index for each element of a \$\endgroup\$ – Dominic van Essen Aug 18 at 12:47
  • \$\begingroup\$ @DominicvanEssen Of course, very nice - thanks! :-) \$\endgroup\$ – Noodle9 Aug 18 at 13:09
1
\$\begingroup\$

Japt, 4 bytes

Takes input as an array of lines, outputs a 2D-array.

ü_¸g

Try it

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Io, 73 bytes

method(i,i map(split first)unique map(I,i select(split first==I))flatten)

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This does not seem to guarantee "the log lines within each group must preserve the order they were found in the original file". In the Tio link, the fish leaves the bar before ordering the beer. \$\endgroup\$ – Laikoni Aug 18 at 11:35
  • \$\begingroup\$ @Laikoni I'm pretty sure that the order is correct now. Try it online! \$\endgroup\$ – user96495 Aug 18 at 13:13
1
\$\begingroup\$

Perl 6, 16 bytes

*.sort:{~m/\w+/}

Try it online!

Sorts by the first string of alphanumeric characters, which should be the app name

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Python 3, 148127 bytes

a={}
try:
 while 1:
  b=input();c=b.split("]")[0]
  if 1-(c in a):a[c]=[]
  a[c]+=[b]
except:[print(e)for k in a for e in a[k]]

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ 122 chars if you don't use try/except and input(). Returns a dict, though \$\endgroup\$ – user Aug 17 at 20:56
  • 1
    \$\begingroup\$ 127 bytes if you prefer your original format. \$\endgroup\$ – user96495 Aug 18 at 12:55
1
\$\begingroup\$

V (vim), 5 bytes

úr/?]

Note: The ? above is in-place of the unprintable byte \$\text{\x}81\$ (the "No Break Here" control character).

Try it online!

Note that this works with a lack of spaces (even one directly after the first ] bracket), with the presence of [] brackets in the log message, and with the presence of an untagged application, Try it online!

How?

úr/?]
ú     - sort by:
 r    -   with flag=r: use match (default behaviour is to use what's after the match)
  /   -     with the pattern:
   ?  -       (byte 83) a shortcut for .\{-}
                                       .     - match any character
                                        \{-} - 0 or more times matching as few times as possible
    ] -       match a literal ']' character
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

AutoHotkey, 74 bytes

Loop,Read,f
{
s:=A_LoopReadLine
FileAppend,%s%`n,% StrSplit(s,"]","[")[1]
}

Reads from a file named f and outputs into multiple files based on the tag.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

SimpleTemplate 0.84, 109 bytes

Yeah, it's pretty long, but does the job!

{@callexplode intoL EOL,argv.0}{@eachL}{@if_ matches"@^(\[.*\])@"M}{@setS.[M.1]S.[M.1],_,EOL}{@/}{@/}{@echoS}

This code generates an array with <old content>, line, <end of line>.

{@echoS} automatically flattens the array and displays it.


Ungolfed:

Yes, it's a mess, but here's a cleaner version:

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

The function explode is a standard PHP function, but accessible from my language.


You can try this on: http://sandbox.onlinephpfunctions.com/code/9c66f8bacc6315ae56e7c193170e430f9cf9d902

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

C# (.NET Core), 181 162 160 bytes

input.GroupBy(l=>l.Split()[0]).ToList().ForEach((g)=>{using(var sw = new StreamWriter(g.Key.Trim('[').Trim(']')+".log")){foreach(var v in g)sw.WriteLine(v);}});

Try it online!

C# (Visual C# Interactive Compiler), 179 bytes

i=>i.GroupBy((l)=>{return l.Split(' ')[0];}).ToList().ForEach((g)=>{using(var sw = new StreamWriter(g.Key.Trim(new char[]{'[',']'})+".log")){foreach(var v in g)sw.WriteLine(v);}})

Try it online!

I'm not sure the first solution is code gulf compliant, so the second solution uses a lambda expression.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Welcome to PPCG! Please consider adding a link to functioning version of your code on Try it Online and a brief description of how your code funtions as possible improvements to your answer. \$\endgroup\$ – Taylor Scott Aug 18 at 14:31
  • \$\begingroup\$ I think (for code golf purposes) it's sufficient to directly return the result of GroupBy. Split uses whitespace as the delimiter by default, so you don't need to specify the ' ' argument. You can also golf the whole (l)=>{return l.Split(' ')[0];} thing to l=>l.Split()[0] \$\endgroup\$ – the default. Aug 18 at 15:54
  • \$\begingroup\$ @mypronounismonicareinstate thanks for suggesting the expression lambda and the tip about white space being the default delimiter of the Split method. \$\endgroup\$ – Black Panther Aug 19 at 20:07
1
\$\begingroup\$

Haskell, 37 bytes

import Data.List
f=sortOn(head.words)

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Rust, 40 bytes

|a|a.sort_by_key(|x|x.split("]").next())

Takes a mutable reference to a slice of strings and sorts it.

Try it on the rust playground

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ The trailing semicolon should not be counted, as it is not part of the closure, but rather part of the variable declaration \$\endgroup\$ – madlaina Aug 20 at 12:12
  • \$\begingroup\$ @madlaina You're right, thanks for the tip. \$\endgroup\$ – corvus_192 Aug 27 at 9:10
0
\$\begingroup\$

Perl 5 -M5.10.0 -Msort=stable, 53 bytes

say sort{(split('\]',$a))[0]cmp(split('\]',$b))[0]}<>

Try it online!

| improve this answer | |
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.