Elegant way to split string into 2 strings on word boundaries to minimize length difference
I have a working solution right now, but it seems really ugly for something so (seemingly) simple.
I tried just breaking it when adding a word goes over the halfway mark, both splitting before and after adding the word, but depending on the length of the words it's either imbalanced towards the first or second line.
Sample inputs that I was initi开发者_运维问答ally having trouble with before the convoluted fix:
Input "Macaroni Cheese"
and "Cheese Macaroni"
Should output "Macaroni<br/> Cheese"
and "Cheese<br/> Macaroni"
respectively.
But simpler solutions either worked on the first but not the second, or the other way around.
So here's what I have that works, but I'm wondering if there's a more elegant way to do this.
public string Get2LineDisplayText(string original)
{
string[] words = original.Split(new[] {' ', '\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
//Degenerate case with only 1 word
if (words.Length <= 1)
{
return original;
}
StringBuilder builder = new StringBuilder();
builder.Append(words[0]); //Add first word without prepending space
bool addedBr = false;
foreach (string word in words.Skip(1))
{
if (builder.Length + word.Length < original.Length / 2) //Word fits on the first line without passing halfway mark
{
builder.Append(' ' + word);
}
else if (!addedBr) //Adding word goes over half, need to see if it's more balanced on the 1st or 2nd line
{
int diffOnLine1 = Math.Abs((builder.Length + word.Length) - (original.Length - builder.Length - word.Length));
int diffOnLine2 = Math.Abs((builder.Length) - (original.Length - builder.Length));
if (diffOnLine1 < diffOnLine2)
{
builder.Append(' ' + word);
builder.Append("<br/>");
}
else
{
builder.Append("<br/>");
builder.Append(' ' + word);
}
addedBr = true;
}
else //Past halfway and already added linebreak, just append
{
builder.Append(' ' + word);
}
}
return builder.ToString();
}
Sample input/output:
Here's what I came up with:
public static string Get2Lines(string input)
{
//Degenerate case with only 1 word
if (input.IndexOf(' ') == -1)
{
return input;
}
int mid = input.Length / 2;
int first_index_after = input.Substring(mid).IndexOf(' ') + mid;
int first_index_before = input.Substring(0, mid).LastIndexOf(' ');
if (first_index_after - mid < mid - first_index_before)
return input.Insert(first_index_after, "<BR />");
else
return input.Insert(first_index_before, "<BR />");
}
public static string Get2LineDisplayText(string original)
{
//Degenerate case with only 1 word
if (!original.Any(Char.IsWhiteSpace))
{
return original;
}
int mid = original.Length / 2;
if (!Char.IsWhiteSpace(original[mid]))
{
for (int i = 1; i < mid; i += i)
{
if (Char.IsWhiteSpace(original[mid + i]))
{
mid = mid + i;
break;
}
if (Char.IsWhiteSpace(original[mid - i]))
{
mid = mid - i;
break;
}
}
}
return original.Substring(0, mid)
+ "<br />" + original.Substring(mid + 1);
}
I tried my hand at it and arrived at :
String splitInMiddle(String s) {
int middle = s.length() / 2;
int right = s.indexOf(" ",middle);
int left = s.lastIndexOf(" ",middle);
int split = right;
if ((right < 0) || (left + right > 2*middle)) {
split = left;
}
return s.substring(0, split) + "<br/>\n" + s.substring(split + 1);
}
The principle is that it looks for the first space after and the last space before. If the left one is closer than the right one pick that.
Then glue the pieces I want with a CR instead of a space.
精彩评论