What is simpliest way to get Line number from char position in String?
What is simpliest way to get Line number from char position in String in C#? (or get Position of line (first char in line) ) Is there any built-in function ? If there are no such function is it good solution to write extension like :
public static class StringExt {
public static in开发者_StackOverflow中文版t LineFromPos(this String S, int Pos) {
int Res = 1;
for (int i = 0; i <= Pos - 1; i++)
if (S[i] == '\n') Res++;
return Res;
}
public static int PosFromLine(this String S, int Pos) { .... }
}
?
Edited: Added method PosFromLine
A slight variation on Jan's suggestion, without creating a new string:
var lineNumber = input.Take(pos).Count(c => c == '\n') + 1;
Using Take
limits the size of the input without having to copy the string data.
You should consider what you want the result to be if the given character is a line feed, by the way... as well as whether you want to handle "foo\rbar\rbaz"
as three lines.
EDIT: To answer the new second part of the question, you could do something like:
var pos = input.Select((value, index) => new { value, index })
.Where(pair => pair.value == '\n')
.Select(pair => pair.index + 1)
.Take(line - 1)
.DefaultIfEmpty(1) // Handle line = 1
.Last();
I think that will work... but I'm not sure I wouldn't just write out a non-LINQ approach...
Count the number of newlines in the substringed input string.
var lineNumber = input.Substring(0, pos).Count(c=>c == '\n') + 1;
edit: and do a +1
because line numbers begin at 1 :-)
If you are going to call the function many times on the same long string, this class can be usefull. It caches the new line positions, so that later it can perform O(log (line breaks in string)) lookup 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));
}
For who is interested in javascript or a more iterative approach.
const {min} = Math
function lineAndColumnNumbersAt(str, pos) {
let line = 1, col = 1
const _pos = min(str.length, pos)
for (let i = 0; i < _pos; i++)
if (str[i] === '\n') {
line++
col = 1
} else
col++
return {line, col}
}
lineAndColumnNumbersAt('test\ntest\ntest', 8)
In ruby:
def line_index
source[0...position].count("\n")
end
def line_number
line_index + 1
end
def lines
source.lines
end
def line_source
lines[line_index]
end
def line_position
position - lines[0...line_index].map(&:size).sum
end
精彩评论