Indentation-based Sort

36

2

Given an ordered list of same-case letter strings (a-z XOR A-Z) where each string is preceded by 0 or more space ( ) characters, output the same list but with the strings sorted at each level of indentation. Indentation depths under different parents count as distinct lists for sorting purposes.

Example

If your input is:

bdellium
  fox
  hound
  alien
aisle
  wasabi
    elf
    alien
  horseradish
    xeno
irk
wren
tsunami
djinn
      zebra

your output should be

aisle
  horseradish
    xeno
  wasabi
    alien
    elf
bdellium
  alien
  fox
  hound
djinn
      zebra
irk
tsunami
wren

If you like, think of it like a directory listing, and you need to sort the names within each directory.

Minutiae

  • An item may be indented by any number of spaces. If it is indented by the same number of spaces as the previous item it belongs in the same sort hierarchy as the previous item. If it is indented by more spaces it is the start of a new sub-hierarchy.
  • If a line is indented by fewer spaces than the line above it, it links up to the closest sub group above it with the same # or fewer spaces before it (like horseradish in the above example, which links onto the wasabi group above it because wasabi is the first item above it to not have more spaces than horseradish)
  • You must preserve the indenting level of each input item in your output
  • Tabs in the output are disallowed
  • The first line of the input will never be indented
  • Your program must handle at least one of all-uppercase and all-lowercase strings; it doesn't have to handle both.

Scoring

This is a , so the answer which uses the fewest bytes wins.

Techrocket9

Posted 2018-08-21T21:59:33.007

Reputation: 615

7Nice challenge! – Adám – 2018-08-21T22:22:52.647

1

Btw, for next time, consider using the Sandbox to iron out issues with a challenge before posting it to the main site.

– Adám – 2018-08-21T22:34:40.820

May we take input as a list of strings and a list of indentation levels? E.g. [0,2,2,2,0,2,4,4,2,4,0,0,0,0,6] and ["bdellium","fox","hound","alien","aisle","wasabi","elf","alien","horseradish","xeno","irk","wren","tsunami","djinn","zebra"]? – Adám – 2018-08-21T22:41:26.530

8@Adám No, the recursive string parsing logic required is the reason I wrote this prompt. – Techrocket9 – 2018-08-21T22:43:35.940

1By 'indenting level', do you mean the amount of spaces or where it is in relation to the previous level? – Jo King – 2018-08-21T23:35:24.753

2If the input is ['a','..b', '.c', '..d'], what should the output be? ['a','..b', '.c', '..d'] or ['a','.c','..b', '..d'] or some other thing? (I'm using '.' instead of space for visual clarity). – Chas Brown – 2018-08-22T00:03:57.787

1I approve of the BNL reference :) – Not that Charles – 2018-08-22T02:06:46.990

1@JoKing The number of spaces before the letters. Each string of the output should have the same number of spaces before it as it does in the input. – Techrocket9 – 2018-08-22T02:21:06.513

1@ChasBrown The first one, ['a','..b', '.c', '..d']. Whether or not a new subgroup forms depends only on whether or not the current line has more spaces before it than the previous line. In your example b has more spaces than a so it's grouped under a. c doesn't have more spaces than b so it ends up in the same sorting group as b. d has more spaces than c so it gets its own subgroup under c. I suppose the implicit consequence of this is that if you have fewer spaces than the line above you that line's subgroup ends and you go up until you find a subgroup you match or exceed the spacing of. – Techrocket9 – 2018-08-22T02:42:20.877

1This is an indentation preserving sort, not an indentation based sort. – Pureferret – 2018-08-22T14:13:51.713

1@Pureferret The grouping levels are based on the level of indentation. It's also indentation preserving, but that's a secondary characteristic. – Techrocket9 – 2018-08-22T14:21:59.697

will there be any spaces in the strings? e.g. " two words" – streetster – 2018-08-22T19:37:04.080

1do we have to handle where the first item in the sub-group is at a lower level in the heirarchy? or will the levels increase? – streetster – 2018-08-22T20:20:04.530

2@streetster strings (a-z XOR A-Z) – Adám – 2018-08-22T21:12:39.240

@streetster The way it's written right now that has to be handled. It should have no impact on the groupings though (in the above example, if fox had an additional two spaces before it fox would retain the same position in the output, just with more spaces). I don't particularly like this edge-case (since the output may not be idempotent if re-run through the program), but now that there are a number of established answers I'm reluctant to change the rules. – Techrocket9 – 2018-08-22T21:37:14.643

1I feel like the first line of the input will never be indented should apply recursively, so that the case that @ChasBrown describes should never happen – Jo King – 2018-08-22T22:57:43.557

@Techrocket9, what if alien had an extra space? Seems a few of us are asking about this case. I agree with Jo King ^^ – streetster – 2018-08-23T06:32:09.570

Answers

1

Pyth, 23 bytes

jeMtSuaG+f</Td/HdeGHQ]Y

Try it here!

Mr. Xcoder

Posted 2018-08-21T21:59:33.007

Reputation: 39 774

14

Python 2, 117 bytes

