Bagaimana cara membagi string, tetapi juga menjaga pembatas?


243

Saya memiliki string multiline yang dibatasi oleh satu set pembatas yang berbeda:

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

Saya dapat membagi string ini menjadi beberapa bagian, menggunakan String.split, tetapi tampaknya saya tidak bisa mendapatkan string yang sebenarnya, yang cocok dengan regex pembatas.

Dengan kata lain, inilah yang saya dapatkan:

  • Text1
  • Text2
  • Text3
  • Text4

Ini yang aku inginkan

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

Apakah ada cara JDK untuk memisahkan string menggunakan regex pembatas tetapi juga menjaga pembatas?


Kalau dipikir-pikir, di mana Anda ingin menyimpan pembatas? Seiring dengan kata-kata atau terpisah? Dalam kasus pertama, apakah Anda akan melampirkannya pada kata sebelum atau sesudahnya? Dalam kasus kedua, jawaban saya adalah apa yang Anda butuhkan ...
PhiLho

Cukup terapkan kelas yang seharusnya membantu Anda mencapai apa yang Anda cari. Lihat di bawah
VonC

Jawaban:


366

Anda dapat menggunakan Lookahead dan Lookbehind. Seperti ini:

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

Dan Anda akan mendapatkan:

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

Yang terakhir adalah apa yang Anda inginkan.

((?<=;)|(?=;))sama dengan memilih karakter kosong sebelum ;atau sesudah ;.

Semoga ini membantu.

Komentar EDIT Fabian Steeg tentang Keterbacaan valid. Keterbacaan selalu menjadi masalah bagi RegEx. Satu hal yang saya lakukan untuk membantu mengurangi ini adalah membuat variabel yang namanya mewakili apa yang regex lakukan dan gunakan format Java String untuk membantu itu. Seperti ini:

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

Ini sedikit membantu. :-D


2
Sangat bagus! Di sini kita bisa melihat lagi kekuatan ekspresi reguler !!
George

1
Senang melihat ada cara untuk melakukan ini dengan String # split, meskipun saya berharap ada cara untuk memasukkan pembatas seperti yang ada untuk StringTokenizer - split(";", true)akan jauh lebih mudah dibaca daripada split("((?<=;)|(?=;))").
Fabian Steeg

3
Itu harus: String.format(WITH_DELIMITER, ";");format adalah metode statis.
john16384

8
Satu komplikasi yang baru saja saya temui adalah pembatas panjang variabel (katakanlah [\\s,]+) yang ingin Anda cocokkan sepenuhnya. Regex yang diperlukan menjadi lebih lama, karena Anda memerlukan tampilan negatif tambahan {depan, belakang} untuk menghindari pencocokan mereka di tengah, misalnya. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
bagaimana jika saya ingin dibagi oleh dua pembatas? Katakanlah ';' atau '.'
miracle-doh

78

Anda ingin menggunakan lookaround, dan berpisah pada kecocokan dengan lebar nol. Berikut ini beberapa contohnya:

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

Dan ya, itu adalah pernyataan triply-nested di sana dalam pola terakhir.

Pertanyaan-pertanyaan Terkait

Lihat juga


1
Perhatikan bahwa ini hanya akan berfungsi untuk ekspresi yang relatif sederhana; Saya mendapat "Lihat-belakang grup tidak memiliki panjang maksimum yang jelas" mencoba menggunakan ini dengan regex yang mewakili semua bilangan real.
daveagp


30

Solusi yang sangat naif, yang tidak melibatkan regex adalah dengan melakukan penggantian string pada pembatas Anda di sepanjang baris (dengan asumsi koma untuk pembatas):

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

Di mana Anda dapat mengganti tilda (~) dengan pembatas unik yang sesuai.

Kemudian jika Anda melakukan pemisahan pada pembatas baru Anda maka saya yakin Anda akan mendapatkan hasil yang diinginkan.


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

Saya tidak terlalu suka sebaliknya, di mana Anda mendapatkan elemen kosong di depan dan belakang. Pembatas biasanya tidak di awal atau di akhir string, sehingga Anda paling sering berakhir dengan membuang dua slot array yang baik.

Sunting: Memperbaiki kasus batas. Sumber yang dikomentari dengan kasus uji dapat ditemukan di sini: http://snippets.dzone.com/posts/show/6453


