문제

숫자와 문자가 들어있는 끈이 있습니다. 나는 문자열을 인접한 자릿수 덩어리와 인접한 글자 덩어리로 나누고 싶습니다.

문자열 "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을 두 번 분할하는 것을 고려했습니다. 그런 다음 결과를 병합합니다. 나는 가독성에 해를 끼칠 것이기 때문에 이것으로부터 멀어졌다.

Regex 패턴이 가독성에 대한 주요 장애물이라는 것을 알기 때문에 의도적으로 Regex 패턴으로 이것을 해결하는 것을 피했습니다.

  • 디버거는 잘 처리하지 않습니다.
  • 그들은 소스 코드를 읽는 사람의 흐름을 방해합니다.
  • 초과 근무 정규식은 유기적으로 성장하고 괴물이됩니다.
  • 그들은 매우 직관적이지 않습니다.

내 질문은 다음과 같습니다.

  • 위의 코드의 가독성을 어떻게 개선 할 수 있습니까?
  • 더 좋은 방법이 있습니까? 이 문제를 우아하게 해결하는 Util 클래스.
  • 정규식 사용과 위에서 쓴 내용과 똑같은 것을 코딩하는 것 사이의 선을 어디에서 그리십니까?
  • Regexes의 가독성/유지 가능성을 어떻게 증가 시키나요?
도움이 되었습니까?

해결책

한 줄의 코드에서 문제를 해결한다면 Regexes를 기꺼이 사용 하시겠습니까?

// 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)");

Regex를 설명하기 위해 의견을 제시하면서, 비록 검색 솔루션 (또는 그 문제에 대한 다른 REGEX 솔루션)보다 읽을 수 있다고 생각합니다.

다른 팁

이 특정 작업에서는 항상 비슷한 것을 손으로 쓰는 대신에 Regex를 사용합니다. 위에서 당신이 한 코드는 적어도 나에게 간단한 정규 표현보다 읽기 쉬운 것입니다 ( (\d+|[^\d]+) 이 경우 내가 볼 수있는 한).

몇 줄을 초과하는 정규 표현을 작성하지 않을 수도 있습니다. 그것들은 일반적으로 읽을 수없고 이해하기 어려울 수 있습니다. 그러나 그들이 대체 할 수있는 코드도 마찬가지입니다! 구문 분석기는 거의 예쁘지 않으며 생성 된 (또는 필기) 파서를 이해하려고하는 것보다 원래 문법을 읽는 것이 더 좋습니다. 규칙적인 문법에 대한 간결한 설명 인 regexes에 대해서도 마찬가지입니다 (IMHO).

그래서, 나는 일반적으로 당신의 질문에서 주신 것처럼 코드에 찬성하여 Regexes를 금지하는 것이 끔찍한 어리석은 아이디어처럼 들립니다. 그리고 정기적 인 표현은 단지 도구 일뿐입니다. 다른 것이 텍스트 구문 분석의 더 나은 작업을 수행한다면 (예 : 실제 구문자, 일부 하위 문자 마법 등) 사용하십시오. 그러나 당신이 그들에게 불편 함을 느끼기 때문에 가능성을 버리지 마십시오. 다른 사람들은 그들과 대처하는 데 더 적은 문제가있을 수 있으며 모든 사람들이 배울 수 있습니다.

편집 : Mmyers의 의견 후 REGEX를 업데이트했습니다.

유틸리티 클래스의 경우 확인하십시오 java.util.scanner. 문제를 해결하는 방법에 대한 여러 가지 옵션이 있습니다. 귀하의 질문에 대해 몇 가지 의견이 있습니다.

디버거는 그들을 다루지 않습니다 (정규 표현)

REGEX가 작동하는지 여부는 데이터의 내용에 따라 다릅니다. 같은 성서를 구축하는 데 사용할 수있는 멋진 플러그인이 있습니다. QuickRex Eclipse의 경우 디버거가 실제로 데이터에 적합한 구문 분석기를 작성하는 데 도움이됩니까?

그들은 소스 코드를 읽는 사람의 흐름을 방해합니다.

