18
\$\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\$
0

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.

\$\endgroup\$
0
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!

\$\endgroup\$
4
  • \$\begingroup\$ Why do you need the \\ ? \$\endgroup\$ Commented Aug 18, 2020 at 13:46
  • \$\begingroup\$ Nice. Did you use readlines() for a particular reason? It seems to be longer than function. \$\endgroup\$ Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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

\$\endgroup\$
1
  • 1
    \$\begingroup\$ @JonathanAllan Confirmed now \$\endgroup\$
    – isaacg
    Commented Aug 19, 2020 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.

\$\endgroup\$
5
  • \$\begingroup\$ 44 bytes \$\endgroup\$
    – Noodle9
    Commented Aug 18, 2020 at 0:21
  • \$\begingroup\$ @Noodle9 We can't yet assume there will be a space but thanks! \$\endgroup\$ Commented Aug 18, 2020 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
    Commented Aug 18, 2020 at 0:28
  • \$\begingroup\$ Yeah there won't be spaces in the brackets, but we'd need to guarantee one directly afterwards. \$\endgroup\$ Commented Aug 18, 2020 at 0:30
  • \$\begingroup\$ Ah! Was reading the examples as gospel. T_T \$\endgroup\$
    – Noodle9
    Commented Aug 18, 2020 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

\$\endgroup\$
0
3
\$\begingroup\$

vim, 13 11 bytes

:sor/\w\+/r

bugfix and byte save thanks to @Dingus!

Try it online!

\$\endgroup\$
0
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.

\$\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!

\$\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.

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

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

Japt, 4 bytes

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

ü_¸g

Try it

\$\endgroup\$
0
1
\$\begingroup\$

Io, 73 bytes

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

Try it online!

\$\endgroup\$
2
  • \$\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
    Commented Aug 18, 2020 at 11:35
  • \$\begingroup\$ @Laikoni I'm pretty sure that the order is correct now. Try it online! \$\endgroup\$
    – user96495
    Commented Aug 18, 2020 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

\$\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!

\$\endgroup\$
2
  • \$\begingroup\$ 122 chars if you don't use try/except and input(). Returns a dict, though \$\endgroup\$
    – user
    Commented Aug 17, 2020 at 20:56
  • 1
    \$\begingroup\$ 127 bytes if you prefer your original format. \$\endgroup\$
    – user96495
    Commented Aug 18, 2020 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
\$\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.

\$\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

\$\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.

\$\endgroup\$
3
  • \$\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\$ Commented Aug 18, 2020 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\$ Commented Aug 18, 2020 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\$
    – bit
    Commented Aug 19, 2020 at 20:07
1
\$\begingroup\$

Haskell, 37 bytes

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

Try it online!

\$\endgroup\$
0
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

\$\endgroup\$
2
  • \$\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
    Commented Aug 20, 2020 at 12:12
  • \$\begingroup\$ @madlaina You're right, thanks for the tip. \$\endgroup\$
    – corvus_192
    Commented Aug 27, 2020 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!

\$\endgroup\$

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