Wahoo ... Terima kasih telah berpartisipasi! Pendekatan yang menarik. Saya tidak yakin ini bisa membantu secara konsisten (dengan itu, terkadang ada pembatas, kadang tidak ada), tetapi +1 untuk upaya tersebut. Namun, Anda masih perlu mengatasi dengan benar kasus batas (nilai kosong atau nol)
VonC

Saya mengundang Anda untuk memperkuat kelas ini dengan benar, mendokumentasikannya dengan seksama, membuat izin dengan findbugs dan checkstyle, dan kemudian menerbitkannya di situs web snippet (untuk menghindari mengacaukan halaman ini dengan banyak kode)
VonC

Anda memenangkan tantangan! Errr ... selamat! Seperti yang Anda ketahui, dari utas kode-tantangan, tidak akan ada poin atau lencana khusus untuk itu ... (sigh): stackoverflow.com/questions/172184 . Tetapi terima kasih atas kontribusi ini.
VonC

@VonC Sebagian besar waktu, melemparkan NPE pada nullargumen adalah cara yang tepat untuk pergi. Menanganinya secara diam-diam menyebabkan kesalahan yang muncul kemudian.
maaartinus

@maaartinus Saya setuju, tapi pasti ada contoh di mana Anda ingin melempar pesan yang lebih ramah pengguna daripada hanya NPE, kan?
VonC

11

Saya tiba di sini terlambat, tetapi kembali ke pertanyaan awal, mengapa tidak menggunakan lookaround saja?

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

keluaran:

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

EDIT: Apa yang Anda lihat di atas adalah apa yang muncul di baris perintah ketika saya menjalankan kode itu, tetapi sekarang saya melihat bahwa itu agak membingungkan. Sulit untuk melacak koma mana yang merupakan bagian dari hasil dan yang ditambahkan oleh Arrays.toString(). Penyorotan sintaksis SO juga tidak membantu. Dengan harapan mendapatkan sorotan untuk bekerja dengan saya alih-alih melawan saya, inilah cara array itu akan melihatnya saat saya mendeklarasikannya dalam kode sumber:

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

Saya harap itu lebih mudah dibaca. Terima kasih untuk informasi selanjutnya, @finnw.


Saya tahu itu keliru - tampak salah bagi saya ketika saya baru saja kembali, setahun setelah fakta. Input sampel dipilih dengan buruk; Saya akan mengedit posting dan mencoba menjelaskan beberapa hal.
Alan Moore


10

Saya tahu ini adalah pertanyaan dan jawaban yang sangat sangat lama juga telah diterima. Tetapi saya tetap ingin menyampaikan jawaban yang sangat sederhana untuk pertanyaan awal. Pertimbangkan kode ini:

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

KELUARAN:

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"

Saya hanya menggunakan batas kata \buntuk membatasi kata-kata kecuali ketika itu mulai dari teks.


1
+1 Jawaban terbaik untuk saya. tetapi tidak berfungsi untuk pembatas alfanumerik dalam string alfanumerik
Casimir et Hippolyte

@CasimiretHippolyte: Terima kasih atas upvote Anda. Bisakah Anda memberikan input sampel yang tidak berfungsi.
anubhava

