Làm thế nào để tách một chuỗi, nhưng cũng giữ các dấu phân cách?


243

Tôi có một chuỗi nhiều dòng được phân định bởi một nhóm các dấu phân cách khác nhau:

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

Tôi có thể chia chuỗi này thành các phần của nó, bằng cách sử dụng String.split, nhưng dường như tôi không thể có được chuỗi thực, khớp với biểu thức phân cách.

Nói cách khác, đây là những gì tôi nhận được:

  • Text1
  • Text2
  • Text3
  • Text4

Đây là những gì tôi muốn

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

Có cách JDK nào để phân tách chuỗi bằng regex phân cách mà còn giữ các dấu phân cách không?


Hãy nghĩ về nó, bạn muốn giữ các dấu phân cách ở đâu? Cùng với từ hay riêng? Trong trường hợp đầu tiên, bạn sẽ gắn chúng với từ trước hoặc từ sau? Trong trường hợp thứ hai, câu trả lời của tôi là những gì bạn cần ...
PhiLho

Chỉ cần thực hiện một lớp sẽ giúp bạn đạt được những gì bạn đang tìm kiếm. Xem bên dưới
VonC

Câu trả lời:


366

Bạn có thể sử dụng Lookahead và Lookbehind. Như thế này:

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("((?<=;)|(?=;))")));

Và bạn sẽ nhận được:

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

Điều cuối cùng là những gì bạn muốn.

((?<=;)|(?=;))bằng để chọn một ký tự trống trước ;hoặc sau ;.

Hi vọng điêu nay co ich.

EDIT Fabian Steeg nhận xét về Khả năng đọc là hợp lệ. Khả năng đọc luôn là vấn đề đối với RegEx. Một điều, tôi làm để giúp giảm bớt điều này là tạo một biến có tên đại diện cho những gì regex làm và sử dụng định dạng Chuỗi Java để giúp điều đó. Như thế này:

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, ";"));
...
}
...

Điều này giúp một chút. : -D


2
Rất đẹp! Ở đây chúng ta có thể thấy một lần nữa sức mạnh của biểu thức thông thường !!
George

1
Rất vui khi thấy có một cách để làm điều này với String # split, mặc dù tôi ước có một cách để bao gồm các dấu phân cách như đã có cho StringTokenizer - split(";", true)sẽ dễ đọc hơn nhiều split("((?<=;)|(?=;))").
Fabian Steeg

3
Đó phải là: String.format(WITH_DELIMITER, ";");như định dạng là một phương thức tĩnh.
john16384

8
Một điều phức tạp tôi vừa gặp là các dấu phân cách có độ dài thay đổi (giả sử [\\s,]+) mà bạn muốn khớp hoàn toàn. Các biểu thức được yêu cầu thậm chí còn dài hơn, vì bạn cần thêm cái nhìn tiêu cực {phía trước, phía sau} để tránh khớp chúng ở giữa, ví dụ: (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
Nếu tôi muốn chia hai dấu phân cách thì sao? hãy cùng nói nào ';' hoặc là '.'
phép lạ-doh

78

Bạn muốn sử dụng ngoại hình và phân chia trên các kết quả khớp có độ rộng bằng không. Dưới đây là một số ví dụ:

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][!!]"
    }
}

Và vâng, đó là khẳng định ba lần lồng trong mẫu cuối cùng.

Câu hỏi liên quan

Xem thêm


1
Lưu ý rằng điều này sẽ chỉ làm việc cho các biểu thức tương đối đơn giản; Tôi nhận được "Nhóm nhìn phía sau không có độ dài tối đa rõ ràng" khi cố gắng sử dụng nhóm này với biểu thức chính quy đại diện cho tất cả các số thực.
daveagp

2
FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

30

Một giải pháp rất ngây thơ, không liên quan đến regex sẽ là thực hiện thay thế chuỗi trên dấu phân cách của bạn dọc theo dòng (giả sử dấu phẩy cho dấu phân cách):

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

