17
\$\begingroup\$

Introduction

Most code-golfers here add explanations to their submissions, so it's easier to understand what's going on. Usually the codelines go on the left and the corresponding explanation to the right with some kind of separator. To make it look pretty, the separators are all on the same column. Also long explanation text is usually wrapped to the next line, so the readers don't have to scroll horizontally to read everything.

However, when you want to edit this explanation because you made some crazy golfs, you often end up spending time to make your explanation pretty again. Since this is a very repetitive task, you want to write a program for this.

The Challenge

Given several lines of code with explanation and a separator, output the nicely formatted code with explanation.

Example

Input

shM-crz1dc4."ANDBYOROF  # z = input

     rz1      # convert input to uppercase
    c   d        # split input on spaces
         c4."ANDBYOROF        # create a list of the words from a packed string which shall be ignored
   -          # filter those words out
 hM                # only take the first letter of all words
s                   # join them into one string

Output

shM-crz1dc4."ANDBYOROF     # z = input

     rz1                   # convert input to uppercase
    c   d                  # split input on spaces
         c4."ANDBYOROF     # create a list of the words from a packed string which shall be
                           # ignored
   -                       # filter those words out
 hM                        # only take the first letter of all words
s                          # join them into one string

One cookie for the first one who can find out what this code does.

The formatting algorithm

  • Find the longest code-line (excluding the explanation and the spaces between code and separator).
  • Add 5 spaces after this code-line and append the corresponding separator with explanation. This is now the reference line.
  • Adjust every other line to this reference line, so that the seperators are all in the same column.
  • Wrap all lines that are longer than 93 characters to a new line in the following way:
    • Find the last word which end is at column 93 or lower.
    • Take all words after this one and wrap them to a new line with the leading separator and the correct spacing. The space between those two words has to be deleted, so the first line ends with a word character and the second line starts with one after the separator.
    • If the resulting line is still longer than 93 characters do the same again until every line is below 94 characters.

Notes

  • A word consists of non-whitespace characters. Words are separated by a single space.
  • The word wrapping is always possible. This means that no word is so long that it would make the wrapping impossible.
  • The input will only contain printable ASCII and won't have any trailing whitespaces
  • The separator will only appear once per line.
  • While the explanation can have unlimited length, the separator and the code can only have a combined maximum length of 93 - 5 = 87 chars. The 5 chars are the spaces between code and separator. Code and separator will always be at least one character long.
  • The input may contain empty lines. Those will never contain any characters (except a newline if you take input as multiline string). Those empty lines have to be present in the output as well.
  • Every line will have some code, a separator and an explanation. Exceptions are empty lines.
  • You may take the input in any reasonable format, as long as it is not pre-processed. Make it clear in your answer which one you use.
  • Output can be a multiline string or a list of strings.

Rules

Test cases

Input format here is a list of string representing the lines and a single string for the separator. Both are separated by a comma. Output is a list of strings.

['shM-crz1dc4."ANDBYOROF  # z = input', '', '     rz1      # convert input to uppercase', '    c   d        # split input on spaces', '         c4."ANDBYOROF        # create a list of the words from a packed string which shall be ignored', '   -          # filter those words out', ' hM                # only take the first letter of all words', 's                   # join them into one string'], "# " -> ['shM-crz1dc4."ANDBYOROF     # z = input', '', '     rz1                   # convert input to uppercase', '    c   d                  # split input on spaces', '         c4."ANDBYOROF     # create a list of the words from a packed string which shall be', '                           # ignored', '   -                       # filter those words out', ' hM                        # only take the first letter of all words', 's                          # join them into one string']
['codecodecode   e#Explanation', 'sdf dsf sdf e#A Very very very very very very very very very long long long long long long long long long long long explanation and it keeps getting longer and longer', '', 'some more codee#  and some more explanation'], "e#" -> ['codecodecode       e#Explanation', 'sdf dsf sdf        e#A Very very very very very very very very very long long long long long ', '                   e#long long long long long long explanation and it keeps getting longer', '                   e#and longer', '', 'some more code     e# and some more explanation']