2
untuk contoh ini tidak bekerja untuk abcdefdengan desebagai pembatas, tetapi Anda dapat memecahkan masalah dengan menggunakan(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte

1
Perhatikan pernyataan pertama untuk menghindari string kosong di hasil ketika string berakhir dengan pembatas, yaitu(?!^|$)
Casimir et Hippolyte


9

Saya telah melihat jawaban di atas dan jujur ​​tidak satupun dari mereka yang saya temukan memuaskan. Apa yang ingin Anda lakukan pada dasarnya meniru fungsi split Perl. Mengapa Java tidak mengizinkan ini dan memiliki metode join () di suatu tempat di luar saya, tetapi saya ngelantur. Anda bahkan tidak perlu kelas untuk ini. Ini hanya sebuah fungsi. Jalankan program sampel ini:

Beberapa jawaban sebelumnya memiliki pemeriksaan nol yang berlebihan, yang saya baru-baru ini menulis sebuah tanggapan terhadap pertanyaan di sini:

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

Bagaimanapun, kodenya:

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

Saya bingung: Java memang memiliki metode split (), yang dimodelkan pada Perl, tetapi jauh lebih kuat. Masalahnya di sini adalah bahwa split Java () tidak memberikan cara untuk mengembalikan pembatas, yang dapat Anda capai di Perl dengan melampirkan regex dalam menangkap tanda kurung.
Alan Moore


7

Saya suka gagasan StringTokenizer karena itu Enumerable.
Tapi itu juga sudah usang, dan ganti dengan String.split yang mengembalikan String yang membosankan [] (dan tidak termasuk pembatas).

Jadi saya menerapkan StringTokenizerEx yang merupakan Iterable, dan yang membutuhkan regexp sejati untuk membagi string.

Regexp sejati berarti itu bukan 'Urutan karakter' yang diulang untuk membentuk pembatas:
'o' hanya akan cocok dengan 'o', dan membagi 'ooo' menjadi tiga pembatas, dengan dua string kosong di dalamnya:

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

Tetapi regexp o + akan mengembalikan hasil yang diharapkan saat memisahkan "aooob"

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

Untuk menggunakan StringTokenizerEx ini:

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

Kode kelas ini tersedia di Cuplikan DZone .

Seperti biasa untuk respons kode-tantangan (satu kelas mandiri dengan kasus uji termasuk), salin-tempel (dalam direktori 'src / test') dan jalankan . Metode utamanya () menggambarkan penggunaan yang berbeda.


Catatan: (sunting akhir 2009)

Artikel Final Thoughts: Java Puzzler: Splitting Hairs bekerja dengan baik menjelaskan perilaku aneh di Indonesia String.split().
Josh Bloch bahkan berkomentar menanggapi artikel itu:

Ya, ini menyakitkan. FWIW, itu dilakukan untuk alasan yang sangat bagus: kompatibilitas dengan Perl.
Orang yang melakukannya adalah Mike "madbot" McCloskey, yang sekarang bekerja bersama kami di Google. Mike memastikan bahwa ekspresi reguler Java lulus hampir setiap tes ekspresi reguler 30K Perl (dan berlari lebih cepat).

Google common-library Guava juga mengandung Splitter yang:

  • lebih mudah digunakan
  • dikelola oleh Google (dan bukan oleh Anda)

Jadi mungkin perlu dicoba. Dari dokumentasi kasar awal mereka (pdf) :

JDK memiliki ini:

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

Tidak apa-apa untuk menggunakan ini jika Anda ingin apa yang dilakukannya: - ekspresi reguler - hasil sebagai array - cara menangani potongan kosong

Teka-teki mini: ", a ,, b,". Split (",") mengembalikan ...

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

Jawab: (e) Tidak satu pun di atas.

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

Hanya Trailing Empty yang dilewati! (Siapa yang tahu solusinya untuk mencegah melewatkan? Ini yang menyenangkan ...)

Bagaimanapun, Splitter kami hanya lebih fleksibel: Perilaku default sederhana:

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

Jika Anda ingin fitur tambahan, tanyakan!

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

Urutan metode konfigurasi tidak masalah - selama pemisahan, pemangkasan terjadi sebelum memeriksa kekosongan.



6

Lulus aurgument ke-3 sebagai "benar". Ini akan mengembalikan pembatas juga.

StringTokenizer(String str, String delimiters, true);

4

Berikut ini adalah implementasi bersih sederhana yang konsisten dengan Pattern#splitdan bekerja dengan pola panjang variabel, yang terlihat di belakang tidak dapat mendukung, dan lebih mudah digunakan. Ini mirip dengan solusi yang disediakan oleh @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]);
}

Saya tidak melakukan cek kosong di sini, Pattern#splittidak, mengapa harus saya. Saya tidak suka ifpada akhirnya tetapi diperlukan untuk konsistensi dengan Pattern#split. Kalau tidak, saya akan menambahkan tanpa syarat, menghasilkan string kosong sebagai elemen terakhir dari hasil jika string input berakhir dengan pola.

Saya mengonversi ke String [] untuk konsistensi dengan Pattern#split, saya menggunakan new String[0]daripada new String[result.size()], lihat di sini untuk alasannya.

Inilah tes saya:

@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

Saya akan memposting versi kerja saya juga (pertama sangat mirip dengan 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()]);
}

Dan inilah solusi kedua dan putarannya 50% lebih cepat dari yang pertama:

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

Solusi kandidat lain menggunakan regex. Mempertahankan urutan token, dengan benar mencocokkan beberapa token dari jenis yang sama dalam satu baris. The downside adalah bahwa regex agak buruk.

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

Output sampel:

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

1