Nơi bạn có thể thay thế tilda (~) bằng một dấu phân cách duy nhất thích hợp.

Sau đó, nếu bạn thực hiện một phân chia trên dấu phân cách mới của bạn thì tôi tin rằng bạn sẽ nhận được kết quả mong muốn.


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: ""
*/

Tôi không thực sự thích cách khác, nơi bạn có một yếu tố trống ở phía trước và phía sau. Một dấu phân cách thường không ở đầu hoặc cuối chuỗi, do đó bạn thường lãng phí hai khe mảng tốt.

Chỉnh sửa: Đã sửa trường hợp giới hạn. Nguồn bình luận với các trường hợp thử nghiệm có thể được tìm thấy ở đây: http://snippets.dzone.com/posts/show/6453


Wahoo ... Cảm ơn bạn đã tham gia! Cách tiếp cận thú vị. Tôi không chắc chắn nó có thể được giúp đỡ một cách nhất quán (với điều đó, đôi khi có một dấu phân cách, đôi khi không có), nhưng +1 cho nỗ lực. Tuy nhiên, bạn vẫn cần giải quyết chính xác các trường hợp giới hạn (giá trị rỗng hoặc null)
VonC

Tôi mời bạn củng cố chính xác lớp này, ghi chép kỹ lưỡng về nó, thực hiện một lượt tìm kiếm và kiểm tra, sau đó xuất bản nó trên một trang web đoạn trích (để tránh làm lộn xộn trang này với hàng tấn mã)
VonC

Bạn đã chiến thắng thử thách! Ơ ... chúc mừng! Như bạn đã biết, từ chuỗi thử thách mã, sẽ không có điểm hoặc huy hiệu đặc biệt nào cho điều đó ... (thở dài): stackoverflow.com/questions/172184 . Nhưng cảm ơn bạn đã đóng góp.
VonC

@VonC Hầu hết thời gian, ném NPE vào nullđối số là cách chính xác để đi. Âm thầm xử lý nó dẫn đến lỗi hiển thị sau này.
maaartinus

@maaartinus Tôi đồng ý, nhưng chắc chắn có trường hợp bạn muốn gửi một thông điệp thân thiện với người dùng hơn là chỉ NPE, phải không?
VonC

11

Tôi đến đây muộn, nhưng trở lại câu hỏi ban đầu, tại sao không sử dụng ngoại hình?

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

đầu ra:

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

EDIT: Những gì bạn thấy ở trên là những gì xuất hiện trên dòng lệnh khi tôi chạy mã đó, nhưng bây giờ tôi thấy rằng nó hơi khó hiểu. Thật khó để theo dõi dấu phẩy nào là một phần của kết quả và được thêm vào bởi Arrays.toString(). Làm nổi bật cú pháp của SO cũng không giúp được gì. Với hy vọng có được sự nổi bật để làm việc với tôi thay vì chống lại tôi, đây là cách các mảng đó trông như thế nào, tôi đã khai báo chúng trong mã nguồn:

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

Tôi hy vọng nó dễ đọc hơn. Cảm ơn vì đã đề phòng, @finnw.


Tôi biết nó có vẻ sai - nó có vẻ sai đối với tôi khi tôi trở lại với nó ngay bây giờ, một năm sau thực tế. Đầu vào mẫu được chọn kém; Tôi sẽ chỉnh sửa bài viết và cố gắng làm rõ mọi thứ.
Alan Moore

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

10

Tôi biết đây là một câu hỏi rất cũ và câu trả lời cũng đã được chấp nhận. Nhưng tôi vẫn muốn gửi một câu trả lời rất đơn giản cho câu hỏi ban đầu. Xem xét mã này:

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

ĐẦU RA:

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"

Tôi chỉ sử dụng ranh giới từ \bđể phân định các từ trừ khi nó bắt đầu văn bản.


