开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