2008-09-10 13 views
7

tengo cadena como estamejor manera de analizar espacio separado del texto

/c SomeText\MoreText "Some Text\More Text\Lol" SomeText 

Quiero tokenize que, sin embargo no puedo dividir en los espacios. He encontrado un analizador algo feo que funciona, pero me pregunto si alguien tiene un diseño más elegante.

Esto está en C# por cierto.

EDIT: Mi versión fea, aunque fea, es O (N) y en realidad puede ser más rápido que usar un RegEx.

private string[] tokenize(string input) 
{ 
    string[] tokens = input.Split(' '); 
    List<String> output = new List<String>(); 

    for (int i = 0; i < tokens.Length; i++) 
    { 
     if (tokens[i].StartsWith("\"")) 
     { 
      string temp = tokens[i]; 
      int k = 0; 
      for (k = i + 1; k < tokens.Length; k++) 
      { 
       if (tokens[k].EndsWith("\"")) 
       { 
        temp += " " + tokens[k]; 
        break; 
       } 
       else 
       { 
        temp += " " + tokens[k]; 
       } 
      } 
      output.Add(temp); 
      i = k + 1; 
     } 
     else 
     { 
      output.Add(tokens[i]); 
     } 
    } 

    return output.ToArray();    
} 
+0

favor nos dicen más sobre lo que estamos tratando de lograr, incluyendo por qué no se puede dividir en espacios. Entonces podemos adaptar nuestras respuestas a su situación. –

Respuesta

16

El término de la computadora para lo que estás haciendo es lexical analysis; lea eso para un buen resumen de esta tarea común.

Según su ejemplo, supongo que desea que los espacios en blanco separen sus palabras, pero las cosas entre comillas se deben tratar como una "palabra" sin las comillas.

La forma más sencilla de hacer esto es definir una palabra como una expresión regular:

([^"^\s]+)\s*|"([^"]+)"\s* 

Esta expresión indica que una "palabra" es o bien (1) no cita, texto no está en blanco rodeado de espacios en blanco, o (2) texto sin comillas rodeado de comillas (seguido de algunos espacios en blanco). Tenga en cuenta el uso de la captura de paréntesis para resaltar el texto deseado.

Armado con esa expresión regular, su algoritmo es simple: busque en su texto la siguiente "palabra" tal como se define mediante la captura de paréntesis, y devuélvala. Repite eso hasta que te quedes sin "palabras".

Este es el código de trabajo más simple que pude encontrar, en VB.NET. Tenga en cuenta que tenemos que marcar ambos grupos para los datos ya que hay dos conjuntos de paréntesis de captura.

Dim token As String 
Dim r As Regex = New Regex("([^""^\s]+)\s*|""([^""]+)""\s*") 
Dim m As Match = r.Match("this is a ""test string""") 

While m.Success 
    token = m.Groups(1).ToString 
    If token.length = 0 And m.Groups.Count > 1 Then 
     token = m.Groups(2).ToString 
    End If 
    m = m.NextMatch 
End While 

Nota 1: Will's respuesta, arriba, es la misma idea que éste. Esperemos que esta respuesta se explican los detalles detrás de la escena un poco mejor :)

8

El espacio de nombres Microsoft.VisualBasic.FileIO (en Microsoft.VisualBasic.dll) tiene una TextFieldParser puede utilizar para dividir el espacio delimeted texto. Maneja cadenas dentro de comillas (es decir, "esto es un token" thisistokentwo) bien.

Nota: solo porque la DLL dice VisualBasic no significa que solo pueda usarlo en un proyecto de VB. Es parte de todo el Marco.

0

También es posible que desee buscar expresiones regulares. Eso podría ayudarte. Aquí está una muestra arrancada de MSDN ...

using System; 
using System.Text.RegularExpressions; 

public class Test 
{ 

    public static void Main() 
    { 

     // Define a regular expression for repeated words. 
     Regex rx = new Regex(@"\b(?<word>\w+)\s+(\k<word>)\b", 
      RegexOptions.Compiled | RegexOptions.IgnoreCase); 

     // Define a test string.   
     string text = "The the quick brown fox fox jumped over the lazy dog dog."; 

     // Find matches. 
     MatchCollection matches = rx.Matches(text); 

     // Report the number of matches found. 
     Console.WriteLine("{0} matches found in:\n {1}", 
          matches.Count, 
          text); 

     // Report on each match. 
     foreach (Match match in matches) 
     { 
      GroupCollection groups = match.Groups; 
      Console.WriteLine("'{0}' repeated at positions {1} and {2}", 
           groups["word"].Value, 
           groups[0].Index, 
           groups[1].Index); 
     } 

    } 

} 
// The example produces the following output to the console: 
//  3 matches found in: 
//   The the quick brown fox fox jumped over the lazy dog dog. 
//  'The' repeated at positions 0 and 4 
//  'fox' repeated at positions 20 and 25 
//  'dog' repeated at positions 50 and 54 
0

Craig es correcto — uso de expresiones regulares. Regex.Split puede ser más conciso para sus necesidades.

0

[^ \ t] + \ t | "[^"] + "t

usando la expresión regular definitivamente parece \ la mejor opción, sin embargo, esta solo devuelve toda la cadena. Estoy tratando de modificarla, pero no mucha suerte hasta ahora.

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t"); 
+0

Esto no funcionará porque Regex.Split está diseñado para capturar basado en separadores, no en tokens. Use Regex.Match para obtener el efecto deseado. –

3

Existe el enfoque de máquina de estado.

private enum State 
    { 
     None = 0, 
     InTokin, 
     InQuote 
    } 

    private static IEnumerable<string> Tokinize(string input) 
    { 
     input += ' '; // ensure we end on whitespace 
     State state = State.None; 
     State? next = null; // setting the next state implies that we have found a tokin 
     StringBuilder sb = new StringBuilder(); 
     foreach (char c in input) 
     { 
      switch (state) 
      { 
       default: 
       case State.None: 
        if (char.IsWhiteSpace(c)) 
         continue; 
        else if (c == '"') 
        { 
         state = State.InQuote; 
         continue; 
        } 
        else 
         state = State.InTokin; 
        break; 
       case State.InTokin: 
        if (char.IsWhiteSpace(c)) 
         next = State.None; 
        else if (c == '"') 
         next = State.InQuote; 
        break; 
       case State.InQuote: 
        if (c == '"') 
         next = State.None; 
        break; 
      } 
      if (next.HasValue) 
      { 
       yield return sb.ToString(); 
       sb = new StringBuilder(); 
       state = next.Value; 
       next = null; 
      } 
      else 
       sb.Append(c); 
     } 
    } 

Se puede ampliar fácilmente para cosas como comillas anidadas y escapes. Si regresa como IEnumerable<string>, su código solo analizará todo lo que necesite. No hay inconvenientes reales para ese tipo de enfoque perezoso, ya que las cadenas son inmutables, así que sabes que input no va a cambiar antes de que hayas analizado todo.

Ver: http://en.wikipedia.org/wiki/Automata-Based_Programming

Cuestiones relacionadas