문자열을 나누는 방법과 구분 기호를 유지하는 방법은 무엇입니까?


243

여러 줄로 구분 된 여러 줄 문자열이 있습니다.

(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)

이 문자열을을 사용하여 부분으로 나눌 수 String.split있지만 구분 기호 정규 표현식과 일치하는 실제 문자열을 얻을 수없는 것 같습니다.

즉, 이것이 내가 얻는 것입니다.

  • Text1
  • Text2
  • Text3
  • Text4

이것이 내가 원하는거야

  • Text1
  • DelimiterA
  • Text2
  • DelimiterC
  • Text3
  • DelimiterB
  • Text4

구분 기호 정규식을 사용하여 문자열을 분할하고 구분 기호를 유지하는 JDK 방법이 있습니까?


구분 기호를 어디에 유지하고 싶습니까? 단어와 함께 또는 분리? 첫 번째 경우, 앞 또는 뒤에 나오는 단어에 첨부 하시겠습니까? 두 번째 경우, 내 대답은 당신이 필요로하는 것입니다.
— PhiLho

원하는 것을 달성하는 데 도움이되는 클래스를 구현했습니다. 아래를 참조하십시오
— VonC

답변:


366

Lookahead 및 Lookbehind를 사용할 수 있습니다. 이처럼 :

System.out.println(Arrays.toString("a;b;c;d".split("(?<=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("(?=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("((?<=;)|(?=;))")));

그리고 당신은 얻을 것이다 :

[a;, b;, c;, d]
[a, ;b, ;c, ;d]
[a, ;, b, ;, c, ;, d]

마지막은 당신이 원하는 것입니다.

((?<=;)|(?=;))전 ;또는 후에 빈 문자를 선택하는 것과 같습니다 ;.

도움이 되었기를 바랍니다.

가독성에 대한 Fabian Steeg 의견 편집 이 유효합니다. 가독성은 항상 RegEx의 문제입니다. 한 가지, 나는 이것을 완화시키는 데 도움을주기 위해 정규 표현식이하는 것을 나타내는 이름을 가진 변수를 만들고 Java String 형식을 사용하여 도움을줍니다. 이처럼 :

static public final String WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))";
...
public void someMethod() {
...
final String[] aEach = "a;b;c;d".split(String.format(WITH_DELIMITER, ";"));
...
}
...

이것은 약간 도움이됩니다. :-디


2
아주 좋아요! 여기서 우리는 정규 표현식의 힘을 다시 볼 수 있습니다 !!
— 조지

1
내가 있고 StringTokenizer를 위해 거기로 구분 기호를 포함 할 수있는 방법이 있었으면 좋겠어하지만 니스, 문자열 # 분할이 할 수있는 방법이 볼 수 - split(";", true)훨씬 더 읽을보다 것이다 split("((?<=;)|(?=;))").
— Fabian Steeg

3
String.format(WITH_DELIMITER, ";");형식은 정적 메서드이므로 다음 과 같아야합니다 .
— john16384

