2011-08-31 15 views
6

¿Cuál es la forma más sencilla de obtener el número de línea de la posición de caracteres en Cadena en C#? (u obtener Posición de línea (primer carácter en línea)) ¿Hay alguna función incorporada? Si no hay tal función, ¿es buena solución escribir extensiones como:¿Cuál es la manera más simple de obtener el número de línea de la posición de char en String?

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) { .... } 

} 

?

Editado: Añadido método PosFromLine

+1

Si se llama a esto mucho, por ejemplo, durante muchos cientos/miles de líneas, hay mejores maneras de lo que está haciendo. Por ejemplo, si está procesando un archivo secuencialmente, podría 'recordar' el número de línea en el que se encuentra e incrementarlo cada vez que toque una nueva línea. O bien, podría "almacenar en caché" el número de línea por cada 1000 caracteres más o menos con un diccionario, y usar la entrada de caché que precede a la consulta como punto de partida. Si el rendimiento no es un problema, busque algo claro directamente de Jan/Jon. –

+0

Consulte ["¿Las preguntas deberían incluir" etiquetas "en sus títulos?]] (Http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles), donde el consenso es "no, no deberían"! –

Respuesta

14

Una ligera variación en la sugerencia de enero, sin crear una nueva cadena:

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

Usando Take limita el tamaño de la entrada sin tener que copiar los datos de cadena.

Debe tener en cuenta lo que quiere que el resultado sea si el carácter dado es un avance de línea, por cierto ... así como si desea manejar "foo\rbar\rbaz" como tres líneas.

EDIT: Para responder a la nueva segunda parte de la pregunta, usted podría hacer algo como:

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

Yo creo que va a funcionar ... pero no estoy seguro de que no lo haría solo escriba un enfoque que no sea LINQ ...

+0

+1: No para "Skeet"; razones de factor ;-), pero para "sin crear una nueva cadena" –

+0

@Jon: sobre "foo \ rbar \ rbaz": Creo que para obtener el mejor resultado debe reconocer los tres tipos de saltos de línea ("\ n", "\ r", "\ r \ n"), pero todas las soluciones son interesantes – Astronavigator

+0

@Astronavigator: Eso se vuelve más difícil, porque querría contar "\ r \ n" como salto de línea único, por lo que se convierte en estado. Ick.A menos que * necesites * ese comportamiento, me limitaría a contar '\ n' :) –

10

Contar el número de saltos de línea en la cadena de entrada substringed.

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

edición: y hacer un +1 porque los números de línea comienzan en 1 :-)

+0

Muy claro y obvio, aunque probablemente no tan eficiente como el ejemplo en la pregunta. –

1

Si va a llamar a la función muchas veces en la misma cadena larga, esta clase puede ser útil. Almacena en caché las nuevas posiciones de línea, para luego poder realizar O (log (saltos de línea en cadena)) búsqueda GetLine y O (1) para 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; 
    } 
} 

Aquí es mi caso de prueba:

[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)); 
} 
Cuestiones relacionadas