Лучший способ разбора текста, разделенного пробелом

у меня есть строка, как это

 /c SomeTextMoreText "Some TextMore TextLol" SomeText

Я хочу, чтобы маркировать его, однако я не могу просто разделить на пространствах. Я придумал несколько уродливый парсер, который работает, но мне интересно, есть ли у кого-нибудь более элегантный дизайн.

это в C#, кстати.

EDIT: моя уродливая версия, в то время как уродливая, O(N) и может быть быстрее, чем использование регулярного выражения.

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

6 ответов


компьютер термин для того, что вы делаете лексический анализ; прочитайте это для хорошего резюме этой общей задачи.

основываясь на вашем примере, я предполагаю, что вы хотите пробела для разделения слов, но все в кавычках должно рассматриваться как "слово" без кавычек.

самый простой способ сделать это-определить слово как регулярное выражение:

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

это выражение утверждает, что" слово " является либо (1) не кавычкой, текст без пробелов, окруженный пробелами, или (2) текст без кавычек, окруженный кавычками (за которым следуют некоторые пробелы). Обратите внимание на использование круглых скобок для выделения нужного текста.

вооруженный этим регулярным выражением, ваш алгоритм прост: найдите свой текст для следующего "слова", как определено скобками захвата, и верните его. Повторяйте это, пока у вас не закончатся "слова".

вот самый простой бит рабочего кода, который я мог бы придумать, в VB.NET - ... Обратите внимание, что у нас есть проверить и группы для данных, поскольку есть два набора скобок.

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

Примечание 1: Уилл!--4--> ответ, выше, та же идея, что и эта. Надеюсь, этот ответ объясняет детали за сценой немного лучше :)


Microsoft.На языке VisualBasic.Пространство имен FileIO (в Microsoft.На языке VisualBasic.dll) имеет TextFieldParser, который можно использовать для разделения текста с разделителями пространства. Он хорошо обрабатывает строки в кавычках (т. е. "это один токен" thisistokentwo).

Примечание, только потому, что DLL говорит VisualBasic не означает, что вы можете использовать его только в проекте VB. Его часть всей структуры.


существует подход государственной машины.

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

его можно легко расширить для таких вещей, как вложенные кавычки и экранирование. Возвращение как IEnumerable<string> позволяет вашему коду анализировать столько, сколько вам нужно. Нет никаких реальных недостатков такого ленивого подхода, поскольку строки неизменяемы, поэтому вы знаете, что input не изменится, прежде чем вы проанализируете все это.

см.:http://en.wikipedia.org/wiki/Automata-Based_Programming


вы также можете посмотреть регулярные выражения. Это может тебе помочь. Вот образец, вырванный из 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

Крэг правильно - используйте регулярные выражения. регулярное выражение.Сплит может быть более кратким для ваших нужд.


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

С помощью регулярных выражений, безусловно, выглядит лучше, но это просто возвращает всю строку. Я пытаюсь его подправить, но пока не очень.

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