If you intend to call a function multiple times in one long line, this class may be useful. It caches new line positions, so that later it can do O (log (line breaks in a line)) look up for GetLine and O (1) for GetOffset .
public class LineBreakCounter { List<int> lineBreaks_ = new List<int>(); int length_; public LineBreakCounter(string text) { if (text == null) throw new ArgumentNullException(nameof(text)); length_ = text.Length; for (int i = 0; i < text.Length; i++) { if (text[i] == '\n') lineBreaks_.Add(i); else if (text[i] == '\r' && i < text.Length - 1 && text[i + 1] == '\n') lineBreaks_.Add(++i); } } public int GetLine(int offset) { if (offset < 0 || offset > length_) throw new ArgumentOutOfRangeException(nameof(offset)); var result = lineBreaks_.BinarySearch(offset); if (result < 0) return ~result; else return result; } public int Lines => lineBreaks_.Count + 1; public int GetOffset(int line) { if (line < 0 || line >= Lines) throw new ArgumentOutOfRangeException(nameof(line)); if (line == 0) return 0; return lineBreaks_[line - 1] + 1; } }
Here is my test case:
[TestMethod] public void LineBreakCounter_ShouldFindLineBreaks() { var text = "Hello\nWorld!\r\n"; var counter = new LineBreakCounter(text); Assert.AreEqual(0, counter.GetLine(0)); Assert.AreEqual(0, counter.GetLine(3)); Assert.AreEqual(0, counter.GetLine(5)); Assert.AreEqual(1, counter.GetLine(6)); Assert.AreEqual(1, counter.GetLine(8)); Assert.AreEqual(1, counter.GetLine(12)); Assert.AreEqual(1, counter.GetLine(13)); Assert.AreEqual(2, counter.GetLine(14)); Assert.AreEqual(3, counter.Lines); Assert.AreEqual(0, counter.GetOffset(0)); Assert.AreEqual(6, counter.GetOffset(1)); Assert.AreEqual(14, counter.GetOffset(2)); }
ghord source share