# Text Justification in Java

## August 15, 2014

This is a problem on LeetCode and the submission rate, as of August 2014, is 14% where of the 56,868 submissions only 7,954 were right. As you can see, this problem is a bit daunting. The trick is the math operators and when to use them. There’s many solutions to this but, for me, in Java, here’s a fun way to solve that.

# The Problem

Given an array of words and a length L, format the text such that each line has exactly L characters and is fully (left and right) justified.

You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ‘ ‘ when necessary so that each line has exactly L characters.

Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.

For the last line of text, it should be left justified and no extra space is inserted between words.

For example, take this array: `["This", "is", "an", "example", "of", "text", "justification."]`, and we’ll want 16 characters in length.

Return the formatted lines as:

```[
"This    is    an",
"example  of text",
"justification.  "
]```

Note: Each word is guaranteed not to exceed the length – which is 16 in this case.

Corner Cases: A line other than the last line might contain only one word. What should you do in this case? In this case, that line should be left-justified.

# My Approach

What’s the best approach here? Iterative is the approach as the input can be many lines and the length can be as long as 50 to 100. If we keep doing a recursive approach, eventually, we’d get a stackoverflow if we handle too many lines for processing. Keep in mind the processing time for all of this.

So, first I create three `ListArray`. One to return, one to copy the `String[]` words, and one to use the words to use during the line justification.

So then we check if the word length fits exactly into the `L` and from there, we simply add that there. If not (which usually it isn’t), we do a loop until a word fits and then check for the upcoming space. Usually the word fits with an upcoming space.

So, if the word doesn’t fit the length, we cut it off there and continue with the justification. If the word fits with an upcoming space, we continue because we can assume another word is allowed to fit.

We continue this process until 1 of 2 things happen:

• We run out of characters to process in the line (smooth fit = # of words and one space in between)
• The word length is longer than “characters left”

Usually, we hit number 2 and then we begin the line justification. If number 1 is met, there is no need for justification because the line fits perfectly with one space between each word. However, like I said, usually #2 is met because smooth fits are rare.

Before we move onto the function to justify the line, we check to see if there are any words left in the `ListArray` which has all of the words. If there are no words left, that signifies that we are on the last line and no text justification is needed other than a simple left justification.

In that case, all you do is add the words like you would as a regular append function would. Then take the remaining length of the spaces left and add the spaces to the line to meet the `L` requirement.

If not, we take the function and this is where the beauty happens. We use a StringBuilder because StringBuilder is faster when performing concatenations. This is because when you concatenate a `String`, you are internally creating a new object every time since `String` is immutable (unable to be changed).

So, in the function `createLine`, we get the string of words to justify, length of words of words, the length to use and if it’s the last line. If it is the last line, we do simply a left justify (which I explained before), if not we keep going.

So when we keep going, we take the spaces by doing length of words combined minus length of the line (L). We take the number of spaces divided by the words total to get the number of spaces each word gets in between (even). Then we take the same variables (spaces/words total) and do the remainder math operator (%) to get the remaining left.

The first loop adds the word to the `StringBuilder` and the next for loop adds the spaces each word gets. Finally, to achieve justification, we go through the remainder and add one space until the remainder is less than the words to add.

What happens is that the justification goes from left to right (because English is read left to right) so place the odd spaces starting from the left. We add one from the remainder (of the decimal that was “each word gets x amount of spaces) until we are done.

Once we are done, your line should be justified but to make sure, we cut the line to the length given, using setLength, to remove any trailing spaces and then return the StringBuilder.

Once returned, the function ends and we parse the `StringBuilder` to a `String` (`.toString()`) and add it to the list to return. We clear the `toUse` list and set the `wordsUsed` to `0` and get it ready for the next iteration.

We do this until we have no more words left in `wordsLeft` and after that, we will have a list full of justified lines ready to send back.

# My Solution

``````import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class main {

public static void main(String[] args) {

String[] theText = {"This", "is", "an", "example", "of", "text", "justification."}; // 18
//String[] theText = {"What","must","be","shall","be."}; // 12
//String[] theText = {"Listen","to","many,","speak","to","a","few."}; //  6
int L = 18;
System.out.println(main.fullJustify(theText, L));

}

public static List<String> fullJustify(String[] words, int L) {

// First we create the list to send back
List<String> list = new ArrayList<String>(); // make a list every row

// Then we create two more. wordsLeft = words left to use
List<String> wordsLeft = new ArrayList<String>(Arrays.asList(words)); // words left to recover from

// and the words to use in a current line
List<String> toUse = new ArrayList<String>();

// words used so loop knows how many words are left.
int wordsUsed = 0;

// used to detect if on last line
boolean lastLine = false;

// if we receive a list with no words... we output the number of spaces
// as defined by L so L = 3 would output [   ]
if (wordsLeft.get(0).length() == 0) {

// new string buffer to add the word
StringBuffer noBuffer = new StringBuffer(L);

// add spaces as dictated by L
noBuffer.append(" ");
return list;
} else{
// Normally, we do not so we do this instead.
// Go through words left list while
// there is still words in it
while (wordsLeft.size() > 0) {

// These variables help set the spaces, word index
// and character values left to be analyzed
int lengthTotalChars = 0;
int spacesLeft = L;
int charsTotal = 0;
int i = 0;

// while we have spaces left, and
// words used is MORE than the size of wordsLeft
while (spacesLeft > 0 && wordsUsed < wordsLeft.size()) {
// words length to detect if there's space
// to fit into the L of the paragraph
int getWordLength = wordsLeft.get(i).length();

// To avoid negative numbers, if spaces left
// is more than word length, that means no
// room left so we sit it to 0
if (spacesLeft < getWordLength) spacesLeft = 0;

// if space left is EQUAL to word length (smooth fit)
// OR space left is MORE than word left (above it)
// there is still room so we advance.
if (spacesLeft >= getWordLength) {

// take space away from length of word
// for every character, add to characters total
// add to the ListArray called toUse for parsing
// lastly, take away wordsToGo and add one to wordsUsed
spacesLeft -= getWordLength;
charsTotal += wordsLeft.get(i).length();
wordsUsed++;

// if length of total chars (word length & space)
// is LESS than length, we can continue adding
// to the variables
if (lengthTotalChars < L) {
lengthTotalChars++;
spacesLeft--;
}
}

// we move onto the next
// word so we +1 (i) the number
i++;
}

// we take the number of words left
// and clear it from 0 tp that.
wordsLeft.subList(0, toUse.size()).clear();

// if we have no more words left, obviously
// that means we are on the last line
if (wordsLeft.size() == 0) lastLine = true;

// we now parse the line accordingly
// and return the new justified string to add to List
// toUse = words fitted in one line, L = length of paragraph
// charsTotal = number of characters (excluding spaces)
// lastLine == detect if on last line)
list.add(createLine(toUse, L, charsTotal, lastLine).toString());

// we clear the toUse list after
// every line is finished
// and set the wordsUsed to 0.
toUse.clear();
wordsUsed = 0;
}

// after every word is finished parsing
// we return the new list
return list;
}
}

public static StringBuffer createLine(List<String> s, int lineLength, int charsTotal, boolean lastLine) {

// create a new StringBuffer for performance
// during string concatenation
StringBuffer sb = new StringBuffer(lineLength);

// Set the length of paragraph
int spaceInAll = lineLength;

// set words total to use
int wordsTotal = s.size();

// if we're on the last line,
// we are going to left-justify instead
if (lastLine) {
for (int spaceLoop = 0; spaceLoop < s.size(); spaceLoop++) {
// add append the string with the word and extra space
sb.append(s.get(spaceLoop) + " ");
int newWordLength = s.get(spaceLoop).length() + 1;
spaceInAll -= newWordLength;
}
// what remains gets appended with more spaces
// until we run out of spaces
for (int remainingLastSpace = 0; remainingLastSpace < spaceInAll; remainingLastSpace++)
sb.append(" ");
}else{
// If not last line, we'll check if the whole
// word takes up the whole line
if (s.get(0).length() == lineLength) {
// so we just append the word
// without adding more spaces
sb.append(s.get(0));
}else{
// here's where the MAGIC happens
// spaces is the paragraph divided by characters total
int spaces = lineLength - charsTotal;

// lastToAdd = will say how many
// words to use in the algorithm
if (wordsTotal == 1) {
// if we have 1 word
// lastToAdd will obviously be 1
} else {
// if not, it will be word total MINUS 1
}

// number = get spaces divide by words
// This is how many space breaks are in line
int number = spaces/lastToAdd;

// get the remaining (if odd space breaks)
// to add to the left-most for that "justification"
// example "Hello   World!"
//  L=14    -----XXY------ [X = number, Y =toAdd_remain]

// every word, we iterate this loop
for (int remainder = 0; remainder < wordsTotal; remainder++) {
// first we add the word to the StringBuffer
sb.append(s.get(remainder));
// each word gets this mount of spaces
for (int sP = 0; sP < number; sP++)
sb.append(" ");

// if the loop is less than toAdd_remainder
// that means we still have one more space
// to give to this word on the line
if (remainder < toAdd_remain) sb.append(" ");
}
}
}

// Then we trim the sentence to the length
// by trimming the trailing added space
// so it fits exactly into the paragraph line
sb.setLength(lineLength);

// Readability for the console
System.out.println(sb.toString() + "| (" + sb.toString().length() + ")");

// we return the newly-justified
// StringBuffer and we are finished
// with the space parsing
return sb;
}
}``````

Despite this being from 2014, I am posting this again on my new blog in 2017. Excuse any of my anti-design patterns in Java. :-)