8
방금 마주 친 한 가지 합병증은 가변 길이 구분 기호 (예 [\\s,]+:)로 완전히 일치시키고 싶습니다. 중간에 일치하는 것을 피하기 위해 추가적인 부정적인 모습이 필요하기 때문에 필요한 정규 표현식이 더 길어집니다 (예 : 뒤에). (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
— Michał Politowski

3
두 개의 분리 문자로 나누려면 어떻게해야합니까? 의 말을하자 ';' 또는 '.'
— 기적 doh

78

둘러보기를 사용하고 너비가 0 인 일치로 분할하려고합니다. 여기 몇 가지 예가 있어요.

public class SplitNDump {
    static void dump(String[] arr) {
        for (String s : arr) {
            System.out.format("[%s]", s);
        }
        System.out.println();
    }
    public static void main(String[] args) {
        dump("1,234,567,890".split(","));
        // "[1][234][567][890]"
        dump("1,234,567,890".split("(?=,)"));   
        // "[1][,234][,567][,890]"
        dump("1,234,567,890".split("(?<=,)"));  
        // "[1,][234,][567,][890]"
        dump("1,234,567,890".split("(?<=,)|(?=,)"));
        // "[1][,][234][,][567][,][890]"

        dump(":a:bb::c:".split("(?=:)|(?<=:)"));
        // "[][:][a][:][bb][:][:][c][:]"
        dump(":a:bb::c:".split("(?=(?!^):)|(?<=:)"));
        // "[:][a][:][bb][:][:][c][:]"
        dump(":::a::::b  b::c:".split("(?=(?!^):)(?<!:)|(?!:)(?<=:)"));
        // "[:::][a][::::][b  b][::][c][:]"
        dump("a,bb:::c  d..e".split("(?!^)\\b"));
        // "[a][,][bb][:::][c][  ][d][..][e]"

        dump("ArrayIndexOutOfBoundsException".split("(?<=[a-z])(?=[A-Z])"));
        // "[Array][Index][Out][Of][Bounds][Exception]"
        dump("1234567890".split("(?<=\\G.{4})"));   
        // "[1234][5678][90]"

        // Split at the end of each run of letter
        dump("Boooyaaaah! Yippieeee!!".split("(?<=(?=(.)\\1(?!\\1))..)"));
        // "[Booo][yaaaa][h! Yipp][ieeee][!!]"
    }
}

그리고 그렇습니다, 그것은 마지막 패턴에서 삼중으로 주장 된 주장입니다.

관련 질문

또한보십시오


1
이것은 비교적 간단한 표현식에만 적용됩니다. 나는 모든 실수를 나타내는 정규 표현식으로 이것을 사용하려고하는 "뒤에 보이는 그룹에는 명백한 최대 길이가 없습니다"라는 메시지가 나타납니다.
— daveagp


30

정규 표현식과 관련이없는 매우 순진한 해결책은 구분 기호에 쉼표를 가정하여 구분 기호에 문자열 대체를 수행하는 것입니다.

string.replace(FullString, "," , "~,~")

tilda (~)를 고유 한 구분 기호로 바꿀 수있는 곳.

그런 다음 새 구분 기호를 나누면 원하는 결과를 얻을 수 있다고 생각합니다.


24
import java.util.regex.*;
import java.util.LinkedList;

public class Splitter {
    private static final Pattern DEFAULT_PATTERN = Pattern.compile("\\s+");

    private Pattern pattern;
    private boolean keep_delimiters;

    public Splitter(Pattern pattern, boolean keep_delimiters) {
        this.pattern = pattern;
        this.keep_delimiters = keep_delimiters;
    }
    public Splitter(String pattern, boolean keep_delimiters) {
        this(Pattern.compile(pattern==null?"":pattern), keep_delimiters);
    }
    public Splitter(Pattern pattern) { this(pattern, true); }
    public Splitter(String pattern) { this(pattern, true); }
    public Splitter(boolean keep_delimiters) { this(DEFAULT_PATTERN, keep_delimiters); }
    public Splitter() { this(DEFAULT_PATTERN); }

    public String[] split(String text) {
        if (text == null) {
            text = "";
        }

        int last_match = 0;
        LinkedList<String> splitted = new LinkedList<String>();

        Matcher m = this.pattern.matcher(text);

        while (m.find()) {

            splitted.add(text.substring(last_match,m.start()));

            if (this.keep_delimiters) {
                splitted.add(m.group());
            }

            last_match = m.end();
        }

        splitted.add(text.substring(last_match));

        return splitted.toArray(new String[splitted.size()]);
    }

    public static void main(String[] argv) {
        if (argv.length != 2) {
            System.err.println("Syntax: java Splitter <pattern> <text>");
            return;
        }

        Pattern pattern = null;
        try {
            pattern = Pattern.compile(argv[0]);
        }
        catch (PatternSyntaxException e) {
            System.err.println(e);
            return;
        }

        Splitter splitter = new Splitter(pattern);

        String text = argv[1];
        int counter = 1;
        for (String part : splitter.split(text)) {
            System.out.printf("Part %d: \"%s\"\n", counter++, part);
        }
    }
}

/*
    Example:
    > java Splitter "\W+" "Hello World!"
    Part 1: "Hello"
    Part 2: " "
    Part 3: "World"
    Part 4: "!"
    Part 5: ""
*/

나는 앞뒤로 빈 요소를 얻는 다른 방법을 좋아하지 않습니다. 분리 문자는 일반적으로 문자열의 시작 또는 끝에 있지 않으므로 두 개의 양호한 배열 슬롯을 낭비하게됩니다.

편집 : 고정 된 경우. 테스트 사례가있는 주석 처리 된 소스는 다음에서 찾을 수 있습니다. http://snippets.dzone.com/posts/show/6453


와후 ... 참여해 주셔서 감사합니다! 재미있는 접근법. 나는 그것이 일관되게 도움이 될 수 있는지 확실하지 않지만 (때로는 구분 기호가 있고 때로는 그렇지 않습니다) 노력에 +1합니다. 그러나 여전히 제한 사례 (빈 값 또는 null 값)를 올바르게 처리해야합니다.
— VonC

이 강의를 제대로 강화하고 철저하게 문서화하고 findbugs 및 checkstyle을 사용한 다음 스 니펫 웹 사이트에 게시하십시오 (수 많은 코드로이 페이지를 어지럽히 지 않기 위해)
— VonC

당신은 도전을 이겼습니다! 축하합니다! 아시다시피 코드 챌린지 스레드에는 특별한 점이나 배지가 없습니다. (한숨) : stackoverflow.com/questions/172184 . 그러나이 기여에 감사드립니다.
— VonC

@VonC 대부분의 경우 null논쟁에 NPE를 던지는 것이 올바른 길입니다. 자동으로 처리하면 나중에 오류가 표시됩니다.
— maaartinus

@maaartinus 동의합니다.하지만 NPE보다 더 사용자 친화적 인 메시지를 보내고 싶은 경우가 있습니다.
— VonC

11

늦게 도착했지만 원래 질문으로 돌아가서 둘러보기를 사용하지 않는 이유는 무엇입니까?

Pattern p = Pattern.compile("(?<=\\w)(?=\\W)|(?<=\\W)(?=\\w)");
System.out.println(Arrays.toString(p.split("'ab','cd','eg'")));
System.out.println(Arrays.toString(p.split("boo:and:foo")));

산출:

[', ab, ',', cd, ',', eg, ']
[boo, :, and, :, foo]

편집 : 위의 내용은 해당 코드를 실행할 때 명령 줄에 나타나는 내용이지만 약간 혼란 스럽습니다. 어떤 쉼표가 결과의 일부이고 어떤 쉼표가 추가되었는지 추적하기는 어렵습니다 Arrays.toString(). SO의 구문 강조는 도움이되지 않습니다. 강조 표시 가 저를 대신하지 않고 나와 함께 작동하도록하기 위해 소스 코드에서 이러한 배열을 어떻게 선언했는지 보여줍니다.

{ "'", "ab", "','", "cd", "','", "eg", "'" }
{ "boo", ":", "and", ":", "foo" }

나는 그것이 더 읽기 쉽기를 바랍니다. @finnw 감사합니다.


나는 그것이 틀린 것처럼 보인다는 것을 알고있다. 사실 1 년 후 바로 지금 돌아 왔을 때 그것은 나에게 잘못되었다. 샘플 입력이 잘못 선택되었습니다. 글을 수정하고 내용을 명확히하려고합니다.
— Alan Moore


10

나는 이것이 매우 오래된 질문이라는 것을 알고 있으며 대답도 받아 들여졌습니다. 그러나 여전히 원래 질문에 대한 간단한 답변을 제출하고 싶습니다. 이 코드를 고려하십시오.

String str = "Hello-World:How\nAre You&doing";
inputs = str.split("(?!^)\\b");
for (int i=0; i<inputs.length; i++) {
   System.out.println("a[" + i + "] = \"" + inputs[i] + '"');
}

산출:

a[0] = "Hello"
a[1] = "-"
a[2] = "World"
a[3] = ":"
a[4] = "How"
a[5] = "
"
a[6] = "Are"
a[7] = " "
a[8] = "You"
a[9] = "&"
a[10] = "doing"

텍스트의 시작 부분을 제외하고 단어 경계 \b를 사용하여 단어를 구분합니다 .


1
+1 가장 좋은 답변입니다. 그러나 영숫자 문자열의 영숫자 구분 기호에는 작동하지 않습니다.
— Casimir et Hippolyte

@CasimiretHippolyte : 공감 해 주셔서 감사합니다. 작동하지 않는 샘플 입력을 제공해 주시겠습니까?
— anubhava 2016 년

2
예를 들어이 작동하지 않습니다 abcdef와 de구분 기호로,하지만 당신은 사용하여 문제를 해결할 수 있습니다(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
— 카시미르 등이 풀리 테

1
문자열이 구분자로 끝나는 경우 결과에서 빈 문자열을 피하기위한 첫 번째 주장을 참고하십시오.(?!^|$)
— Casimir et Hippolyte


9

나는 위의 답변을 보았고 정직하게도 만족스럽지 않습니다. 당신이하고 싶은 것은 본질적으로 Perl split 기능을 모방하는 것입니다. 왜 Java가 이것을 허용하지 않고 어딘가에 join () 메소드를 가지고 있습니까? 당신은 이것을 위해 실제로 수업이 필요하지 않습니다. 그저 기능 일뿐입니다. 이 샘플 프로그램을 실행하십시오.

이전 답변 중 일부는 과도한 null 검사가있어 최근에 질문에 대한 답변을 썼습니다.

https://stackoverflow.com/users/18393/cletus

어쨌든 코드 :

public class Split {
    public static List<String> split(String s, String pattern) {
        assert s != null;
        assert pattern != null;
        return split(s, Pattern.compile(pattern));
    }

    public static List<String> split(String s, Pattern pattern) {
        assert s != null;
        assert pattern != null;
        Matcher m = pattern.matcher(s);
        List<String> ret = new ArrayList<String>();
        int start = 0;
        while (m.find()) {
            ret.add(s.substring(start, m.start()));
            ret.add(m.group());
            start = m.end();
        }
        ret.add(start >= s.length() ? "" : s.substring(start));
        return ret;
    }

    private static void testSplit(String s, String pattern) {
        System.out.printf("Splitting '%s' with pattern '%s'%n", s, pattern);
        List<String> tokens = split(s, pattern);
        System.out.printf("Found %d matches%n", tokens.size());
        int i = 0;
        for (String token : tokens) {
            System.out.printf("  %d/%d: '%s'%n", ++i, tokens.size(), token);
        }
        System.out.println();
    }

    public static void main(String args[]) {
        testSplit("abcdefghij", "z"); // "abcdefghij"
        testSplit("abcdefghij", "f"); // "abcde", "f", "ghi"
        testSplit("abcdefghij", "j"); // "abcdefghi", "j", ""
        testSplit("abcdefghij", "a"); // "", "a", "bcdefghij"
        testSplit("abcdefghij", "[bdfh]"); // "a", "b", "c", "d", "e", "f", "g", "h", "ij"
    }
}

혼란 스러워요 : Java에는 split () 메소드가 있습니다.이 메소드는 Perl의 모델이지만 훨씬 덜 강력합니다. 여기서의 문제는 Java의 split ()이 구분 기호를 반환 할 수있는 방법을 제공하지 않는다는 것입니다. 괄호를 캡처하여 정규 표현식을 묶어 Perl에서 얻을 수 있습니다.
— Alan Moore


7

StringTokenizer라는 아이디어는 Enumerable이기 때문에 좋아합니다.
그러나 그것은 또한 더 이상 사용되지 않으며, 지루한 String []을 리턴하는 String.split으로 대체됩니다 (구분자를 포함하지 않습니다).

그래서 Iterable 인 StringTokenizerEx를 구현했으며 문자열을 분할하는 데 실제 정규 표현식이 필요합니다.

진정한 정규 표현식은 구분 기호를 형성하기 위해 반복되는 '문자 시퀀스'가 아님을 의미합니다
.

[o], '', [o], '', [o]

그러나 정규 표현식 o +는 "aooob"를 분할 할 때 예상 결과를 반환합니다

[], 'a', [ooo], 'b', []

이 StringTokenizerEx를 사용하려면

final StringTokenizerEx aStringTokenizerEx = new StringTokenizerEx("boo:and:foo", "o+");
final String firstDelimiter = aStringTokenizerEx.getDelimiter();
for(String aString: aStringTokenizerEx )
{
    // uses the split String detected and memorized in 'aString'
    final nextDelimiter = aStringTokenizerEx.getDelimiter();
}

이 클래스의 코드는 DZone Snippets 에서 사용할 수 있습니다 .

코드 챌린지 응답 (테스트 케이스가 포함 된 하나의 자체 포함 클래스)에 대해 평소와 같이 복사하여 ( 'src / test'디렉토리에) 붙여 넣기 를 실행하십시오 . main () 메소드는 다양한 사용법을 보여줍니다.


참고 : (2009 년 말 편집)

Final Thoughts : Java Puzzler : Splitting Hairs 기사 는 기괴한 동작을 설명하는 훌륭한 작업을 수행합니다 String.split().
조쉬 블로흐 (Josh Bloch)도이 기사에 대한 답변으로 다음과 같이 논평했다.

예, 이것은 고통입니다. FWIW는 Perl과의 호환성이라는 아주 좋은 이유로 이루어졌습니다.
그것을 한 사람은 Mike "madbot"McCloskey이며, 현재 Google에서 우리와 함께 일합니다. Mike는 Java의 정규 표현식이 30K Perl 정규 표현식 테스트를 거의 모두 통과하고 더 빠르게 실행되도록했습니다.

Google 공통 라이브러리 Guava 에는 다음과 같은 스플리터도 포함되어 있습니다.

  • 사용하기 더 간단
  • Google이 관리하며 귀하가 아닌

따라서 체크 아웃 할 가치가 있습니다. 자신의에서 초기 거친 문서 (PDF) :

JDK에는 다음이 있습니다.

String[] pieces = "foo.bar".split("\\.");

정확히 무엇을 원한다면 이것을 사용하는 것이 좋습니다 :-정규 표현식-배열 결과-빈 조각을 처리하는 방법

미니 퍼즐 : ", a ,, b,". split ( ",") 반환 ...

(a) "", "a", "", "b", ""
(b) null, "a", null, "b", null
(c) "a", null, "b"
(d) "a", "b"
(e) None of the above

답 : (e) 위의 어느 것도 아닙니다.

",a,,b,".split(",")
returns
"", "a", "", "b"

후행 빈 용기 만 건너 뜁니다! (누구를 피하는 해결 방법을 알고있는 사람은 누구입니까?)

어쨌든 Splitter는 더 유연합니다. 기본 동작은 단순합니다.

Splitter.on(',').split(" foo, ,bar, quux,")
--> [" foo", " ", "bar", " quux", ""]

추가 기능을 원하면 요청하십시오!

Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split(" foo, ,bar, quux,")
--> ["foo", "bar", "quux"]

구성 방법의 순서는 중요하지 않습니다. 분할하는 동안 빈을 확인하기 전에 트리밍이 발생합니다.



6

3 번째 aurgument를 "true"로 전달하십시오. 분리 문자도 리턴합니다.

StringTokenizer(String str, String delimiters, true);

4

다음은 Pattern#split지원되지 않는 가변 길이 패턴과 일관 되고 작동 하는 간단한 깔끔한 구현입니다 . 사용이 더 쉽습니다. @cletus에서 제공 하는 솔루션 과 유사합니다 .

public static String[] split(CharSequence input, String pattern) {
    return split(input, Pattern.compile(pattern));
}

public static String[] split(CharSequence input, Pattern pattern) {
    Matcher matcher = pattern.matcher(input);
    int start = 0;
    List<String> result = new ArrayList<>();
    while (matcher.find()) {
        result.add(input.subSequence(start, matcher.start()).toString());
        result.add(matcher.group());
        start = matcher.end();
    }
    if (start != input.length()) result.add(input.subSequence(start, input.length()).toString());
    return result.toArray(new String[0]);
}

나는 여기서 null 점검을 Pattern#split하지 않고, 왜, 내가해야합니까?. if끝에는 마음에 들지 않지만와 일치해야합니다 Pattern#split. 그렇지 않으면 입력 문자열이 패턴으로 끝나는 경우 무조건 추가하여 결과의 ​​마지막 요소로 빈 문자열이됩니다.

내가 함께 일관성을 위해 문자열 []로 변환 Pattern#split내가 사용 new String[0]하는 대신 new String[result.size()], 참조 여기 이유에 대해.

내 테스트는 다음과 같습니다.

@Test
public void splitsVariableLengthPattern() {
    String[] result = Split.split("/foo/$bar/bas", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar", "/bas" }, result);
}

@Test
public void splitsEndingWithPattern() {
    String[] result = Split.split("/foo/$bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar" }, result);
}

@Test
public void splitsStartingWithPattern() {
    String[] result = Split.split("$foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "", "$foo", "/bar" }, result);
}

@Test
public void splitsNoMatchesPattern() {
    String[] result = Split.split("/foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/bar" }, result);
}

2

작업 버전도 게시합니다 (먼저 Markus와 유사 함).

public static String[] splitIncludeDelimeter(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    int now, old = 0;
    while(matcher.find()){
        now = matcher.end();
        list.add(text.substring(old, now));
        old = now;
    }

    if(list.size() == 0)
        return new String[]{text};

    //adding rest of a text as last element
    String finalElement = text.substring(old);
    list.add(finalElement);

    return list.toArray(new String[list.size()]);
}

그리고 여기에 두 번째 솔루션이 있으며 첫 번째 솔루션보다 50 % 더 빠릅니다.

public static String[] splitIncludeDelimeter2(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    StringBuffer stringBuffer = new StringBuffer();
    while(matcher.find()){
        matcher.appendReplacement(stringBuffer, matcher.group());
        list.add(stringBuffer.toString());
        stringBuffer.setLength(0); //clear buffer
    }

    matcher.appendTail(stringBuffer); ///dodajemy reszte  ciagu
    list.add(stringBuffer.toString());

    return list.toArray(new String[list.size()]);
}

2

정규식을 사용하는 또 다른 후보 솔루션. 토큰 순서를 유지하고 같은 유형의 여러 토큰을 연속으로 정확하게 일치시킵니다. 단점은 정규 표현식이 불쾌하다는 것입니다.

package javaapplication2;

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

public class JavaApplication2 {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String num = "58.5+variable-+98*78/96+a/78.7-3443*12-3";

        // Terrifying regex:
        //  (a)|(b)|(c) match a or b or c
        // where
        //   (a) is one or more digits optionally followed by a decimal point
        //       followed by one or more digits: (\d+(\.\d+)?)
        //   (b) is one of the set + * / - occurring once: ([+*/-])
        //   (c) is a sequence of one or more lowercase latin letter: ([a-z]+)
        Pattern tokenPattern = Pattern.compile("(\\d+(\\.\\d+)?)|([+*/-])|([a-z]+)");
        Matcher tokenMatcher = tokenPattern.matcher(num);

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

        while (!tokenMatcher.hitEnd()) {
            if (tokenMatcher.find()) {
                tokens.add(tokenMatcher.group());
            } else {
                // report error
                break;
            }
        }

        System.out.println(tokens);
    }
}

샘플 출력 :

[58.5, +, variable, -, +, 98, *, 78, /, 96, +, a, /, 78.7, -, 3443, *, 12, -, 3]

1

Java API 에서이 기능을 수행하는 기존 함수를 알지 못하지만 (존재하지 않음) 여기에는 자체 구현이 있습니다 (하나 이상의 구분 기호가 단일 토큰으로 반환됩니다. 원하는 경우 각 구분 기호는 별도의 토큰으로 반환되므로 약간의 적응이 필요합니다.

static String[] splitWithDelimiters(String s) {
    if (s == null || s.length() == 0) {
        return new String[0];
    }
    LinkedList<String> result = new LinkedList<String>();
    StringBuilder sb = null;
    boolean wasLetterOrDigit = !Character.isLetterOrDigit(s.charAt(0));
    for (char c : s.toCharArray()) {
        if (Character.isLetterOrDigit(c) ^ wasLetterOrDigit) {
            if (sb != null) {
                result.add(sb.toString());
            }
            sb = new StringBuilder();
            wasLetterOrDigit = !wasLetterOrDigit;
        }
        sb.append(c);
    }
    result.add(sb.toString());
    return result.toArray(new String[0]);
}


1

패턴과 매처를 사용하는 것이 좋습니다. 정규식은 String.split에서 사용하는 것보다 다소 복잡해야합니다.


+1, 이것이 올바른 방법입니다. StringTokenizer는 구분 기호를 캡처 그룹에 배치하면 구분 기호를 출력하지만 기본적으로 사용되지 않습니다. split ()과 함께 lookahead를 사용하는 것은 허용 된 답변의 의견에 요약 된 이유로 해킹입니다. 주로 둘 이상의 구분 기호가있을 때 혼란이됩니다. 그러나 Pattern and Matcher를 사용하면 몇 줄에 실제 토크 나이저를 사용할 수 있습니다.
— johncip

1

나는 그것이 가능하지 않다고 생각 String#split하지만 StringTokenizer, 구분 기호를 정규식으로 정의 할 수는 없지만 한 자리 문자 클래스로만 정의 할 수는 있지만를 사용할 수는 있습니다 .

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims

거기에 구분 기호를 지정하기 위해 정규식을 정의 할 수 없습니다.
— Daniel Rikowski 님

1
그러나 StringTokenizer는 단일 문자 분리 문자 만 허용합니다.
— Michael Borgwardt

1

여유가 있다면 Java의 replace (CharSequence target, CharSequence replacement) 메소드를 사용하고 분리 할 다른 구분자를 채우십시오. 예 : 문자열 "boo : and : foo"를 분할하고 오른쪽 문자열에 ':'을 유지하고 싶습니다.

String str = "boo:and:foo";
str = str.replace(":","newdelimiter:");
String[] tokens = str.split("newdelimiter");

중요 사항 : 이것은 문자열에 "newdelimiter"가 더 이상없는 경우에만 작동합니다! 따라서 일반적인 솔루션이 아닙니다. 그러나 CharSequence를 알고 있으면 String에 나타나지 않을 것입니다. 이것은 매우 간단한 해결책입니다.



0

빠른 답변 : \ b와 같은 비 물리적 경계를 사용하여 분할하십시오. 나는 그것이 작동하는지 (PHP와 JS에서 사용되는지) 시험하려고 노력할 것이다.

가능하고 일종의 작업이지만 너무 많이 분리 될 수 있습니다. 실제로 분할하려는 문자열과 필요한 결과에 따라 다릅니다. 자세한 내용을 알려 주시면 더 나은 도움을 드리겠습니다.

다른 방법은 구분 기호를 캡처하고 (변수로 가정) 나중에 결과에 추가하여 자신의 분할을 수행하는 것입니다.

나의 빠른 테스트 :

String str = "'ab','cd','eg'";
String[] stra = str.split("\\b");
for (String s : stra) System.out.print(s + "|");
System.out.println();

결과:

'|ab|','|cd|','|eg|'|

조금 너무 ... :-)



0

불통 Pattern.split ()는 리스트에 매칭 패턴을 포함하는

추가

// add match to the list
        matchList.add(input.subSequence(start, end).toString());

전체 소스

public static String[] inclusiveSplit(String input, String re, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<String>();

    Pattern pattern = Pattern.compile(re);
    Matcher m = pattern.matcher(input);

    // Add segments before each match found
    while (m.find()) {
        int end = m.end();
        if (!matchLimited || matchList.size() < limit - 1) {
            int start = m.start();
            String match = input.subSequence(index, start).toString();
            matchList.add(match);
            // add match to the list
            matchList.add(input.subSequence(start, end).toString());
            index = end;
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index, input.length())
                    .toString();
            matchList.add(match);
            index = end;
        }
    }

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

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize - 1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}


