Better way to write a string slicer

Here is some code to insert a new line escape sequence after a certain number of characters (for fixed width displays)

    private string spliceNoteText(string text)
{
StringBuilder sb = new StringBuilder();

int maxWidth = 70;
int blockOfText;

for (int i = 0; i <= text.Length; i += maxWidth)
{
if (i >= text.Length)
continue;

int charsRemaining = text.Length - i;

if (charsRemaining <= maxWidth)
blockOfText = charsRemaining;
else
blockOfText = maxWidth;

sb.Append(text.Substring(i, blockOfText));
sb.Append("\n");

}
return sb.ToString();
}


It's is ugly and boring, can it be made prettier or more elegant?

You could already create the StringBuilder with the text you want to 'splice', rather than building it up again. Then you can just determine how many newline characters you want to add (and you should use Environment.NewLine instead of specifying it explicitly), and insert them in the right place.

    private string SpliceNoteText(string text, int maxWidth)
{
StringBuilder sb = new StringBuilder(text);

for (int i = 0; i < (sb.Length / maxWidth); i++)
{
int insertPosition = i * maxWidth;
sb.Insert(insertPosition, Environment.NewLine);
}

return sb.ToString();
}

• The logic will be off however as you'd have to account for the added newlines if you go forward through the builder. You should go backwards through it. Oct 10 '11 at 20:59
• Inserting characters in a string builder means that you will be copying half the string by average for each insert. Copying strings to a new string builder will scale much better. Oct 11 '11 at 20:16

You can make it reusable by taking a line length parameter, and you can use a regular expression to reduce the code somewhat:

public static string SpliceNoteText(string text, int lineLength) {
return Regex.Replace(text, "(.{" + lineLength + "})", "\$1" + Environment.NewLine);
}


Note that I used Environment.NewLine which gives the newline character combination for the current system, instead of the newline character escape sequence. That's what you would normally do, but it might not apply in your specific case, depending on how you are going to use the string.

Edit:

The regular expression is of course not as fast as the original code. A simple performance test for splicing a 100000 character string gives:

Original  0,23 ms.
Regexp    1,31 ms.
Insert   15,06 ms.


You can avoid creating a bunch of temporary strings (copying all your character data twice) by using StringBuilder to something nearer its full potential.

private static string SpliceNoteText(string text, int maxWidth)
{
if (string.IsNullOrEmpty(text)) return text;

int numWraps = (text.Length - 1) / maxWidth;
StringBuilder sb = new StringBuilder(text.Length + numWraps * Environment.NewLine.Length);

for (int i = 0, off = 0; i < numWraps; i++, off += maxWidth) {
sb.Append(text, off, maxWidth).Append(Environment.NewLine);
}
sb.Append(text, off, text.Length - off);

return sb.ToString();
}


Could also use linq

public static string SpliceNoteText(string text, int lineLength)
{
var segments = (int)Math.Ceiling( (double)text.Length / (double)lineLength);
return string.Join(Environment.NewLine,
Enumerable.Range(0,segments
).Select(i => x.Substring(i * maxLength, Math.Min( lineLength, text.Length - i * lineLength ))));
}


Updated

Faster with lookup

public static string SpliceNoteText(string text, int lineLength)
{
int k = 0;
return string.Join(Environment.NewLine, text.ToLookup(c => k++ / lineLength)
.Select(e => new String(e.ToArray())));
}


To splice the string in general, this would be faster and more efficient then using LINQ or other methods.

public static IEnumerable<string> Splice(this string s, int spliceLength)
{
if (s == null)
throw new ArgumentNullException("s");
if (spliceLength < 1)
throw new ArgumentOutOfRangeException("spliceLength");

if (s.Length == 0)
yield break;
var start = 0;
for (var end = spliceLength; end < s.Length; end += spliceLength)
{
yield return s.Substring(start, spliceLength);
start = end;
}
yield return s.Substring(start);
}


Then to use with your function:

static string SpliceNoteText(string text)
{
return String.Join(Environment.NewLine, text.Splice(70));
}


If you'd rather not use a separate (extension) method or use String.Join(), it should be easy to combine them.

static string SpliceNoteText(string text)
{
if (text == null)
throw new ArgumentNullException("text");

if (text.Length == 0)
return String.Empty;
const int spliceLength = 70;
var sb = new StringBuilder();
var start = 0;
for (var end = spliceLength; end < s.Length; end += spliceLength)
{
sb.Append(text, start, spliceLength).AppendLine();
start = end;
}
sb.Append(text, start, text.Length - start);
return sb.ToString();
}