lambda S:[s[-1]for s in sorted(reduce(lambda t,s:t+[[v for v in t[-1]if v.count(' ')<s.count(' ')]+[s]],S,[[]]))[1:]]

Try it online!

Takes as input a list of strings; and outputs a list of strings, sorted as required.

The idea is to turn each element into a list which contains the "absolute path" as a list; and then let Python handle the sorting. E.g. if the input is:

[
 'a',
 ' c',
 '  d',
 ' b'
]

Then via the reduce(), we convert to a list of lists:

[
 ['a'],
 ['a',' c'],
 ['a',' c', '  d'],
 ['a',' b']
]

which gets sorted as:

[
 ['a'],
 ['a',' b']
 ['a',' c'],
 ['a',' c', '  d'],
]

and then output the last element of each list in the list-of-lists to get:

[
 'a',
 ' b'
 ' c',
 '  d',
]

Chas Brown

Posted 2018-08-21T21:59:33.007

Reputation: 8 959

Wow the solution I was about to post was 183 bytes...I suck lol – Don Thousand – 2018-08-22T00:18:47.310

4@Rushabh Mehta: My first try was around 205 bytes... then hack away! :) – Chas Brown – 2018-08-22T00:28:31.703

7

APL (Dyalog Unicode), 31 bytesSBCS

Anonymous prefix lambda, takes and returns list of strings.

{⍵[⍋{1=≢⍵:⍺⋄⍵}⍀↑' '(,⊂⍨⊣=,)¨⍵]}

Try it online!

{} "dfn"; is argument

⍵[] index the argument with the following indices:

  ' '()¨⍵ apply the following tacit function to each string with space as left argument:

   , concatenate the space to the string

   ⊣= Boolean list indicating where the space is equal to each character that

   ,⊂⍨ use that to partition (begin part where true) the concatenation of space and string

   mix list of lists of strings into matrix of strings

  {}⍀ vertical cumulative reduction by this "dfn"; and are upper and lower args:

   ≢⍵ the length of the lower string

   1= is that equal to 1? (i.e. is there nothing but the single space there?)

   :⍺ if so, return the upper argument

   ⋄⍵ else, return the lower argument

   grade up (find indices which will sort that)

Adám

Posted 2018-08-21T21:59:33.007

Reputation: 37 779

7

Retina, 47 bytes

+-1m`(?<=^\2(\S+).*?¶( *)) 
$1 
O$`
$L$&
\S+ 
 

Try it online! Note: Several lines have trailing spaces. Explanation:

+-1m`(?<=^\2(\S+).*?¶( *)) 
$1 

The first step is to insert each word into following lines at the same indentation. For example, with the lines aisle, wasabi and elf the resulting lines are aisle, aisle wasabi and aisle wasabi elf. I discovered this regex by trial and error so there may be edge cases with it.

O$`
$L$&

We can now sort the lines case-insensitively.

\S+ 
 

Delete all of the inserted words.

Neil

Posted 2018-08-21T21:59:33.007

Reputation: 95 035

4

Perl 6, 120 83 81 63 54 37 47 42 bytes

-5 bytes thanks to nwellnhof

{my@a;.sort:{@a[+.comb(' ')..*+1]=$_;~@a}}

Try it online!

This uses Chas Brown's method. An anonymous code block that takes a list of lines and returns a list of lines.

Explanation:

{                                        }  # Anonymous code block
 my@a;  # Declare a local list
      .sort # Sort the given list of lines
           :{                           }  # By converting each line to:
             @a[+.comb(' ')..*+1]=$_;      # Set the element at that indentation level onwards to that line
                                     ~@a   # And return the list coerced to a string

Jo King

Posted 2018-08-21T21:59:33.007

Reputation: 38 234

@nwellnhof Thanks for pointing that out. I think I've fixed it in the latest version – Jo King – 2018-08-22T09:57:34.790

@nwellnhof Ah well, it was shorter in a previous iteration. Thanks for the bytes off, but I had to change it slightly – Jo King – 2018-08-22T11:01:08.307

Oh, right. Actually, something like {my@a;.sort:{@a[+.comb(' ')...*>@a]=$_;~@a}} is required to support higher indentation levels. – nwellnhof – 2018-08-22T12:02:56.187

3

Clean, 112 101 bytes

import StdEnv
f=flatten
?e=[0\\' '<-e]
$[h:t]#(a,b)=span(\u= ?u> ?h)t
=sort[[h:f($a)]: $b]
$e=[]

f o$

Try it online!

Anonymous function :: [[Char]] -> [[Char]] which wraps $ :: [[Char]] -> [[[Char]]] into the right output format. $ groups the strings into "more spaces than" and "everything else afterwards", recurses over each group and sorts when they're adjoined. At each step, the list being sorted looks like:

[[head-of-group-1,child-1,child-2..],[head-of-group-2,child-1,child-2..]..]

Clean, 127 bytes

import StdEnv
$l=[x++y\\z<- ?(map(span((>)'!'))l),(x,y)<-z]
?[h:t]#(a,b)=span(\(u,_)=u>fst h)t
=sort[[h:flatten(?a)]: ?b]
?e=[]

