Task: Sort an outline at every level




Go: package main

import (
    "fmt"
    "math"
    "sort"
    "strings"
)

func sortedOutline(originalOutline []string, ascending bool) {
    outline := make([]string, len(originalOutline))
    copy(outline, originalOutline) 
    indent := ""
    del := "\x7f"
    sep := "\x00"
    var messages []string
    if strings.TrimLeft(outline[0], " \t") != outline[0] {
        fmt.Println("    outline structure is unclear")
        return
    }
    for i := 1; i < len(outline); i++ {
        line := outline[i]
        lc := len(line)
        if strings.HasPrefix(line, "  ") || strings.HasPrefix(line, " \t") || line[0] == '\t' {
            lc2 := len(strings.TrimLeft(line, " \t"))
            currIndent := line[0 : lc-lc2]
            if indent == "" {
                indent = currIndent
            } else {
                correctionNeeded := false
                if (strings.ContainsRune(currIndent, '\t') && !strings.ContainsRune(indent, '\t')) ||
                    (!strings.ContainsRune(currIndent, '\t') && strings.ContainsRune(indent, '\t')) {
                    m := fmt.Sprintf("corrected inconsistent whitespace use at line %q", line)
                    messages = append(messages, indent+m)
                    correctionNeeded = true
                } else if len(currIndent)%len(indent) != 0 {
                    m := fmt.Sprintf("corrected inconsistent indent width at line %q", line)
                    messages = append(messages, indent+m)
                    correctionNeeded = true
                }
                if correctionNeeded {
                    mult := int(math.Round(float64(len(currIndent)) / float64(len(indent))))
                    outline[i] = strings.Repeat(indent, mult) + line[lc-lc2:]
                }
            }
        }
    }
    levels := make([]int, len(outline))
    levels[0] = 1
    margin := ""
    for level := 1; ; level++ {
        allPos := true
        for i := 1; i < len(levels); i++ {
            if levels[i] == 0 {
                allPos = false
                break
            }
        }
        if allPos {
            break
        }
        mc := len(margin)
        for i := 1; i < len(outline); i++ {
            if levels[i] == 0 {
                line := outline[i]
                if strings.HasPrefix(line, margin) && line[mc] != ' ' && line[mc] != '\t' {
                    levels[i] = level
                }
            }
        }
        margin += indent
    }
    lines := make([]string, len(outline))
    lines[0] = outline[0]
    var nodes []string
    for i := 1; i < len(outline); i++ {
        if levels[i] > levels[i-1] {
            if len(nodes) == 0 {
                nodes = append(nodes, outline[i-1])
            } else {
                nodes = append(nodes, sep+outline[i-1])
            }
        } else if levels[i] < levels[i-1] {
            j := levels[i-1] - levels[i]
            nodes = nodes[0 : len(nodes)-j]
        }
        if len(nodes) > 0 {
            lines[i] = strings.Join(nodes, "") + sep + outline[i]
        } else {
            lines[i] = outline[i]
        }
    }
    if ascending {
        sort.Strings(lines)
    } else {
        maxLen := len(lines[0])
        for i := 1; i < len(lines); i++ {
            if len(lines[i]) > maxLen {
                maxLen = len(lines[i])
            }
        }
        for i := 0; i < len(lines); i++ {
            lines[i] = lines[i] + strings.Repeat(del, maxLen-len(lines[i]))
        }
        sort.Sort(sort.Reverse(sort.StringSlice(lines)))
    }
    for i := 0; i < len(lines); i++ {
        s := strings.Split(lines[i], sep)
        lines[i] = s[len(s)-1]
        if !ascending {
            lines[i] = strings.TrimRight(lines[i], del)
        }
    }
    if len(messages) > 0 {
        fmt.Println(strings.Join(messages, "\n"))
        fmt.Println()
    }
    fmt.Println(strings.Join(lines, "\n"))
}

func main() {
    outline := []string{
        "zeta",
        "    beta",
        "    gamma",
        "        lambda",
        "        kappa",
        "        mu",
        "    delta",
        "alpha",
        "    theta",
        "    iota",
        "    epsilon",
    }

    outline2 := make([]string, len(outline))
    for i := 0; i < len(outline); i++ {
        outline2[i] = strings.ReplaceAll(outline[i], "    ", "\t")
    }

    outline3 := []string{
        "alpha",
        "    epsilon",
        "        iota",
        "    theta",
        "zeta",
        "    beta",
        "    delta",
        "    gamma",
        "    \t   kappa", 
        "        lambda",
        "        mu",
    }

    outline4 := []string{
        "zeta",
        "    beta",
        "   gamma",
        "        lambda",
        "         kappa",
        "        mu",
        "    delta",
        "alpha",
        "    theta",
        "    iota",
        "    epsilon",
    }

    fmt.Println("Four space indented outline, ascending sort:")
    sortedOutline(outline, true)

    fmt.Println("\nFour space indented outline, descending sort:")
    sortedOutline(outline, false)

    fmt.Println("\nTab indented outline, ascending sort:")
    sortedOutline(outline2, true)

    fmt.Println("\nTab indented outline, descending sort:")
    sortedOutline(outline2, false)

    fmt.Println("\nFirst unspecified outline, ascending sort:")
    sortedOutline(outline3, true)

    fmt.Println("\nFirst unspecified outline, descending sort:")
    sortedOutline(outline3, false)

    fmt.Println("\nSecond unspecified outline, ascending sort:")
    sortedOutline(outline4, true)

    fmt.Println("\nSecond unspecified outline, descending sort:")
    sortedOutline(outline4, false)
}




