13

テキストベースのダイスローラーを作っています。「2d10+5」のような文字列を受け取り、ロールの結果として文字列を返します。私の問題は、文字列を有用な部分に分割して情報に解析するトークナイザーに現れることです。

String[] tokens = message.split("(?=[dk\\+\\-])");

これは、奇妙で予期しない結果をもたらしています。何が原因なのか正確にはわかりません。それは、正規表現、私の誤解、または Java が単に Java である可能性があります。何が起こっているかは次のとおりです。

  • 3d6+4文字列配列を生成します[3, d6, +4]。正解です。
  • d%文字列配列を生成します[d%]。正解です。
  • d20文字列配列を生成します[d20]。正解です。
  • d%+3文字列配列を生成します[, d%, +3]。これは正しくありません。
  • d20+2文字列配列を生成します[, d20, +2]。これは正しくありません。

4 番目と 5 番目の例では、配列の先頭に余分な空の文字列が表示される原因となっている奇妙なことがあります。他の例が反証しているように、文字列の先頭に数字がないことではありません。パーセント記号やプラス記号の存在ではありません。

今のところ、空白文字列の for ループを続行しているだけですが、それは応急処置のようなものです。配列の先頭にある空白文字列の原因を知っている人はいますか? どうすれば修正できますか?

4

3 に答える 3

13

ソース コードを調べてみると、この動作の背後にある正確な問題がわかりました。

メソッドはString.split()内部的に を使用しPattern.split()ます。結果の配列を返す前の split メソッドは、最後に一致したインデックスをチェックするか、実際に一致するかどうかを確認します。最後に一致したインデックスが の場合、0パターンが文字列の先頭にある空の文字列と一致したか、まったく一致しなかったことを意味します。この場合、返される配列は、同じ要素を含む単一要素の配列です。

ソースコードは次のとおりです。

public String[] split(CharSequence input, int limit) {
        int index = 0;
        boolean matchLimited = limit > 0;
        ArrayList<String> matchList = new ArrayList<String>();
        Matcher m = matcher(input);

        // Add segments before each match found
        while(m.find()) {
            if (!matchLimited || matchList.size() < limit - 1) {
                String match = input.subSequence(index, m.start()).toString();
                matchList.add(match);

                // Consider this assignment. For a single empty string match
                // m.end() will be 0, and hence index will also be 0
                index = m.end();
            } else if (matchList.size() == limit - 1) { // last one
                String match = input.subSequence(index,
                                                 input.length()).toString();
                matchList.add(match);
                index = m.end();
            }
        }

        // If no match was found, return this
        if (index == 0)
            return new String[] {input.toString()};

        // Rest of them is not required

上記のコードの最後の条件 -index == 0が true の場合、単一の要素配列が入力文字列と共に返されます。

ここで、 が になる場合を考えてみましょindex0

  1. まったく一致しないとき。(すでにその条件の上のコメントにあるように)
  2. 一致が先頭にあり、一致した文字列の長さがの場合、ブロック内 (ループ内)0の index の値-ifwhile

    index = m.end();
    

    可能な一致文字列は空の文字列(長さ = 0) のみです。ここではまさにそうです。また、それ以上一致するものはありません。一致しない場合indexは、別のインデックスに更新されます。

したがって、あなたのケースを考慮してください:

  • の場合d%、最初のd. したがって、インデックス値は になります0。しかし、それ以上一致するものがないため、インデックス値は更新されず、if条件は になりtrue、元の文字列を持つ単一要素の配列を返します。

  • の前に 1 つ、 の前にd20+21 つ、2 つの一致があるためです。そのため、インデックス値が更新されるため、@Stema の回答で既に説明したように、文字列の最初の文字である区切り文字で分割された結果として空の文字列が含まれる上記のコードが返されます。d+ArrayList

したがって、必要な動作を取得するには (区切り文字が先頭にない場合にのみ区切り文字で分割されるため、正規表現パターンに否定的な後読みを追加できます):

"(?<!^)(?=[dk+-])"  // You don't need to escape + and hyphen(when at the end)

これは、文字クラスが続く空の文字列で分割されますが、文字列の先頭は前にありません。


"ad%"正規表現パターン -で文字列を分割する場合を考えてみましょう"a(?=[dk+-])"。これにより、最初の要素が空の文字列である配列が得られます。ここでの唯一の変更点は、空の文字列が次のように置き換えられることaです。

"ad%".split("a(?=[dk+-])");  // Prints - `[, d%]`

なんで?これは、一致する文字列の長さが1. したがって、最初の一致後のインデックス値 -はbutでm.end()はないため、単一の要素配列は返されません。01

于 2013-09-18T11:43:37.597 に答える
5

ケース 2 と 3 では起こらないことに驚いたので、ここでの本当の問題は

「d20」と「d%」の先頭に空の文字列ないのはなぜですか?

Rohit Jain が彼の詳細な分析で説明したように、これは、文字列の先頭で見つかった一致が 1 つだけで、match.end インデックスが 0 の場合に発生します。マッチ)。

問題は、d%+3分割している文字から始まることです。したがって、正規表現は最初の文字の前に一致し、最初に空の文字列が得られます。

後読みを追加して、式が文字列の先頭で一致しないようにして、そこで分割されないようにすることができます。

String[] tokens = message.split("(?<!^)(?=[dk\\+\\-])");

(?<!^)文字列の先頭にない場合、true の後読みアサーションです。

于 2013-09-18T11:22:23.560 に答える
0

分割ではなく単純なマッチングをお勧めします。

Matcher matcher = Pattern.compile("([1-9]*)(d[0-9%]+)([+-][0-9]+)?").matcher(string);
if(matcher.matches()) {
    String first = matcher.group(1);
    // etc
}

正規表現の保証はありませんが、うまくいくと思います...

于 2013-09-18T11:33:11.413 に答える