Лучший способ разобрать разделенный пробелами текст - PullRequest
7 голосов
/ 10 сентября 2008

У меня есть такая строка

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

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

Это в C # кстати.

РЕДАКТИРОВАТЬ: Моя уродливая версия, хотя и уродливая, O (N) и на самом деле может быть быстрее, чем с использованием 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();            
}

Ответы [ 6 ]

16 голосов
/ 10 сентября 2008

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

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

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

([^"^\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: Ответ Уилла выше, та же идея, что и этот. Надеюсь, этот ответ немного лучше объясняет детали сцены:)

7 голосов
/ 10 сентября 2008

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

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

3 голосов
/ 11 сентября 2008

Есть подход конечного автомата.

    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

0 голосов
/ 10 сентября 2008

[^ \ т] + \ т | "[^"] + "\ т

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

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");
0 голосов
/ 10 сентября 2008

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

0 голосов
/ 10 сентября 2008

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