1
+1 Câu trả lời tốt nhất cho tôi. nhưng nó không hoạt động đối với các dấu phân cách chữ và số trong một chuỗi ký tự chữ và số
Casimir et Hippolyte

@CasimiretHippolyte: Cảm ơn bạn đã upvote. Bạn có thể vui lòng cung cấp một đầu vào mẫu khi nó không hoạt động.
anubhava

2
ví dụ: điều này không hoạt động abcdefvới detư cách là dấu phân cách, nhưng bạn có thể giải quyết vấn đề bằng cách sử dụng(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte

1
Lưu ý xác nhận đầu tiên để tránh một chuỗi trống trong kết quả khi chuỗi kết thúc bằng dấu phân cách, tức là(?!^|$)
Casimir et Hippolyte

1
FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

9

Tôi đã xem xét các câu trả lời ở trên và thành thật không ai trong số chúng tôi thấy thỏa đáng. Những gì bạn muốn làm về cơ bản là bắt chước chức năng phân chia Perl. Tại sao Java không cho phép điều này và có một phương thức tham gia () ở đâu đó nằm ngoài tôi nhưng tôi lạc đề. Bạn thậm chí không cần một lớp học cho việc này thực sự. Nó chỉ là một chức năng. Chạy chương trình mẫu này:

Một số câu trả lời trước đó có kiểm tra null quá mức, gần đây tôi đã viết một câu trả lời cho một câu hỏi ở đây:

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

Dù sao, mã:

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

Tôi bối rối: Java có một phương thức split (), được mô hình hóa trên Perl, nhưng ít mạnh hơn nhiều. Vấn đề ở đây là sự phân tách () của Java không cung cấp cách nào để trả về các dấu phân cách mà bạn có thể đạt được trong Perl bằng cách kèm theo biểu thức chính quy trong việc bắt các dấu ngoặc đơn.
Alan Moore

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

7

Tôi thích ý tưởng của StringTokenizer vì nó là Vô số.
Nhưng nó cũng đã lỗi thời và được thay thế bằng String.split trả về một chuỗi nhàm chán [] (và không bao gồm các dấu phân cách).

Vì vậy, tôi đã triển khai StringTokenizerEx là một Iterable và cần một biểu thức chính quy để phân tách một chuỗi.

Một biểu thức chính quy có nghĩa là nó không phải là một 'Chuỗi ký tự' được lặp lại để tạo thành dấu phân cách:
'o' sẽ chỉ khớp với 'o' và chia 'ooo' thành ba dấu phân cách, bên trong có hai chuỗi trống:

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

Nhưng regrec o + sẽ trả về kết quả mong đợi khi chia "aooob"

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

Để sử dụng StringTokenizerEx này:

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

Mã của lớp này có sẵn tại DZone Snippets .

Như thường lệ đối với phản hồi thử thách mã (một lớp độc lập có bao gồm các trường hợp kiểm tra), sao chép-dán nó (trong thư mục 'src / test') và chạy nó . Phương thức main () của nó minh họa các cách sử dụng khác nhau.


Lưu ý: (chỉnh sửa cuối năm 2009)

Bài viết Suy nghĩ cuối cùng: Java Puzzler: Splits Hairs thực hiện tốt công việc giải thích hành vi kỳ quái trong String.split().
Josh Bloch thậm chí còn bình luận khi trả lời bài báo đó:

Vâng, đây là một nỗi đau. FWIW, nó đã được thực hiện vì một lý do rất tốt: khả năng tương thích với Perl.
Người đã làm điều đó là Mike "madbot" McCloskey, người hiện đang làm việc với chúng tôi tại Google. Mike đảm bảo rằng các biểu thức chính quy của Java đã vượt qua hầu hết các bài kiểm tra biểu thức chính quy 30K Perl (và chạy nhanh hơn).

Guava thư viện chung của Google cũng chứa một Splitter là:

  • sử dụng đơn giản hơn
  • được duy trì bởi Google (chứ không phải bởi bạn)

Vì vậy, nó có thể có giá trị được kiểm tra. Từ tài liệu thô ban đầu của họ (pdf) :

JDK có điều này:

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

Sử dụng cái này là tốt nếu bạn muốn chính xác những gì nó làm: - biểu thức chính quy - kết quả là một mảng - cách xử lý các phần trống của nó

Câu đố nhỏ: ", a ,, b,". Split (",") trả về ...

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

Trả lời: (e) Không có điều nào ở trên.

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

Chỉ có trống rỗng được bỏ qua! (Ai biết cách giải quyết để tránh bỏ qua? Đó là một trò vui ...)

Trong mọi trường hợp, Bộ chia của chúng tôi đơn giản là linh hoạt hơn: Hành vi mặc định là đơn giản:

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

Nếu bạn muốn các tính năng bổ sung, yêu cầu chúng!

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

Thứ tự của các phương thức cấu hình không thành vấn đề - trong quá trình phân tách, việc cắt xén xảy ra trước khi kiểm tra trống.


FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

6

Vượt qua aurgument thứ 3 là "đúng". Nó sẽ trả lại dấu phân cách là tốt.

StringTokenizer(String str, String delimiters, true);

4

Dưới đây là một triển khai sạch đơn giản phù hợp Pattern#splitvà hoạt động với các mẫu có chiều dài thay đổi, nhìn phía sau không thể hỗ trợ và dễ sử dụng hơn. Nó tương tự như giải pháp được cung cấp bởi @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]);
}