0

위의 코드 중 일부를 기반으로하는 그루비 버전이 있습니다. 어쨌든 짧습니다. 머리와 꼬리를 조건부로 포함합니다 (비어 있지 않은 경우). 마지막 부분은 데모 / 테스트 사례입니다.

List splitWithTokens(str, pat) {
    def tokens=[]
    def lastMatch=0
    def m = str=~pat
    while (m.find()) {
      if (m.start() > 0) tokens << str[lastMatch..<m.start()]
      tokens << m.group()
      lastMatch=m.end()
    }
    if (lastMatch < str.length()) tokens << str[lastMatch..<str.length()]
    tokens
}

[['<html><head><title>this is the title</title></head>',/<[^>]+>/],
 ['before<html><head><title>this is the title</title></head>after',/<[^>]+>/]
].each { 
   println splitWithTokens(*it)
}


0

그럼에도 불구하고 작동하는 매우 순진하고 비효율적 인 솔루션 문자열에서 split을 두 번 사용한 다음 두 배열을 연결

String temp[]=str.split("\\W");
String temp2[]=str.split("\\w||\\s");
int i=0;
for(String string:temp)
System.out.println(string);
String temp3[]=new String[temp.length-1];
for(String string:temp2)
{
        System.out.println(string);
        if((string.equals("")!=true)&&(string.equals("\\s")!=true))
        {
                temp3[i]=string;
                i++;
        }
//      System.out.println(temp.length);
//      System.out.println(temp2.length);
}
System.out.println(temp3.length);
String[] temp4=new String[temp.length+temp3.length];
int j=0;
for(i=0;i<temp.length;i++)
{
        temp4[j]=temp[i];
        j=j+2;
}
j=1;
for(i=0;i<temp3.length;i++)
{
        temp4[j]=temp3[i];
        j+=2;
}
for(String s:temp4)
System.out.println(s);