나는 그것이 당신이 그들과 얼마나 편안한 지에 달려 있다고 생각합니다. 개인적으로, 나는 50 줄의 문자열 구문 분석 코드 이상의 합리적인 동정인을 읽고 싶습니다. 그러나 그것은 아마도 개인적인 일일 것입니다.

초과 근무 정규식은 유기적으로 성장하고 괴물이됩니다.

나는 그들이 할 것이라고 생각하지만, 아마도 그들이 초점을 맞추지 못하게하는 코드의 문제 일 것입니다. 소스 데이터의 복잡성이 증가하면 더 표현적인 솔루션이 필요한지 여부를 주시해야 할 것입니다 (아마도 ANTLR과 같은 파서 생성기).

그들은 매우 직관적이지 않습니다.

그들은 패턴 일치 언어입니다. 나는 그들이 그 맥락에서 꽤 직관적이라고 말할 것이다.

위의 코드의 가독성을 어떻게 개선 할 수 있습니까?

Regex를 사용하는 것 외에는 확실하지 않습니다.

더 좋은 방법이 있습니까? 이 문제를 우아하게 해결하는 Util 클래스.

위에서 언급 한 java.util.scanner.

정규식 사용과 위에서 쓴 내용과 똑같은 것을 코딩하는 것 사이의 선을 어디에서 그리십니까?

개인적으로 나는 합리적으로 간단한 것을 위해 Regex를 사용합니다.

Regexes의 가독성/유지 가능성을 어떻게 증가 시키나요?

확장하기 전에 신중하게 생각하고, 추가주의를 기울이면 코드와 Regex를 자세히 설명하여 무엇을하고 있는지 명확하게하십시오.

나는 이와 같은 것을 사용합니다 (경고, 테스트되지 않은 코드). 나에게 이것은 Regexps를 피하려고하는 것보다 훨씬 더 읽기 쉬운다. Regexps는 올바른 장소에서 사용될 때 훌륭한 도구입니다.

주석을 주석하고 의견에 입력 및 출력 값의 예를 제공하는 것도 도움이됩니다.

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

나는 Regex 나 자신에 대해 지나치게 미쳤지 않지만 이것은 그들이 실제로 물건을 단순화하는 경우처럼 보입니다. 당신이하고 싶은 것은 그것들을 고안 할 수있는 가장 작은 방법에 넣고, 적절하게 이름을 지은 다음, 모든 제어 코드를 다른 방법에 넣는 것입니다.

예를 들어, "숫자 또는 문자의 횡단 블록"메소드를 코딩 한 경우 발신자는 각 통화 결과를 인쇄하는 것만으로 매우 간단하고 간단한 루프가되며 호출 한 메소드는 잘 정의되어 있습니다. 구문에 대해 아무것도 몰랐더라도 Regex의 의도는 분명 할 것이며,이 방법은 시간이 지남에 따라 사람들이 그것을 막을 가능성이 없을 것입니다.

이것의 문제점은 Regex 도구가 매우 간단 하고이 용도로 잘 적응되어있어서 방법 호출을 정당화하기가 어렵다는 것입니다.

아직 올바른 코드를 게시 한 사람이 없으므로 촬영하겠습니다.

먼저 비록 검색 버전입니다. 나는 마지막으로 보이는 어떤 유형의 문자를 축적하기 위해 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;
}

이제 Regex 버전입니다. 이것은 기본적으로 Juha S.가 게시 한 것과 동일한 코드이지만 Regex는 실제로 작동합니다.

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

Regexes를 읽을 수 있도록 노력하는 한 가지 방법은 이름입니다. 제 생각에는 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]

awww, 누군가가 나를 코드로 이겼다. Regex 버전을 읽고 유지하기가 더 쉽다고 생각합니다. 또한 두 구현과 예상 출력 간의 출력 차이에 유의하십시오 ...

산출:

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"+"]");
    }

}

루프를 단순화하기 위해이 클래스를 사용할 수 있습니다.

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 센트

BTW이 클래스는 다른 맥락에서도 재사용 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top