Tôi không thực hiện kiểm tra null ở đây, Pattern#splitkhông, tại sao tôi phải. Tôi không thích ifở cuối nhưng điều đó là bắt buộc để thống nhất với Pattern#split. Nếu không, tôi sẽ nối thêm vô điều kiện, dẫn đến một chuỗi rỗng là phần tử cuối cùng của kết quả nếu chuỗi đầu vào kết thúc bằng mẫu.

Tôi chuyển đổi thành Chuỗi [] để thống nhất với Pattern#split, tôi sử dụng new String[0]chứ không phải new String[result.size()], xem tại đây để biết lý do.

Dưới đây là các bài kiểm tra của tôi:

@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

Tôi cũng sẽ đăng các phiên bản làm việc của mình (đầu tiên thực sự giống với 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()]);
}

Và đây là giải pháp thứ hai và vòng của nó nhanh hơn 50% so với giải pháp thứ nhất:

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

Một giải pháp ứng viên khác sử dụng regex. Giữ lại thứ tự mã thông báo, khớp chính xác nhiều mã thông báo cùng loại trong một hàng. Nhược điểm là regex là loại khó chịu.

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

Đầu ra mẫu:

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

1

Tôi không biết về một hàm hiện có trong API Java thực hiện điều này (không có nghĩa là nó không tồn tại), nhưng đây là cách triển khai của riêng tôi (một hoặc nhiều dấu phân cách sẽ được trả về dưới dạng một mã thông báo; nếu bạn muốn mỗi dấu phân cách được trả lại dưới dạng một mã thông báo riêng biệt, nó sẽ cần một chút thích ứng):

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

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

1

Tôi đề nghị sử dụng Pattern and Matcher, gần như chắc chắn sẽ đạt được những gì bạn muốn. Biểu thức chính quy của bạn sẽ cần phức tạp hơn một chút so với những gì bạn đang sử dụng trong String.split.


+1, Đây là cách đúng đắn. StringTokenizer sẽ xuất các dấu phân cách nếu bạn đặt chúng trong các nhóm bắt giữ, nhưng về cơ bản nó không được dùng nữa. Sử dụng lookahead với split () là hacky vì những lý do được nêu trong các nhận xét của câu trả lời được chấp nhận - chủ yếu là nó trở thành một mớ hỗn độn khi có nhiều hơn một dấu phân cách. Nhưng bạn có thể có một mã thông báo thực sự trong một vài dòng với Mẫu và Trình so khớp.
johncip

