Лучший способ разобрать разделенный пробелами текст
У меня есть такая строка
/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 ответов
Компьютерный термин для того, что вы делаете, это лексический анализ; прочитайте это для хорошего резюме этой общей задачи.
Исходя из вашего примера, я предполагаю, что вы хотите, чтобы пробел разделял ваши слова, но все, что в кавычках, следует рассматривать как "слово" без кавычек.
Самый простой способ сделать это - определить слово как регулярное выражение:
([^"^\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: ответ Will выше, та же идея, что и этот. Надеюсь, этот ответ объясняет детали за сценой немного лучше:)
Пространство имен Microsoft.VisualBasic.FileIO (в Microsoft.VisualBasic.dll) содержит TextFieldParser, который можно использовать для разделения текста, разделенного пробелами. Он хорошо обрабатывает строки в кавычках (то есть "это один токен").
Обратите внимание, то, что в 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 может быть более кратким для ваших нужд.
[^\ Т]+\ т |"[^"]+"\ т
использование регулярных выражений определенно выглядит как лучшая ставка, однако эта просто возвращает всю строку. Я пытаюсь настроить это, но пока не очень удача.
string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");