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 = 87chars. 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
- Function or full program allowed.
- Default rules for input/output.
- Standard loopholes apply.
- This is code-golf, so lowest byte-count wins. Tiebreaker is earlier submission.
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!
5 Answers 5
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
-
1\$\begingroup\$ to whoever downvoted: the answer is fixed. \$\endgroup\$Ven– Ven2016年03月23日 10:00:39 +00:00Commented 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– Value Ink2016年03月25日 00:36:48 +00:00Commented Mar 25, 2016 at 0:36
-
\$\begingroup\$ @KevinLau well spotted, fixed! \$\endgroup\$Ven– Ven2016年03月25日 08:38:09 +00:00Commented Mar 25, 2016 at 8:38
-
\$\begingroup\$ Can you update your example output as well? \$\endgroup\$Value Ink– Value Ink2016年03月25日 15:58:57 +00:00Commented Mar 25, 2016 at 15:58
-
\$\begingroup\$ @KevinLau done. \$\endgroup\$Ven– Ven2016年03月25日 16:00:32 +00:00Commented Mar 25, 2016 at 16:00
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
-
\$\begingroup\$ If anyone can figure out why
replace(" *"+s)isn't working butreplaceAll(" *"+s)is I'd love to hear it -- I can't figure it out. \$\endgroup\$CAD97– CAD972016年03月23日 04:38:10 +00:00Commented Mar 23, 2016 at 4:38 -
\$\begingroup\$ <badguess>
replaceuses strings butreplaceAlluses regexes. </badguess> \$\endgroup\$CalculatorFeline– CalculatorFeline2016年03月23日 04:40:10 +00:00Commented Mar 23, 2016 at 4:40 -
\$\begingroup\$ @CatsAreFluffy well, you're correct! Don't know how I didn't realize that :P \$\endgroup\$CAD97– CAD972016年03月23日 04:47:15 +00:00Commented Mar 23, 2016 at 4:47
-
\$\begingroup\$ Can't you remove the newline? \$\endgroup\$CalculatorFeline– CalculatorFeline2016年03月23日 04:56:11 +00:00Commented 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\$CalculatorFeline– CalculatorFeline2016年03月23日 05:01:47 +00:00Commented Mar 23, 2016 at 5:01
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
mapcall and taking out some unnecessarystripfunctions 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
mapcalls together by leveraging the power of higher-order functions, meaning that the first map call will set the length variablelcorrectly even if it's called after the declaration of the helper functions. -4 bytes. - Abused multiline strings to replace
\nwith actual newlines, plus a little trick usingifover ternary operators (whenjoinis called on an array withnilvalues, they become empty strings) .joincan apparently be replaced with a*.
-
\$\begingroup\$ I think it should be fixed now? \$\endgroup\$Value Ink– Value Ink2016年03月23日 20:19:27 +00:00Commented Mar 23, 2016 at 20:19
-
\$\begingroup\$ how does this wrap at 94? \$\endgroup\$Ven– Ven2016年03月23日 20:26:50 +00:00Commented 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– Value Ink2016年03月24日 02:26:29 +00:00Commented 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 = 87chars. 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– Value Ink2016年03月24日 16:50:48 +00:00Commented Mar 24, 2016 at 16:50 -
\$\begingroup\$ ah, well spotted, makes sense! \$\endgroup\$Ven– Ven2016年03月24日 17:01:15 +00:00Commented Mar 24, 2016 at 17:01
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
-
\$\begingroup\$ @nimi Hopefully the updates now make for a better solution. \$\endgroup\$Matt– Matt2016年03月25日 01:24:40 +00:00Commented 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– Matt2016年03月25日 18:43:45 +00:00Commented Mar 25, 2016 at 18:43
-
\$\begingroup\$ No. Now have a +1. \$\endgroup\$nimi– nimi2016年03月25日 19:24:40 +00:00Commented Mar 25, 2016 at 19:24
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
length of the longest code-line + 5. This also applied to lines which only contain an explanation, because they were wrapped. \$\endgroup\$