0
    String expression = "((A+B)*C-D)*E";
    expression = expression.replaceAll("\\+", "~+~");
    expression = expression.replaceAll("\\*", "~*~");
    expression = expression.replaceAll("-", "~-~");
    expression = expression.replaceAll("/+", "~/~");
    expression = expression.replaceAll("\\(", "~(~"); //also you can use [(] instead of \\(
    expression = expression.replaceAll("\\)", "~)~"); //also you can use [)] instead of \\)
    expression = expression.replaceAll("~~", "~");
    if(expression.startsWith("~")) {
        expression = expression.substring(1);
    }

    String[] expressionArray = expression.split("~");
    System.out.println(Arrays.toString(expressionArray));

regexp를 사용하면 다음과 같습니다.Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
— Tsolak Barseghyan

0

이 질문의 미묘한 점 중 하나는 "선행 구분 기호"질문과 관련이 있습니다. 토큰과 구분 기호가 결합 된 경우 토큰으로 시작하는지 구분 기호로 시작해야하는지 알아야합니다. 물론 선행 탈회를 버려야한다고 가정 할 수 있지만 이것은 정당하지 않은 가정으로 보입니다. 당신은 또한 후행 delim 여부를 알고 싶을 수도 있습니다. 이에 따라 두 개의 부울 플래그가 설정됩니다.