Python: 


from itertools import chain, product, takewhile, tee
from functools import cmp_to_key, reduce







def sortedOutline(cmp):
    
    def go(outlineText):
        indentTuples = indentTextPairs(
            outlineText.splitlines()
        )
        return bindLR(
            minimumIndent(enumerate(indentTuples))
        )(lambda unitIndent: Right(
            outlineFromForest(
                unitIndent,
                nest(foldTree(
                    lambda x: lambda xs: Node(x)(
                        sorted(xs, key=cmp_to_key(cmp))
                    )
                )(Node('')(
                    forestFromIndentLevels(
                        indentLevelsFromLines(
                            unitIndent
                        )(indentTuples)
                    )
                )))
            )
        ))
    return go




def main():
    

    ascending = comparing(root)
    descending = flip(ascending)

    spacedOutline = 

    tabbedOutline = 

    confusedOutline = 

    raggedOutline = 

    def displaySort(kcmp):
        
        k, cmp = kcmp
        return [
            tested(cmp, k, label)(
                outline
            ) for (label, outline) in [
                ('4-space indented', spacedOutline),
                ('tab indented', tabbedOutline),
                ('Unknown 1', confusedOutline),
                ('Unknown 2', raggedOutline)
            ]
        ]

    def tested(cmp, cmpName, outlineName):
        
        def go(outline):
            print('\n' + outlineName, cmpName + ':')
            either(print)(print)(
                sortedOutline(cmp)(outline)
            )
        return go

    
    ap([
        displaySort
    ])([
        ("(A -> Z)", ascending),
        ("(Z -> A)", descending)
    ])





def forestFromIndentLevels(tuples):
    
    def go(xs):
        if xs:
            intIndent, v = xs[0]
            firstTreeLines, rest = span(
                lambda x: intIndent < x[0]
            )(xs[1:])
            return [Node(v)(go(firstTreeLines))] + go(rest)
        else:
            return []
    return go(tuples)




def indentLevelsFromLines(indentUnit):
    
    def go(xs):
        w = len(indentUnit)
        return [
            (len(x[0]) // w, x[1])
            for x in xs
        ]
    return go



def indentTextPairs(xs):
    
    def indentAndText(s):
        pfx = list(takewhile(lambda c: c.isspace(), s))
        return (pfx, s[len(pfx):])
    return [indentAndText(x) for x in xs]



def outlineFromForest(tabString, forest):
    
    def go(indent):
        def serial(node):
            return [indent + root(node)] + list(
                concatMap(
                    go(tabString + indent)
                )(nest(node))
            )
        return serial
    return '\n'.join(
        concatMap(go(''))(forest)
    )






def minimumIndent(indexedPrefixes):
    
    (xs, ts) = tee(indexedPrefixes)
    (ys, zs) = tee(ts)

    def mindentLR(charSet):
        if list(charSet):
            def w(x):
                return len(x[1][0])

            unit = min(filter(w, ys), key=w)[1][0]
            unitWidth = len(unit)

            def widthCheck(a, ix):
                
                wx = len(ix[1][0])
                return a if (a or 0 == wx) else (
                    ix[0] if 0 != wx % unitWidth else a
                )
            oddLine = reduce(widthCheck, zs, None)
            return Left(
                'Inconsistent indentation width at line ' + (
                    str(1 + oddLine)
                )
            ) if oddLine else Right(''.join(unit))
        else:
            return Right('')

    def tabSpaceCheck(a, ics):
        
        charSet = a[0].union(set(ics[1][0]))
        return a if a[1] else (
            charSet, ics[0] if 1 < len(charSet) else None
        )

    indentCharSet, mbAnomalyLine = reduce(
        tabSpaceCheck, xs, (set([]), None)
    )
    return bindLR(
        Left(
            'Mixed indent characters found in line ' + str(
                1 + mbAnomalyLine
            )
        ) if mbAnomalyLine else Right(list(indentCharSet))
    )(mindentLR)





def Left(x):
    
    return {'type': 'Either', 'Right': None, 'Left': x}



def Right(x):
    
    return {'type': 'Either', 'Left': None, 'Right': x}



def Node(v):
    
    return lambda xs: {'type': 'Tree', 'root': v, 'nest': xs}



def ap(fs):
    
    def go(xs):
        return [
            f(x) for (f, x)
            in product(fs, xs)
        ]
    return go



def bindLR(m):
    
    def go(mf):
        return (
            mf(m.get('Right')) if None is m.get('Left') else m
        )
    return go



def comparing(f):
    
    def go(x, y):
        fx = f(x)
        fy = f(y)
        return -1 if fx < fy else (1 if fx > fy else 0)
    return go



def concatMap(f):
    
    def go(xs):
        return chain.from_iterable(map(f, xs))
    return go



def either(fl):
    
    return lambda fr: lambda e: fl(e['Left']) if (
        None is e['Right']
    ) else fr(e['Right'])



def flip(f):
    
    return lambda a, b: f(b, a)



def foldTree(f):
    
    def go(node):
        return f(root(node))([
            go(x) for x in nest(node)
        ])
    return go



def nest(t):
    
    return t.get('nest')



def root(t):
    
    return t.get('root')



def span(p):
    
    def match(ab):
        b = ab[1]
        return not b or not p(b[0])

    def f(ab):
        a, b = ab
        return a + [b[0]], b[1:]

    def go(xs):
        return until(match)(f)(([], xs))
    return go



def until(p):
    
    def go(f):
        def g(x):
            v = x
            while not p(v):
                v = f(v)
            return v
        return g
    return go



if __name__ == '__main__':
    main()