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

StackOverflow https://stackoverflow.com/questions/54866

  •  09-06-2019
  •  | 
  •  

Вопрос

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

 /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();            
}
Это было полезно?

Решение

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

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

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

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

Другие советы

Пространство имен 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

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

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

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

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top