5

数字と文字を含む文字列があります。文字列を連続した数字のチャンクと連続した文字のチャンクに分割したいと考えています。

文字列「34A312O5M444123A」を考えてみましょう。

出力したい: ["34", "A", "312", "O", "5", "M", "444123", "A"]

私は動作し、次のようなコードを持っています:

List<String> digitsAsElements(String str){
  StringBuilder digitCollector = new StringBuilder();

  List<String> output = new ArrayList<String>();

  for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);

    if (Character.isDigit(cChar))
       digitCollector.append(cChar);
    else{
      output.add(digitCollector.toString());
      output.add(""+cChar);

      digitCollector = new StringBuilder();
    }         
  }

  return output;
}

str を 2 回分割して、すべての数字のチャンクを含む配列とすべての文字のチャンクを含む配列を取得することを検討しました。次に、結果をマージします。可読性を損なうため、これを避けました。

正規表現パターンは読みやすさの大きな障害であることがわかったので、正規表現パターンでこれを解決することを意図的に避けました。

  • デバッガーはそれらをうまく処理しません。
  • それらは、誰かがソース コードを読む流れを妨げます。
  • 残業正規表現は有機的に成長し、モンスターになります。
  • 彼らは非常に直感的ではありません。

私の質問は次のとおりです。

  • 上記のコードの読みやすさを改善するにはどうすればよいですか?
  • これを行うより良い方法はありますか?この問題をエレガントに解決する Util クラス。
  • 正規表現を使用することと、私が上で書いたものに似たものをコーディングすることの境界線はどこにあるのでしょうか?
  • regExes の可読性/保守性をどのように向上させますか?
4

8 に答える 8

13

この特定のタスクでは、同様のものを手書きする代わりに、常に正規表現を使用します。上記のコードは、少なくとも私にとっては、単純な正規表現よりも読みにくいです ((\d+|[^\d]+)この場合、私が見る限り)。

数行を超える正規表現を書くことは避けたいと思うかもしれません。それらは、通常は判読不能で理解しにくいものである可能性がありますが、それらを置き換えることができるコードも同様です! パーサーはほとんどきれいではなく、通常は、生成された (または手書きの) パーサーを理解しようとするよりも、元の文法を読んだほうがよいでしょう。通常の文法の簡潔な説明にすぎない正規表現についても同じことが言えます(imho)。

したがって、一般的に、質問で与えられたようなコードを支持して正規表現を禁止することは、ひどくばかげた考えのように聞こえます。そして、正規表現は単なるツールであり、それ以下でもそれ以上でもありません。他の何かがテキストの解析に優れている場合 (たとえば、実際のパーサー、部分文字列マジックなど)、それを使用してください。しかし、あなたがそれらに不快感を覚えたからといって、可能性を捨てないでください。他の人はそれらに対処するのにそれほど問題がなく、すべての人が学ぶことができます.

編集: mmyers によるコメントの後に正規表現を更新しました。

于 2009-06-04T19:35:24.450 に答える
7

ユーティリティ クラスについては、java.util.Scannerを確認してください。問題を解決する方法については、いくつかのオプションがあります。あなたの質問に対していくつかコメントがあります。

デバッガーはそれら (正規表現) を適切に処理しません

正規表現が機能するかどうかは、データの内容によって異なります。Eclipse 用のQuickRExなど、正規表現を作成するのに役立つ便利なプラグインがいくつかありますが、デバッガーは実際にデータに適したパーサーを作成するのに役立ちますか?

それらは、誰かがソース コードを読む流れを妨げます。

それはあなたが彼らとどれだけ快適かによると思います。個人的には、50 行以上の文字列解析コードよりも妥当な正規表現を読みたいと思っていますが、それは個人的なことかもしれません。

残業正規表現は有機的に成長し、モンスターになります。