1

Tôi không nghĩ là có thể với String#split, nhưng bạn có thể sử dụng StringTokenizer, mặc dù điều đó sẽ không cho phép bạn xác định dấu phân cách của mình là biểu thức chính quy, mà chỉ là một lớp các ký tự một chữ số:

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

Ở đó tôi không thể xác định một biểu thức chính quy để chỉ định các dấu phân cách của tôi.
Daniel Rikowski

1
StringTokenizer chỉ cho phép các ký tự phân cách một ký tự, mặc dù.
Michael Borgwardt

1

Nếu bạn có đủ khả năng, hãy sử dụng phương thức thay thế của Java (mục tiêu CharSequence, thay thế CharSequence) và điền vào một dấu phân cách khác để phân chia. Ví dụ: Tôi muốn tách chuỗi "boo: and: foo" và giữ ':' ở chuỗi bên phải của nó.

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

Lưu ý quan trọng: Điều này chỉ hoạt động nếu bạn không có thêm "newd Friiter" trong Chuỗi của mình! Vì vậy, nó không phải là một giải pháp chung. Nhưng nếu bạn biết một CharSequence mà bạn có thể chắc chắn rằng nó sẽ không bao giờ xuất hiện trong Chuỗi, đây là một giải pháp rất đơn giản.


FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

0

Câu trả lời nhanh: sử dụng giới hạn vật lý như \ b để phân chia. Tôi sẽ thử và thử nghiệm xem nó có hoạt động không (đã sử dụng nó trong PHP và JS).

Có thể, và loại công việc, nhưng có thể chia quá nhiều. Trên thực tế, nó phụ thuộc vào chuỗi bạn muốn tách và kết quả bạn cần. Cung cấp thêm chi tiết, chúng tôi sẽ giúp bạn tốt hơn.

Một cách khác là thực hiện phân tách của riêng bạn, chụp dấu phân cách (giả sử nó là biến) và sau đó thêm nó vào kết quả.

Bài kiểm tra nhanh của tôi:

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

Kết quả:

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

Một chút quá nhiều ... :-)


FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

0

Tweaked Pattern.split () để đưa mẫu phù hợp vào danh sách

Thêm

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

Nguồn đầy đủ

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

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

0

Đây là một phiên bản hấp dẫn dựa trên một số mã ở trên, trong trường hợp nó giúp. Dù sao nó cũng ngắn. Điều kiện bao gồm đầu và đuôi (nếu chúng không trống). Phần cuối cùng là một trường hợp demo / thử nghiệm.

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

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9

0

Một giải pháp cực kỳ ngây thơ và không hiệu quả, tuy nhiên vẫn hoạt động. Sử dụng hai lần trên chuỗi và nối hai mảng

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

Với Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
regrec,

0

Một trong những điều tinh tế trong câu hỏi này liên quan đến câu hỏi "dấu phân cách hàng đầu": nếu bạn sắp có một chuỗi mã thông báo và dấu phân cách kết hợp, bạn phải biết liệu nó bắt đầu bằng mã thông báo hay dấu phân cách. Tất nhiên bạn có thể chỉ cần giả định rằng một delim hàng đầu nên được loại bỏ nhưng điều này có vẻ là một giả định không chính đáng. Bạn cũng có thể muốn biết liệu bạn có một dấu phân cách hay không. Điều này đặt hai cờ boolean tương ứng.

Được viết bằng Groovy nhưng một phiên bản Java nên khá rõ ràng:

            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

Tôi không biết Java quá rõ, nhưng nếu bạn không thể tìm thấy một phương thức Split thực hiện điều đó, tôi khuyên bạn chỉ nên làm cho riêng mình.

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

Nó không quá thanh lịch, nhưng nó sẽ làm được.


Nhưng nếu bạn có nhiều dấu phân cách liên tiếp thì sao?
Kip

FYI: Được hợp nhất từ stackoverflow.com/questions/275768/
Shog9
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.