Groovy로 작성되었지만 Java 버전은 상당히 분명해야합니다.

            String tokenRegex = /[\p{L}\p{N}]+/ // a String in Groovy, Unicode alphanumeric
            def finder = phraseForTokenising =~ tokenRegex
            // NB in Groovy the variable 'finder' is then of class java.util.regex.Matcher
            def finderIt = finder.iterator() // extra method added to Matcher by Groovy magic
            int start = 0
            boolean leadingDelim, trailingDelim
            def combinedTokensAndDelims = [] // create an array in Groovy

            while( finderIt.hasNext() )
            {
                def token = finderIt.next()
                int finderStart = finder.start()
                String delim = phraseForTokenising[ start  .. finderStart - 1 ]
                // Groovy: above gets slice of String/array
                if( start == 0 ) leadingDelim = finderStart != 0
                if( start > 0 || leadingDelim ) combinedTokensAndDelims << delim
                combinedTokensAndDelims << token // add element to end of array
                start = finder.end()
            }
            // start == 0 indicates no tokens found
            if( start > 0 ) {
                // finish by seeing whether there is a trailing delim
                trailingDelim = start < phraseForTokenising.length()
                if( trailingDelim ) combinedTokensAndDelims << phraseForTokenising[ start .. -1 ]

                println( "leading delim? $leadingDelim, trailing delim? $trailingDelim, combined array:\n $combinedTokensAndDelims" )

            }

-2

Java를 잘 모르지만, 그렇게하는 Split 메소드를 찾을 수 없다면 직접 작성하는 것이 좋습니다.

string[] mySplit(string s,string delimiter)
{
    string[] result = s.Split(delimiter);
    for(int i=0;i<result.Length-1;i++)
    {
        result[i] += delimiter; //this one would add the delimiter to each items end except the last item, 
                    //you can modify it however you want
    }
}
string[] res = mySplit(myString,myDelimiter);

너무 우아하지는 않지만 그렇게 할 것입니다.


그러나 여러 구분 기호가 연속으로 있으면 어떻게됩니까?
— Kip

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.