Pergunta

Eu tenho uma corda assim

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

Quero tokenizá-lo, mas não posso simplesmente dividir os espaços.Eu criei um analisador um tanto feio que funciona, mas estou me perguntando se alguém tem um design mais elegante.

Isso está em C # aliás.

EDITAR: Minha versão feia, embora feia, é O(N) e pode realmente ser mais rápida do que usar um 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();            
}
Foi útil?

Solução

O termo computacional para o que você está fazendo é análise lexical;leia isso para obter um bom resumo desta tarefa comum.

Com base no seu exemplo, suponho que você queira que os espaços em branco separem suas palavras, mas o que estiver entre aspas deve ser tratado como uma "palavra" sem as aspas.

A maneira mais simples de fazer isso é definir uma palavra como uma expressão regular:

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

Esta expressão afirma que uma "palavra" é (1) texto sem citações e sem espaços em branco cercado por espaços em branco ou (2) texto sem citações cercado por aspas (seguido por algum espaço em branco).Observe o uso de parênteses de captura para destacar o texto desejado.

Armado com esse regex, seu algoritmo é simples:pesquise em seu texto a próxima "palavra" conforme definido pelos parênteses de captura e retorne-a.Repita isso até ficar sem “palavras”.

Aqui está o código de trabalho mais simples que consegui criar, em VB.NET.Observe que temos que verificar ambos grupos para dados, pois há dois conjuntos de parênteses 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 A resposta acima é a mesma ideia que esta.Esperamos que esta resposta explique um pouco melhor os detalhes dos bastidores :)

Outras dicas

O namespace Microsoft.VisualBasic.FileIO (em Microsoft.VisualBasic.dll) possui um TextFieldParser que você pode usar para dividir em texto delimitado por espaço.Ele lida bem com strings entre aspas (ou seja, "este é um token" thisistokentwo).

Observe que só porque a DLL diz VisualBasic não significa que você só pode usá-lo em um projeto VB.Faz parte de todo o Framework.

Existe a abordagem da 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);
        }
    }

Ele pode ser facilmente estendido para coisas como aspas aninhadas e escape.Retornando como IEnumerable<string> permite que seu código analise apenas o que você precisa.Não há desvantagens reais nesse tipo de abordagem preguiçosa, pois as strings são imutáveis, então você sabe disso input não vai mudar antes de você analisar tudo.

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

Você também pode querer examinar expressões regulares.Isso pode ajudá-lo.Aqui está um exemplo extraído do 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

Craig está certo - use expressões regulares. Regex.Split pode ser mais conciso para suas necessidades.

[^ ]+ |"[^"]+"

usar o Regex definitivamente parece a melhor aposta, porém este apenas retorna a string inteira.Estou tentando ajustá-lo, mas não tive muita sorte até agora.

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top