おそらくそうかもしれませんが、それはおそらく、彼らが住んでいるコードが焦点を合わせていないという問題です。ソース データの複雑さが増している場合は、より表現力豊かなソリューション (ANTLR のようなパーサー ジェネレーターなど) が必要かどうかに注意する必要があります。

彼らは非常に直感的ではありません。

それらはパターンマッチング言語です。その文脈では、彼らはかなり直感的だと思います。

上記のコードの読みやすさを改善するにはどうすればよいですか?

正規表現を使用する以外はわかりません。

これを行うより良い方法はありますか?この問題をエレガントに解決する Util クラス。

前述の java.util.Scanner.

正規表現を使用することと、私が上で書いたものに似たものをコーディングすることの境界線はどこにあるのでしょうか?

個人的には、合理的に単純なものには正規表現を使用します。

regExes の可読性/保守性をどのように向上させますか?

拡張する前に慎重に検討し、コードと正規表現を詳細にコメントアップして、何をしているのかが明確になるように細心の注意を払ってください。

于 2009-06-04T19:59:12.470 に答える
5

1 行のコードで問題を解決することを意味する場合、正規表現を使用しますか?

// Split at any position that's either:
// preceded by a digit and followed by a non-digit, or
// preceded by a non-digit and followed by a digit.
String[] parts = str.split("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)");

正規表現を説明するコメントがあるので、正規表現以外のソリューション (または他の正規表現ソリューション) よりも読みやすいと思います。

于 2009-06-05T00:50:05.703 に答える
2

このようなものを使用します(警告、テストされていないコード)。私にとって、これは正規表現を回避しようとするよりもはるかに読みやすいです。正規表現は、適切な場所で使用すると優れたツールになります。

メソッドにコメントを付けたり、コメントで入力値と出力値の例を提供したりすることも役に立ちます。

List<String> digitsAsElements(String str){
    Pattern p = Pattern.compile("(\\d+|\\w+)*");
    Matcher m = p.matcher(str);

    List<String> output = new ArrayList<String>();
    for(int i = 1; i <= m.groupCount(); i++) {
       output.add(m.group(i));
    }
    return output;
}
于 2009-06-04T19:54:40.980 に答える
1

ああ、誰かが私をコーディングに打ち負かしました。正規表現バージョンの方が読みやすく、保守しやすいと思います。また、2 つの実装と予想される出力との出力の違いに注意してください ...

出力:

digitsAsElements1("34A312O5MNI444123A") = [34, A, 312, O, 5, M, , N, , I, 444123, A]
digitsAsElements2("34A312O5MNI444123A") = [34, A, 312, O, 5, MNI, 444123, A]
Expected: [34, A, 312, O, 5, MN, 444123, A]

比較:

DigitsAsElements.java:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DigitsAsElements {

    static List<String> digitsAsElements1(String str){
        StringBuilder digitCollector = new StringBuilder();

        List<String> output = new ArrayList<String>();

        for (int i = 0; i < str.length(); i++){
          char cChar = str.charAt(i);

          if (Character.isDigit(cChar))
             digitCollector.append(cChar);
          else{
            output.add(digitCollector.toString());
            output.add(""+cChar);

            digitCollector = new StringBuilder();
          }         
        }

        return output;
      }

    static List<String> digitsAsElements2(String str){
        // Match a consecutive series of digits or non-digits
        final Pattern pattern = Pattern.compile("(\\d+|\\D+)");
        final Matcher matcher = pattern.matcher(str);

        final List<String> output = new ArrayList<String>();
        while (matcher.find()) {
            output.add(matcher.group());
        }

        return output;
      }

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("digitsAsElements(\"34A312O5MNI444123A\") = " +
                digitsAsElements1("34A312O5MNI444123A"));
        System.out.println("digitsAsElements2(\"34A312O5MNI444123A\") = " +
                digitsAsElements2("34A312O5MNI444123A"));
        System.out.println("Expected: [" +
                "34, A, 312, O, 5, MN, 444123, A"+"]");
    }

}
于 2009-06-04T19:58:47.960 に答える
1

