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


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
예를 들어이 작동하지 않습니다 abcdefde구분 기호로,하지만 당신은 사용하여 문제를 해결할 수 있습니다(?!^|$)(?:(?<=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.