Saya tidak tahu fungsi yang ada di Java API yang melakukan ini (yang bukan untuk mengatakan itu tidak ada), tapi inilah implementasi saya sendiri (satu atau lebih pembatas akan dikembalikan sebagai token tunggal; jika Anda ingin setiap pembatas akan dikembalikan sebagai token terpisah, itu akan memerlukan sedikit adaptasi):

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

Saya sarankan menggunakan Pattern and Matcher, yang hampir pasti akan mencapai apa yang Anda inginkan. Ekspresi reguler Anda harus agak lebih rumit daripada apa yang Anda gunakan di String.split.


+1, Ini adalah cara yang benar. StringTokenizer akan menampilkan pembatas jika Anda menempatkannya dalam kelompok tangkap, tetapi pada dasarnya tidak digunakan lagi. Menggunakan lookahead dengan split () adalah hacky untuk alasan yang diuraikan dalam komentar dari jawaban yang diterima - terutama yang menjadi berantakan ketika ada lebih dari satu pembatas. Tetapi Anda dapat memiliki tokenizer nyata dalam beberapa baris dengan Pattern and Matcher.
johncip

1

Saya tidak berpikir itu mungkin dengan String#split, tetapi Anda dapat menggunakan StringTokenizer, meskipun itu tidak akan memungkinkan Anda untuk mendefinisikan pembatas Anda sebagai regex, tetapi hanya sebagai kelas karakter satu digit:

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

Di sana saya tidak bisa mendefinisikan regex untuk menentukan pembatas saya.
Daniel Rikowski

1
StringTokenizer hanya memungkinkan untuk pembatas satu karakter.
Michael Borgwardt

1

Jika Anda mampu, gunakan metode penggantian Java (target CharSequence, penggantian CharSequence) dan isi pembatas lain untuk dibagi. Contoh: Saya ingin membagi string "boo: and: foo" dan simpan ':' di String sebelah kanannya.

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

Catatan penting: Ini hanya berfungsi jika Anda tidak memiliki "pendatang baru" lebih lanjut di String Anda! Jadi, itu bukan solusi umum. Tetapi jika Anda tahu CharSequence di mana Anda dapat yakin bahwa itu tidak akan pernah muncul di String, ini adalah solusi yang sangat sederhana.



0

Jawaban cepat: gunakan batas non fisik seperti \ b untuk membelah. Saya akan mencoba dan bereksperimen untuk melihat apakah itu berfungsi (menggunakannya dalam PHP dan JS).

Itu mungkin, dan jenis pekerjaannya, tetapi mungkin berpisah terlalu banyak. Sebenarnya, itu tergantung pada string yang ingin Anda bagi dan hasil yang Anda butuhkan. Berikan detail lebih lanjut, kami akan membantu Anda lebih baik.

Cara lain adalah dengan melakukan split Anda sendiri, menangkap pembatas (seandainya itu variabel) dan menambahkannya setelah itu ke hasilnya.

Tes cepat saya:

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

Hasil:

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

Terlalu banyak ... :-)



0

Tweak Pattern.split () untuk memasukkan pola yang cocok ke dalam daftar

Ditambahkan

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

Sumber lengkap

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

Berikut adalah versi asyik berdasarkan pada beberapa kode di atas, jika itu membantu. Pendeknya, pokoknya. Syaratnya termasuk kepala dan ekor (jika tidak kosong). Bagian terakhir adalah demo / test case.

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

Solusi yang sangat naif dan tidak efisien yang bekerja. Gunakan dua kali pada string dan kemudian menggabungkan dua array

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

Dengan regexp, ini akan menjadi:Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
Tsolak Barseghyan

0

Salah satu seluk-beluk dalam pertanyaan ini melibatkan pertanyaan "pembatas terkemuka": jika Anda akan memiliki array token dan pembatas gabungan Anda harus tahu apakah itu dimulai dengan token atau pembatas. Anda tentu saja bisa saja berasumsi bahwa batasan terkemuka harus dibuang tetapi ini tampaknya asumsi yang tidak dapat dibenarkan. Anda mungkin juga ingin tahu apakah Anda memiliki trailing delim atau tidak. Ini menetapkan dua bendera boolean yang sesuai.

Ditulis dalam Groovy tetapi versi Java harus cukup jelas:

            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

Saya tidak terlalu mengenal Java, tetapi jika Anda tidak dapat menemukan metode Split yang melakukan itu, saya sarankan Anda membuatnya sendiri.

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

Tidak terlalu elegan, tapi itu akan berhasil.


tetapi bagaimana jika Anda memiliki beberapa pembatas dalam satu baris?
Kip

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.