Happy Coding!

\$\endgroup\$
5
  • 1
    \$\begingroup\$ @Matt All seperators are always at column length of the longest code-line + 5. This also applied to lines which only contain an explanation, because they were wrapped. \$\endgroup\$
    – Denker
    Commented Mar 24, 2016 at 19:45
  • \$\begingroup\$ Oh my god I have being doing this wrong this for the last 3 hours. I was trying to wrap long code and leaving the explanations long..... Well starting over. At least now it is easier. Thanks. You worded it fine.... I'm just silly. \$\endgroup\$
    – Matt
    Commented Mar 24, 2016 at 19:47
  • \$\begingroup\$ Wrap all lines that are longer than 93 characters Does that mean the code, including leading spaces, will never be more than 87 characters long? \$\endgroup\$
    – Matt
    Commented Mar 24, 2016 at 19:54
  • \$\begingroup\$ @Matt The code and the seperator together will never be longer than 87 characters since we need 5 spaces between code and seperator and one character for the explanation. \$\endgroup\$
    – Denker
    Commented Mar 24, 2016 at 20:04
  • 1
    \$\begingroup\$ The Pyth code finds the abbreviation of any given string. I would know because that was an answer to my question. \$\endgroup\$
    – Aplet123
    Commented Mar 26, 2016 at 20:19

5 Answers 5

9
\$\begingroup\$

LiveScript, 243 236 233 228 219 225 bytes

f = (x,k,m+5)->l=(.length);x.=map(->it/"#k"=>..0-=/ +$/;m>?=5+l ..0);i=0;[..0&&..0+' '*(m- l ..0)+k+..1 for x]=>while i<(l ..),++i=>j=(s=..[i])lastIndexOf ' ' 93;(..splice i+1 0 ' '*m+k+s[j to]*'';s.=substr 0 j) if 94<l s;..[i]=s

How it works: mostly like the Java code. Start off by aliasing length (LiveScript allows to create a function from operators by using parentheses). .= is a = a.b - which we use here to map.

=> blabla .. is the Smalltalk-ish cascade construct: the left side of => is accessible as .. for the rest of the block; and will be returned. Here, it's the element split on k. Note: I'm using string interpolation, because / only means "split" with a literal string.

LS allows us to use a-=/regexp/ in this lambda as well (also works with string literals): it's just sugar for a .replace call.

Finally, the >?= is the combinatory >?-assign operator, which returns the greater of two operands.

LS has Python/Haskell-style for comprehensions, with nothing fancy in them, except the "string * times" to repeat the space long enough.

This for comprehension serves as the topic (see the block about cascades anove).

We then loop into each element of the array (the one we just built with the comprehension), and if any line is bigger than 93chars, we find the last index of , split there, and push the separated line right after this current iteration's (... So that the next iteration will split again if the line's too big).

Only last thing fancy a[j to] is a range (from j to the end), but since it uses Array methods we have to join it back to a string, which we do using overloaded *: *''.

example

s = """this is kod # Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
d # y

efgh # z"""

f = (x,k,m=5)->l=(.length);x.=map(->it/"#k"=>..0-=/ +$/;m>?=5+l ..0);i=0;[..0&&..0+' '*(m- l ..0)+k+..1 for x]=>while i<(l ..),++i=>j=(s=..[i])lastIndexOf ' ' 93;(..splice i+1 0 ' '*m+k+s[j to]*'';s.=substr 0 j) if 94<l s;..[i]=s
    
console.log (f s / '\n', '#') * \\n

output:

this is kod     # Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
                # tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
                # veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
                # commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
                # velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
                # cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
                # est laborum.
d               # y

