7

私はこのような文字列を持っています

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

6 に答える 6

16

あなたがしていることのコンピュータ用語は字句解析です。この一般的なタスクの概要については、それをお読みください。

あなたの例に基づいて、単語を区切るために空白が必要だと推測していますが、引用符で囲まれたものは引用符なしの「単語」として扱われるべきです。

これを行う最も簡単な方法は、単語を正規表現として定義することです。

([^"^\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:上記のウィルの答えは、これと同じ考えです。うまくいけば、この回答が舞台裏の詳細をもう少しよく説明しています:)

于 2008-09-10T18:20:11.923 に答える
7

Microsoft.VisualBasic.FileIO名前空間(Microsoft.VisualBasic.dll内)には、スペースが不足しているテキストを分割するために使用できるTextFieldParserがあります。引用符で囲まれた文字列(つまり、「これは1つのトークンです」thisistokentwo)を適切に処理します。

DLLがVisualBasicと言っているからといって、VBプロジェクトでしか使用できないわけではないことに注意してください。フレームワーク全体の一部です。

于 2008-09-10T18:03:08.547 に答える
3

ステートマシンのアプローチがあります。

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

ネストされた引用符やエスケープなどのために簡単に拡張できます。as を返すとIEnumerable<string>、コードは必要な分だけ解析できます。input文字列は不変であるため、そのような怠惰なアプローチには実際の欠点はありません。そのため、全体を解析する前に変更されないことがわかります。

参照: http://en.wikipedia.org/wiki/Automata-Based_Programming

于 2008-09-10T20:12:41.450 に答える
0

また、正規表現を調べたい場合もあります。それはあなたを助けるかもしれません。これが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
于 2008-09-10T18:03:47.720 に答える
-1

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

正規表現を使用することは間違いなく最善の策のように見えますが、これは文字列全体を返すだけです。私はそれを微調整しようとしていますが、今のところあまり運がありません。

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");
于 2008-09-10T19:12:17.523 に答える
-1

Craigの言うとおりです。正規表現を使用してください。 Regex.Splitは、必要に応じてより簡潔になる場合があります。

于 2008-09-10T18:15:33.650 に答える