Here is my question:
Given a string, which is made up of space separated words, how can I split that into N strings of (roughly) even length, only breaking on spaces?
Here is what I've gathered from research:
I started by researching word-wrapping algorithms, because it seems to me that this is basically a word-wrapping problem. However, the majority of what I've found so far (and there is A LOT out there about word wrapping) assumes that the width of the line is a known input, and the number of lines is an output. I want the opposite.
I have found a (very) few questions, such as this that seem to be helpful. However, they are all focused on the problem as one of optimization - e.g. how can I split a sentence into a given number of lines, while minimizing the raggedness of the lines, or the wasted whitespace, or whatever, and do it in linear (or NlogN, or whatever) time. These questions seem mostly to be unanswered, as the optimization part of the problem is relatively "hard".
However, I don't care that much about optimization. As long as the lines are (in most cases) roughly even, I'm fine if the solution doesn't work in every single edge case, or can't be proven to be the least time complexity. I just need a real world solution that can take a string, and a number of lines (greater than 2), and give me back an array of strings that will usually look pretty even.
Here is what I've come up with: I think I have a workable method for the case when N=3. I start by putting the first word on the first line, the last word on the last line, and then iteratively putting another word on the first and last lines, until my total width (measured by the length of the longest line) stops getting shorter. This usually works, but it gets tripped up if your longest words are in the middle of the line, and it doesn't seem very generalizable to more than 3 lines.
var getLongestHeaderLine = function(headerText) {
//Utility function definitions
var getLongest = function(arrayOfArrays) {
return arrayOfArrays.reduce(function(a, b) {
return a.length > b.length ? a : b;
});
};
var sumOfLengths = function(arrayOfArrays) {
return arrayOfArrays.reduce(function(a, b) {
return a + b.length + 1;
}, 0);
};
var getLongestLine = function(lines) {
return lines.reduce(function(a, b) {
return sumOfLengths(a) > sumOfLengths(b) ? a : b;
});
};
var getHeaderLength = function(lines) {
return sumOfLengths(getLongestLine(lines));
}
//first, deal with the degenerate cases
if (!headerText)
return headerText;
headerText = headerText.trim();
var headerWords = headerText.split(" ");
if (headerWords.length === 1)
return headerText;
if (headerWords.length === 2)
return getLongest(headerWords);
//If we have more than 2 words in the header,
//we need to split them into 3 lines
var firstLine = headerWords.splice(0, 1);
var lastLine = headerWords.splice(-1, 1);
var lines = [firstLine, headerWords, lastLine];
//The header length is the length of the longest
//line in the header. We will keep iterating
//until the header length stops getting shorter.
var headerLength = getHeaderLength(lines);
var lastHeaderLength = headerLength;
while (true) {
//Take the first word from the middle line,
//and add it to the first line
firstLine.push(headerWords.shift());
headerLength = getHeaderLength(lines);
if (headerLength > lastHeaderLength || headerWords.length === 0) {
//If we stopped getting shorter, undo
headerWords.unshift(firstLine.pop());
break;
}
//Take the last word from the middle line,
//and add it to the last line
lastHeaderLength = headerLength;
lastLine.unshift(headerWords.pop());
headerLength = getHeaderLength(lines);
if (headerLength > lastHeaderLength || headerWords.length === 0) {
//If we stopped getting shorter, undo
headerWords.push(lastLine.shift());
break;
}
lastHeaderLength = headerLength;
}
return getLongestLine(lines).join(" ");
};
debugger;
var header = "an apple a day keeps the doctor away";
var longestHeaderLine = getLongestHeaderLine(header);
debugger;
EDIT: I tagged javascript, because ultimately I would like a solution I can implement in that language. It's not super critical to the problem though, and I would take any solution that works.
EDIT#2: While performance is not what I'm most concerned about here, I do need to be able to perform whatever solution I come up with ~100-200 times, on strings that can be up to ~250 characters long. This would be done during a page load, so it needs to not take forever. For example, I've found that trying to offload this problem to the rendering engine by putting each string into a DIV and playing with the dimensions doesn't work, since it (seems to be) incredibly expensive to measure rendered elements.