Try it online!

Defines the function $ :: [[Char]] -> [[Char]] which separates the strings into tuples in the form (spaces, letters) which are recursively sorted by the helper function ? :: [([Char],[Char])] -> [[([Char],[Char])]].

Explained:

$ list                                  // the function $ of the list
    = [                                 // is
        spaces ++ letters               // the spaces joined with the letters
        \\ sublist <- ? (               // from each sublist in the application of ? to
            map (                       // the application of
                span ((>)'!')           // a function separating spaces and letters
            ) list                      // to every element in the list
        )
        , (spaces, letters) <- sublist  // the spaces and letters from the sublist
    ]

? [head: tail]                              // in the function ? of the head and tail of the input
    # (group, others)                       // let the current group and the others equal
        = span (                            // the result of separating until ... is false
            \(u, _) = u >                   // only elements where there are more spaces
                          fst head          // than in the head of the input
        ) tail                              // the tail of the input
    = sort [
        [head                               // prepend the head of the input to
             : flatten (?group)             // the flat application of ? to the first group
                               ]            // and prepend this to
                                : ?others   // the application of ? to the other group(s)
    ]

? empty = [] // match the empty list

Οurous

Posted 2018-08-21T21:59:33.007

Reputation: 7 916

1

JavaScript (Node.js), 114 100 92 88 bytes

x=>x.map(y=>a=a.split(/ */.exec(y)[0]||a)[0]+y,a="_").sort().map(x=>/ *\w+$/.exec(x)[0])

Try it online!

Similar approach to Chas Brown's Python answer, but using regular expressions instead.

Explanation

x => x.map(                         // 
 y => a = a.split(                  // Renders the indentation paths
  / */.exec(y)[0]                   //  Checks the indentation level
  || a                              //  If this is the top level, go to root
 )[0] + y,                          //  Appends the child to the parent
 a = "_"                            // At first the cursor is at the root
)                                   // 
.sort()                             // Sorts the indentation paths
.map(                               // 
 x => / *\w+$/.exec(x)[0]           // Extracts only the last level of the path
)                                   //

Shieru Asakoto

Posted 2018-08-21T21:59:33.007

Reputation: 4 445

0

K4, 51 bytes

Solution:

{,/(1#'r),'.z.s@'1_'r:(w_x)@<x@w:&s=&/s:+/'" "=/:x}

Example:

q)k){,/(1#'r),'.z.s@'1_'r:(w_x)@<x@w:&s=&/s:+/'" "=/:x}("bdellium";"  fox";"  hound";"  alien";"aisle";"  wasabi";"    elf";"    alien";"  horseradish";"    xeno";"irk";"wren";"tsunami";"djinn";"      zebra")
"aisle"
"  horseradish"
"    xeno"
"  wasabi"
"    alien"
"    elf"
"bdellium"
"  alien"
"  fox"
"  hound"
"djinn"
"      zebra"
"irk"
"tsunami"
"wren"

Assumptions:

a. That each hierarchy will start with the lowest level, i.e. you will not get:

bdellium
      fox
    hound
    alien

Explanation:

{,/(1#'r),'.z.s@'1_'r:(w_x)@<x@w:&s=&/s:+/'" "=/:x} / the solution
{                                                 } / lambda function, implicit x
                                           " "=/:x  / " " equal to each right (/:) x
                                        +/'         / sum up each
                                      s:            / save as s
                                    &/              / find the minimum (ie highest level)
                                  s=                / is s equal to the minimum?
                                 &                  / indices where true 
                               w:                   / save as w
                             x@                     / index into x at these indices
                            <                       / return indices to sort ascending
                           @                        / index into
                      (   )                         / do this together
                       w_x                          / cut x at indices w
                    r:                              / save as r
                 1_'                                / drop first from each r
            .z.s@                                   / apply recurse (.z.s)
          ,'                                        / join each both
    (    )                                          / do this together
     1#'r                                           / take first from each r
  ,/                                                / flatten

streetster

Posted 2018-08-21T21:59:33.007

Reputation: 3 635

0

Perl 5, 166 bytes

sub f{my$p=shift;my@r;while(@i){$i[0]=~/\S/;$c=$-[0];if($p<$c){$r[-1].=$_ for f($c)}elsif($p>$c){last}else{push@r,shift@i}}sort@r}push@i,$_ while<>;print sort@{[f 0]}

Ungolfed (sort of):

sub f {
    my $p = shift;
    my @r;
    while(@i) {
        $i[0] =~ /\S/;
        $c = $-[0];
        if($p < $c) {
            $r[-1] .= $_ for f($c)
        } elsif ($p > $c) {
            last
        } else {
            push @r, shift @i
        }
    }
    sort @r
}

push @i, $_ while <>;
print sort@{[f 0]}

It's a pretty straightforward recursive implementation. We check the indentation level by searching for the first non-space character (/\S/) and getting its index ($-[0]). Unfortunately, we actually have to declare a handful of variables that are used in the recursion, or else they'll be implicitly global and the recursion won't work correctly.

Silvio Mayolo

Posted 2018-08-21T21:59:33.007

Reputation: 1 817