質問
このような文字列があります
/c SomeText\MoreText "Some Text\More Text\Lol" SomeText
トークン化したいのですが、スペースだけで分割することができません。私は機能するやや醜いパーサーを思いつきましたが、もっとエレガントなデザインを持っている人はいないだろうかと思っています。
ちなみにこれはC#です。
編集: 私の醜いバージョンは、醜いですが 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();
}
解決
あなたがやっていることを表すコンピューター用語は次のとおりです。 字句解析;この一般的なタスクの概要については、こちらをお読みください。
あなたの例に基づくと、単語を空白で区切る必要があると思いますが、引用符で囲まれたものは引用符なしの「単語」として扱われる必要があります。
これを行う最も簡単な方法は、単語を正規表現として定義することです。
([^"^\s]+)\s*|"([^"]+)"\s*
この式は、「単語」が、(1) 引用符ではなく、空白文字で囲まれた非空白文字、または (2) 引用符で囲まれた非引用符 (その後に空白文字) のいずれかであることを示します。目的のテキストを強調表示するために括弧を使用していることに注意してください。
この正規表現を使えば、アルゴリズムはシンプルになります。括弧で定義された次の「単語」をテキストで検索し、それを返します。それを「言葉」がなくなるまで繰り返します。
これは、VB.NET で私が思いついた最も単純な動作コードです。確認する必要があることに注意してください 両方 キャプチャする括弧が 2 セットあるため、データのグループになります。
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 があります。引用符内の文字列 (つまり、「this is one token」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
全体を解析するまでは変更されません。
正規表現についても検討してみるとよいでしょう。それはあなたを助けるかもしれません。これは 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");