ループを簡素化するために、このクラスを使用できます。

public class StringIterator implements Iterator<Character> {

    private final char[] chars;
    private int i;

    private StringIterator(char[] chars) {
        this.chars = chars;
    }

    public boolean hasNext() {
        return i < chars.length;
    }

    public Character next() {
        return chars[i++];
    }

    public void remove() {
        throw new UnsupportedOperationException("Not supported.");
    }

    public static Iterable<Character> of(String string) {
        final char[] chars = string.toCharArray();

        return new Iterable<Character>() {

            @Override
            public Iterator<Character> iterator() {
                return new StringIterator(chars);
            }
        };
    }
}

これで次のように書き換えることができます。

for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);
    ...
}

と:

for (Character cChar : StringIterator.of(str)) {
    ...
}

私の2セント

ところで、このクラスは他のコンテキストでも再利用可能です。

于 2009-06-04T20:04:08.217 に答える
1

私自身は正規表現にあまり夢中ではありませんが、これは正規表現が物事を本当に単純化するケースのようです。あなたがやりたいことは、それらをあなたが考案できる最小のメソッドに入れ、それに適切な名前を付けてから、すべての制御コードを別のメソッドに入れることです。

たとえば、「数字または文字のブロックをつかむ」メソッドをコーディングした場合、呼び出し元は各呼び出しの結果を出力するだけの非常に単純で単純なループになり、呼び出すメソッドは明確に定義されているため、構文について何も知らなくても、正規表現の意図は明らかであり、メソッドは制限されているため、時間の経過とともに混乱することはありません。

これに関する問題は、正規表現ツールが非常に単純で、この使用法にうまく適応しているため、これに対するメソッド呼び出しを正当化するのが難しいことです。

于 2009-06-04T20:06:37.400 に答える
1

まだ誰も正しいコードを投稿していないようなので、試してみます。

最初の非正規表現バージョン。最後に表示された文字の種類 (数字または非数字) を蓄積するために StringBuilder を使用していることに注意してください。状態が変化した場合は、その内容をリストにダンプし、新しい StringBuilder を開始します。このようにして、連続する非数字は、連続する数字と同じようにグループ化されます。

static List<String> digitsAsElements(String str) {
    StringBuilder collector = new StringBuilder();

    List<String> output = new ArrayList<String>();
    boolean lastWasDigit = false;
    for (int i = 0; i < str.length(); i++) {
        char cChar = str.charAt(i);

        boolean isDigit = Character.isDigit(cChar);
        if (isDigit != lastWasDigit) {
            if (collector.length() > 0) {
                output.add(collector.toString());
                collector = new StringBuilder();
            }
            lastWasDigit = isDigit;
        }
        collector.append(cChar);
    }
    if (collector.length() > 0)
        output.add(collector.toString());

    return output;
}

今正規表現のバージョン。これは基本的に、Juha S. によって投稿されたものと同じコードですが、正規表現は実際に機能します。

private static final Pattern DIGIT_OR_NONDIGIT_STRING =
        Pattern.compile("(\\d+|[^\\d]+)");
static List<String> digitsAsElementsR(String str) {
    // Match a consecutive series of digits or non-digits
    final Matcher matcher = DIGIT_OR_NONDIGIT_STRING.matcher(str);
    final List<String> output = new ArrayList<String>();
    while (matcher.find()) {
        output.add(matcher.group());
    }
    return output;
}

正規表現を読みやすくする方法の 1 つは、その名前です。私DIGIT_OR_NONDIGIT_STRING(プログラマー) が考えていることをかなりうまく伝えていると思います。

public static void main(String[] args) {
    System.out.println(digitsAsElements( "34A312O5MNI444123A"));
    System.out.println(digitsAsElementsR("34A312O5MNI444123A"));
}

プリント:

[34、A、312、O、5、MNI、444123、A]
[34、A、312、O、5、MNI、444123、A]
于 2009-06-04T20:30:30.377 に答える