efgh            # z
\$\endgroup\$
5
  • 1
    \$\begingroup\$ to whoever downvoted: the answer is fixed. \$\endgroup\$
    – Ven
    Commented Mar 23, 2016 at 10:00
  • 2
    \$\begingroup\$ When an explanation overflows, you need the new line(s) to align their separator characters with the rest, IIRC. \$\endgroup\$
    – Value Ink
    Commented Mar 25, 2016 at 0:36
  • \$\begingroup\$ @KevinLau well spotted, fixed! \$\endgroup\$
    – Ven
    Commented Mar 25, 2016 at 8:38
  • \$\begingroup\$ Can you update your example output as well? \$\endgroup\$
    – Value Ink
    Commented Mar 25, 2016 at 15:58
  • \$\begingroup\$ @KevinLau done. \$\endgroup\$
    – Ven
    Commented Mar 25, 2016 at 16:00
6
\$\begingroup\$

Java, 347 + 19 = 366 bytes

Requires

import java.util.*;

Thus the +19 bytes.

(c,s)->{int p=0,i=0,t;String l;for(;i<c.size();i++){l=c.get(i);l=l.replaceAll(" *"+s,s);p=Math.max(l.indexOf(s),p);c.set(i,l);}p+=5;for(i=0;i<c.size();i++){l=c.get(i);t=l.indexOf(s);while(t>-1&t<p)l=l.substring(0,t)+" "+l.substring(t++);t=93;if(l.length()>t){while(l.charAt(t)!=' ')t--;c.add(i+1,s+l.substring(t));l=l.substring(0,t);}c.set(i,l);}}

Takes in the format f.accept(List<String> code, String seperator). Formats in-place. A version that creates and returns a new List<String> would be trivial to implement but cost some bytes.

Indented + example usage:

static BiConsumer<List<String>, String> prettify = (code, seperator) -> {
    int space = 0, i=0, t;
    String line;
    for (; i<code.size(); i++) { // for each line
        line = code.get(i); // get line
        line = line.replaceAll(" *" + seperator, seperator); // strip space before seperator
        space = Math.max(line.indexOf(seperator), space); // save biggest space until seperator
        code.set(i, line); // save line
    }
    space += 5;
    for (i=0; i<code.size(); i++) { // for each line
        line = code.get(i); // get line
        t = line.indexOf(seperator); // get index of seperator
        while (t>-1&t<space) // while the seperator exists and is further left than desired
            line = line.substring(0,t) + " " + line.substring(t++); // move it right by adding a space before it
        t = 93; // get desired line length
        if (line.length()>t) { // if the line is longer than that
            while (line.charAt(t)!=' ') t--; // scan backwards for a space
            code.add(i+1, seperator + line.substring(t)); // add a line after this one with seperator and the rest of the line
                                                          // the next pass will space it correctly
            line = line.substring(0,t); // cut off this line at that point
        }
        code.set(i, line); // save edited line back to List
    }
};

public static void main(String[] args) {
    List<String> code = new ArrayList<>();
    code.add("shM-crz1dc4.\"ANDBYOROF  # z = input");
    code.add("");
    code.add("     rz1      # convert input to uppercase");
    code.add("    c   d        # split input on spaces");
    code.add("         c4.\"ANDBYOROF        # create a list of the words from a packed string which shall be ignored");
    code.add("   -          # filter those words out");
    code.add(" hM                # only take the first letter of all words");
    code.add("s                   # join them into one string");
    prettify.accept(code, "#");
    code.stream().forEach(System.out::println);
}

... I should probably run this through itself :P

\$\endgroup\$
11
  • \$\begingroup\$ If anyone can figure out why replace(" *"+s) isn't working but replaceAll(" *"+s) is I'd love to hear it -- I can't figure it out. \$\endgroup\$
    – CAD97
    Commented Mar 23, 2016 at 4:38
  • \$\begingroup\$ <badguess> replace uses strings but replaceAll uses regexes. </badguess> \$\endgroup\$ Commented Mar 23, 2016 at 4:40
  • \$\begingroup\$ @CatsAreFluffy well, you're correct! Don't know how I didn't realize that :P \$\endgroup\$
    – CAD97
    Commented Mar 23, 2016 at 4:47
  • \$\begingroup\$ Can't you remove the newline? \$\endgroup\$ Commented Mar 23, 2016 at 4:56
  • \$\begingroup\$ Well the newline can be removed because of required semi:s (which should be .s but whatever) \$\endgroup\$ Commented Mar 23, 2016 at 5:01
