2011-08-31 10 views
6

Co to jest najprostszy sposób, aby uzyskać numer linii od pozycji char w String w C#? (lub uzyskać pozycję linii (pierwszy znak w linii)) Czy jest jakaś wbudowana funkcja? Jeśli nie ma takiej funkcji, dobrym rozwiązaniem jest napisanie rozszerzenia:Jaki jest najprostszy sposób na uzyskanie numeru linii z pozycji char w łańcuchu?

public static class StringExt { 
    public static int 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) { .... } 

} 

?

Zmieniano: Dodano metoda PosFromLine

+1

Jeśli dzwonisz to dużo, na przykład przez wiele setek/tysięcy linii, są lepsze sposoby niż robisz. Na przykład, jeśli przetwarzasz plik sekwencyjnie, możesz "zapamiętać" numer linii, na którym się znajdujesz, i zwiększać go za każdym razem, gdy uderzysz w znak nowej linii. Możesz też "buforować" numer linii na każde 1000 znaków ze słownikiem i użyć pozycji pamięci podręcznej poprzedzającej zapytanie jako punktu początkowego. Jeśli wydajność nie jest problemem, przejdź bezpośrednio do czegoś czystego Jan/Jona. –

+0

Zobacz ["Czy w tytułach powinny znaleźć się" znaczniki "?] (Http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles), gdzie konsensus jest "nie, nie powinni"! –

Odpowiedz

14

Nieznaczne wahania na sugestię Jana, bez tworzenia nowych ciąg:

var lineNumber = input.Take(pos).Count(c => c == '\n') + 1; 

Korzystanie Take ogranicza rozmiar wejścia bez konieczności kopiowania danych ciągu.

Należy rozważyć, co chcesz wynikiem będzie, jeśli dana postać jest wysunięcie wiersza, przy okazji ... jak również, czy chcesz obsługiwać "foo\rbar\rbaz" jak trzy linie.

EDIT: Aby odebrać nowe drugą część pytania, można zrobić coś takiego:

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(); 

myślę że będzie działać ... ale nie jestem pewien, że nie będzie tylko wypisz podejście inne niż LINQ ...

+0

+1: Nie dla "Skeeta" - powody czynnika ;-), ale dla "bez tworzenia nowego ciągu" –

+0

@Jon: about "foo \ rbar \ rbaz": Myślę, że dla najlepszego wyniku powinien rozpoznać wszystkie trzy rodzaje linii podziału ("\ n", "\ r", "\ r \ n"), ale wszystkie rozwiązania są interesujące – Astronavigator

+0

@Astronawigator: To staje się trudniejsze, ponieważ chcesz policzyć "\ r \ n" jako pojedyncza linia podziału, więc staje się stanowy. Ick.Chyba że * potrzebujesz * tego zachowania, będę trzymać się liczenia '\ n' :) –

10

Policzyć liczbę nowych linii w substringed wejściowego łańcucha.

var lineNumber = input.Substring(0, pos).Count(c=>c == '\n') + 1; 

edit: i zrobić +1 ponieważ numery linii rozpoczyna się 1 :-)

+0

Bardzo jasne i oczywiste, a prawdopodobnie nie tak wydajne jak przykład w pytaniu. –

1

Jeśli zamierzasz wywoływać tę funkcję wiele razy na tym samym długim łańcuchu, ta klasa może być przydatna. Buforuje on nowe pozycje wierszy, aby później mógł wykonać wyszukiwanie O (log (podziały wierszy w łańcuchu)) dla GetLine i O (1) dla 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; 
    } 
} 

Oto mój przypadek testowy:

[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)); 
} 
Powiązane problemy