3
\$\begingroup\$

Ruby, 245 237 220 216 212 209 205 bytes

Anonymous function. Pretty basic approach (find max length, add 5, then do processing on each line, with recursion to deal with wrapping) and there might be another approach that saves more bytes.

I deleted the answer earlier that didn't fulfill all the requirements; I didn't want to have a half-answered code as an answer (it was getting downvotes for being incomplete too) but it should do everything the question asks for, now.

->x,d{l,S=0," "
s=->n{m,q=n.size,94-l-d.size
m>q ?(i=n.rindex(S,q)
n[0,i]+"
"+S*l+d+s[n[i+1,m]]):n}
x.map{|n|c,t=n.split d
c=(c||S).rstrip
l=[l,5+c.size].max
[c,t]}.map{|c,t|c+S*(l-c.size)+d+s[t]if t}*"
"}

Changelog:

  • Saved some bytes by leveraging some of the promises in the input, especially the promise that all non-empty lines have the separator character and an explanation.
  • Managed to golf a little more by saving the split strings from the first map call and taking out some unnecessary strip functions based on the promise that the words in the explanation always have exactly one space between them. Also, " " is assigned to a constant now since I use it so much.
  • Chained both of the map calls together by leveraging the power of higher-order functions, meaning that the first map call will set the length variable l correctly even if it's called after the declaration of the helper function s. -4 bytes.
  • Abused multiline strings to replace \n with actual newlines, plus a little trick using if over ternary operators (when join is called on an array with nil values, they become empty strings)
  • .join can apparently be replaced with a *.
\$\endgroup\$
5
  • \$\begingroup\$ I think it should be fixed now? \$\endgroup\$
    – Value Ink
    Commented Mar 23, 2016 at 20:19
  • \$\begingroup\$ how does this wrap at 94? \$\endgroup\$
    – Ven
    Commented Mar 23, 2016 at 20:26
  • \$\begingroup\$ All right, now that I had more time to work on the code, it wraps properly. \$\endgroup\$
    – Value Ink
    Commented Mar 24, 2016 at 2:26
  • \$\begingroup\$ " While the explanation can have unlimited length, the separator and the code can only have a combined maximum length of 93 - 5 = 87 chars. The 5 chars are the spaces between code and separator. Code and separator will always be at least one character long." Your code section is way past the limit, with 97 characters, so the program has undefined behavior. \$\endgroup\$
    – Value Ink
    Commented Mar 24, 2016 at 16:50
  • \$\begingroup\$ ah, well spotted, makes sense! \$\endgroup\$
    – Ven
    Commented Mar 24, 2016 at 17:01
2
\$\begingroup\$

PowerShell, 224 217 235 bytes

param($d,$s)$d=$d-split"`r`n";$p="\s+\$([char[]]$s-join"\")\s";$m=($d|%{($_-split$p)[0].Length}|sort)[-1];$d|%{$l,$c=$_-split$p;$c=if($c){"$s "+(("$c "-split"(.{1,$(87-$m)})\s"|?{$_})-join"`n$(" "*($m+5))$s ")}$l.PadRight($m+5," ")+$c}

Updated the logic to determine the max code string length. Updated to allow multiple separators that include regex meta characters.


Little Explanation

This takes in a whole newline delimited string for input.

param($d,$s)
# $d is a newline delimited string. $s is the separator.
# Take the string and turn it into a string array. Stored as $d
$d=$d-split"`r`n"
# Save a regex pattern as it is used more than once
$p="\s+\$([char[]]$s-join"\")\s"
# Get the longest string of code's length
$m=($d|%{($_-split$p)[0].Length}|sort)[-1]
# Split each line again into code and comment. Write out each line with formatted explanations based on separator column position $m
$d|%{
# Split the line
$l,$c=$_-split$p
# Build the comment string assuming there is one.
$c=if($c){"$s "+(("$c "-split"(.{1,$(87-$m)})\s"|?{$_})-join"`n$(" "*($m+5))$s ")}
# Pad the right amount of space on the code and add the comment string.
$l.PadRight($m+5," ")+$c
}

Sample Output with some Lorem Ipsum

shM-crz1dc4."ANDBYOROF     # z = input

     rz1                   # convert input to uppercase
    c   d                  # split input on spaces
         c4."ANDBYOROF     # But I must explain to you how all this mistaken idea of
                           # denouncing pleasure and praising pain was born and I will give
                           # you a complete account of the system, and expound the actual
                           # teachings of the great explorer
   -                       # filter those words out
 hM                        # only take the first letter of all words
s                          # join them into one string
\$\endgroup\$
3
  • \$\begingroup\$ @nimi Hopefully the updates now make for a better solution. \$\endgroup\$
    – Matt
    Commented Mar 25, 2016 at 1:24
  • \$\begingroup\$ @nimi Anything else you notice wrong? I apparently am having issues reading the last couple of days. \$\endgroup\$
    – Matt
    Commented Mar 25, 2016 at 18:43
  • \$\begingroup\$ No. Now have a +1. \$\endgroup\$
    – nimi
    Commented Mar 25, 2016 at 19:24
1
\$\begingroup\$

MATLAB, 270 265 262 bytes

function d=f(I,s);S=@sprintf;R=@regexprep;m=regexp(I,['\s*\',s]);L=max([m{:}])+4;a=@(x)S('%-*s%s',L,x,s);b=@(x)R(R(x,S('(.{1,%d}(\\s+|$))',93-L),S('$1\n%*s ',L+1,s)),['\n\s*\',s,' $'],'');c=R(I,['(.*?)\s*\',s,'\s*(.*$)'],'${a($1)} ${b($2)}');d=S('%s\n',c{:});end

The program accepts the input I in the form of a cell array of strings where each element of the cell array is a separate line of the input. It also accepts a second input which indicates what the comment character is (i.e. #). The function returns a multi-line string that is properly formatted.

Brief Explanation

function d = f(I,s)
    %// Setup some shortcuts for commonly-used functions
    S = @sprintf;
    R = @regexprep;
    
    %// Find the location of the space AFTER each code block but before a comment
    m = regexp(I, ['\s*\',s]);
    
    %// Compute the maximum column location of the code and add 4 (5 - 1)
    L = max([m{:}]) + 4;
    
    %// This is a callback for when we detect code
    %// It left justifies and pads the string to L width
    a = @(x)S('%-*s%s', L, x, s);
    
    %// This is a callback for when we detect a comment.
    b = @(x)R(...
            R(x, ...
                S('(.{1,%d}(\\s|$))', 93 - L), ... Regex for wrapping text to desired width
                S('$1\n%*s ', L+1, s)), ... Append a newline and padding for next line 
            ['\n\s*\',s,' $'], ''); ... Remove the trailing newline (to be improved)
        
    %// Perform replacement of everything.
    c = R(I, ...
            ['(.*?)\s*\',s,'\s*(.*$)'], ... Match "code comment_char comment"
            '${a($1)} ${b($2)}');   ... Replace using the output of the callbacks
            
    %// Concatenate all of the strings together with a newline in between
    d=S('%s\n',c{:});
end

Example Input

I = {
    'shM-crz1dc4."ANDBYOROF  # z = input'
    ''
    '     rz1      # convert input to uppercase'
    '    c   d        # split input on spaces'
    '         c4."ANDBYOROF        # create a list of the words from a packed string which shall be ignored'
    '   -          # filter those words out'
    ' hM                # only take the first letter of all words'
    's                   # join them into one string'
};

disp(f(I,'#'));

Example Output

shM-crz1dc4."ANDBYOROF     # z = input

     rz1                   # convert input to uppercase
    c   d                  # split input on spaces
         c4."ANDBYOROF     # create a list of the words from a packed string which shall be
                           # ignored
   -                       # filter those words out
 hM                        # only take the first letter of all words
s                          # join them into one string
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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