วิธีการแยกสตริง แต่ยังเก็บตัวคั่น?


243

ฉันมีสตริงหลายบรรทัดซึ่งคั่นด้วยชุดตัวคั่นอื่น:

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

ฉันสามารถแยกสตริงนี้ออกเป็นส่วน ๆ โดยใช้String.splitแต่ดูเหมือนว่าฉันไม่สามารถรับสตริงจริงซึ่งตรงกับตัวคั่น regex

นั่นคือสิ่งที่ฉันได้รับ:

  • Text1
  • Text2
  • Text3
  • Text4

นี่คือสิ่งที่ฉันต้องการ

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

มีวิธี JDK เพื่อแยกสตริงโดยใช้ตัวคั่น regex แต่ยังเก็บตัวคั่นหรือไม่


ลองคิดดูคุณต้องการเก็บตัวคั่นไว้ที่ไหน พร้อมกับคำหรือแยกกัน? ในกรณีแรกคุณจะแนบไปกับคำก่อนหน้าหรือต่อไปนี้หรือไม่? ในกรณีที่สองคำตอบของฉันคือสิ่งที่คุณต้อง ...
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 เสมอ สิ่งหนึ่งที่ฉันทำเพื่อช่วยผ่อนคลายนี้คือการสร้างตัวแปรที่มีชื่อแสดงสิ่งที่ 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, ";"));
...
}
...

มันช่วยได้นิดหน่อย :-D


2
ดีมาก! ที่นี่เราสามารถเห็นพลังของการแสดงออกปกติอีกครั้ง !!
จอร์จ

1
ดีที่จะเห็นว่ามีวิธีการทำเช่นนี้กับ String # แยก แต่ฉันต้องการมีวิธีที่จะรวมตัวคั่นขณะที่มีสำหรับ StringTokenizer - การจะให้อ่านได้มากขึ้นกว่าsplit(";", true) split("((?<=;)|(?=;))")
Fabian Steeg

3
ควรเป็น: String.format(WITH_DELIMITER, ";");รูปแบบเป็นวิธีการคงที่
john16384

8
ปัญหาหนึ่งที่ฉันเพิ่งพบคือตัวคั่นความยาวผันแปร[\\s,]+ที่คุณต้องการจับคู่ให้สมบูรณ์ regexes ที่จำเป็นต้องใช้เพิ่มขึ้นอีกต่อไปเนื่องจากคุณต้องการรูปลักษณ์เชิงลบเพิ่มเติม {ล่วงหน้า, ด้านหลัง} s เพื่อหลีกเลี่ยงการจับคู่มันไว้ตรงกลางเช่น (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
ถ้าฉันต้องการแยกด้วยตัวคั่นสองตัว สมมติว่า ';' หรือ '.'
miracle-doh

78

คุณต้องการใช้ lookarounds และแยกการจับคู่ความกว้างเป็นศูนย์ นี่คือตัวอย่างบางส่วน:

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

และใช่นั่นคือการยืนยันที่ซ้อนกัน triply มีในรูปแบบสุดท้าย

คำถามที่เกี่ยวข้อง

ดูสิ่งนี้ด้วย


1
โปรดทราบว่านี่จะใช้งานได้กับนิพจน์ที่ค่อนข้างง่ายเท่านั้น ฉันได้รับกลุ่ม "Look-behind ไม่มีความยาวสูงสุดที่เห็นได้ชัด" พยายามใช้สิ่งนี้กับ regex ซึ่งแทนจำนวนจริงทั้งหมด
daveagp

2
FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

30

วิธีการแก้ปัญหาที่ไร้เดียงสาที่ไม่เกี่ยวข้องกับ regex คือการดำเนินการแทนที่สตริงบนตัวคั่นของคุณตามบรรทัด (สมมติว่าเครื่องหมายจุลภาคสำหรับตัวคั่น):

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 สำหรับความพยายาม อย่างไรก็ตามคุณยังคงต้องจัดการกับกรณีขีด จำกัด อย่างถูกต้อง (ค่าว่างหรือค่าว่าง)
VonC

ฉันขอเชิญคุณเสริมความแข็งแกร่งให้กับชั้นเรียนอย่างละเอียดจัดทำเอกสารผ่าน findbugs และตรวจสอบแล้วเผยแพร่บนเว็บไซต์ตัวอย่าง (เพื่อหลีกเลี่ยงความยุ่งเหยิงในหน้านี้ด้วยรหัสจำนวนมาก)
VonC

คุณชนะการท้าทาย! เอ่อ ... ขอแสดงความยินดี! ที่คุณรู้ว่าจากหัวข้อรหัสความท้าทายที่จะไม่มีจุดพิเศษหรือป้ายที่ ... (ถอนหายใจ): stackoverflow.com/questions/172184 แต่ขอบคุณสำหรับการสนับสนุนนี้
VonC

@VonC ส่วนใหญ่การโยน NPE บนnullอาร์กิวเมนต์เป็นวิธีที่ถูกต้องในการไป การจัดการอย่างเงียบ ๆ จะนำไปสู่ข้อผิดพลาดที่ปรากฏขึ้นในภายหลัง
maaartinus

@maaartinus ฉันเห็นด้วย แต่แน่นอนมีตัวอย่างที่คุณต้องการส่งข้อความที่เป็นมิตรต่อผู้ใช้มากกว่าแค่ NPE ใช่ไหม?
VonC

11

ฉันมาที่นี่ช้า แต่กลับไปที่คำถามเดิมทำไมไม่ใช้แค่ lookarounds?

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

ฉันหวังว่าจะอ่านง่ายกว่า ขอบคุณสำหรับ heads-up, @finnw


ฉันรู้ว่ามันดูผิด - มันดูผิดสำหรับฉันเมื่อฉันกลับมาหาฉันในตอนนี้หนึ่งปีหลังจากความจริง อินพุตตัวอย่างถูกเลือกไม่ดี ฉันจะแก้ไขโพสต์และพยายามอธิบายสิ่งต่างๆ
Alan Moore

FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

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

2
เช่นนี้ใช้ไม่ได้abcdefกับdeตัวคั่น แต่คุณสามารถแก้ปัญหาได้โดยใช้(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte

1
สังเกตการยืนยันแรกเพื่อหลีกเลี่ยงสตริงว่างในผลลัพธ์เมื่อสตริงลงท้ายด้วยตัวคั่นคือ(?!^|$)
Casimir et Hippolyte

1
FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

9

ฉันดูที่คำตอบข้างต้นและโดยสุจริตฉันไม่พบว่าเป็นที่น่าพอใจ สิ่งที่คุณต้องการทำคือเลียนแบบฟังก์ชันการแบ่ง Perl ทำไม Java ไม่อนุญาตสิ่งนี้และมีวิธีการเข้าร่วม () บางแห่งอยู่นอกเหนือฉัน แต่ฉันพูดนอกเรื่อง คุณไม่จำเป็นต้องเรียนเลย มันเป็นแค่ฟังก์ชั่น รันโปรแกรมตัวอย่างนี้:

คำตอบก่อนหน้านี้บางคำมีการตรวจสอบโมฆะมากเกินไปซึ่งฉันเพิ่งเขียนคำตอบสำหรับคำถามที่นี่:

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 มีวิธีการแยก () ซึ่งเป็นรูปแบบของ Perl แต่มีประสิทธิภาพน้อยกว่ามาก ปัญหาที่นี่คือการแยกของ Java () ให้วิธีการส่งคืนตัวคั่นซึ่งคุณสามารถประสบความสำเร็จใน Perl โดยล้อม regex ในการจับวงเล็บไม่
Alan Moore

FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

7

ฉันชอบความคิดของ StringTokenizer เพราะมันนับได้
แต่มันก็ล้าสมัยและแทนที่ด้วย String.split ซึ่งส่งคืนสตริงที่น่าเบื่อ [] (และไม่รวมถึงตัวคั่น)

ดังนั้นฉันจึงใช้ StringTokenizerEx ซึ่งเป็น Iterable และ regexp ที่แท้จริงในการแบ่งสตริง

regexp ที่แท้จริงหมายความว่าไม่ใช่ 'ลำดับอักขระ' ซ้ำเพื่อสร้างตัวคั่น:
'o' จะจับคู่เฉพาะ 'o' และแยก 'ooo' เป็นสามตัวคั่นโดยมีสตริงว่างสองตัวอยู่ภายใน:

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

แต่ regexp 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' directory) และเรียกใช้ วิธีการหลัก () แสดงให้เห็นถึงประเพณีที่แตกต่างกัน


หมายเหตุ: (แก้ไขปลายปี 2009)

บทความสุดท้ายความคิด: Java ปริศนา: แยกขนString.split()ไม่ได้ทำงานที่ดีการอธิบายพฤติกรรมที่แปลกประหลาดใน
Josh Bloch แสดงความคิดเห็นในการตอบสนองต่อบทความนั้น:

ใช่นี่เป็นความเจ็บปวด FWIW ทำด้วยเหตุผลที่ดีมาก: ใช้งานร่วมกับ Perl ได้
คนที่ทำเช่นนั้นคือ Mike "madbot" McCloskey ซึ่งตอนนี้ทำงานร่วมกับเราที่ Google Mike ทำให้แน่ใจว่านิพจน์ทั่วไปของ Java ผ่านการทดสอบนิพจน์ปกติ 30K Perl ทุก ๆ อัน (และวิ่งเร็วขึ้น)

Guava ไลบรารีทั่วไปของ Google มีตัวแยกซึ่งก็คือ:

  • ใช้ง่ายกว่า
  • ดูแลโดย Google (ไม่ใช่คุณ)

ดังนั้นจึงอาจคุ้มค่าที่จะเช็คเอาท์ จากเอกสารคร่าวๆเริ่มต้นของพวกเขา(pdf) :

JDK มีสิ่งนี้:

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

มันเป็นเรื่องดีที่จะใช้สิ่งนี้หากคุณต้องการสิ่งที่มันทำ: - การแสดงออกปกติ - ผลเป็นอาร์เรย์ - วิธีการจัดการชิ้นส่วนที่ว่างเปล่า

Mini-puzzler: ", 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.on(',').split(" foo, ,bar, quux,")
--> [" foo", " ", "bar", " quux", ""]

หากคุณต้องการคุณสมบัติพิเศษถามพวกเขา!

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

ลำดับของวิธีการตั้งค่าไม่สำคัญ - ในระหว่างการแยกการตัดจะเกิดขึ้นก่อนที่จะตรวจสอบหาสิ่งที่ว่างเปล่า


FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

6

ผ่าน aurgument ที่ 3 เป็น "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แต่มันเป็นสิ่งจำเป็นสำหรับความสอดคล้องกับ มิฉะนั้นฉันจะผนวกอย่างไม่มีเงื่อนไขทำให้สตริงว่างเปล่าเป็นองค์ประกอบสุดท้ายของผลลัพธ์หากสตริงอินพุตลงท้ายด้วยรูปแบบ

ฉันแปลงเป็น String [] เพื่อความสอดคล้อง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

อีกวิธีการแก้ปัญหาผู้สมัครใช้ regex คงคำสั่งโทเค็นอย่างถูกต้องตรงกับหลายโทเค็นประเภทเดียวกันในแถว ข้อเสียคือ regex นั้นน่ารังเกียจ

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

FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

1

ฉันแนะนำให้ใช้ Pattern and Matcher ซึ่งเกือบจะบรรลุสิ่งที่คุณต้องการ นิพจน์ทั่วไปของคุณจะต้องค่อนข้างซับซ้อนกว่าสิ่งที่คุณใช้ใน String.split


+1 นี่คือวิธีที่ถูกต้อง StringTokenizer จะเอาท์พุทตัวคั่นหากคุณวางไว้ในกลุ่มการดักจับ การใช้ lookahead with split () นั้นเป็นการแฮ็กด้วยเหตุผลที่ระบุไว้ในความคิดเห็นของคำตอบที่ยอมรับ - ส่วนใหญ่มันจะเป็นระเบียบเมื่อมีตัวคั่นมากกว่าหนึ่งตัว แต่คุณสามารถมี tokenizer จริงในไม่กี่บรรทัดด้วย Pattern และ Matcher
johncip

1

ฉันไม่คิดว่ามันเป็นไปได้ด้วยString#splitแต่คุณสามารถใช้ a StringTokenizerแม้ว่ามันจะไม่อนุญาตให้คุณกำหนดตัวคั่นของคุณเป็น regex แต่เป็นคลาสของอักขระหลักเดียว:

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

ที่นั่นฉันไม่สามารถกำหนด regex เพื่อระบุตัวคั่นของฉัน
Daniel Rikowski

1
StringTokenizer อนุญาตเฉพาะสำหรับตัวคั่นอักขระเดียวเท่านั้น
Michael Borgwardt

1

หากคุณสามารถจ่ายได้ให้ใช้วิธีการแทนที่ (CharSequence เป้าหมาย, การแทนที่ CharSequence) ของ Java และเติมตัวคั่นอื่นเพื่อแยกด้วย ตัวอย่าง: ฉันต้องการแยกสตริง "boo: และ: foo" และเก็บ ':' ไว้ที่สตริงทางขวา

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

หมายเหตุสำคัญ: ใช้งานได้เฉพาะในกรณีที่คุณไม่มี "newdelimiter" ใน String ของคุณ! ดังนั้นจึงไม่ใช่วิธีแก้ปัญหาทั่วไป แต่ถ้าคุณรู้ CharSequence ซึ่งคุณสามารถมั่นใจได้ว่ามันจะไม่ปรากฏใน String นี่เป็นวิธีแก้ปัญหาที่ง่ายมาก


FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

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|'|

มากเกินไป ... :-)


FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

0

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

FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9

0

นี่คือเวอร์ชั่น Groovy ตามรหัสบางส่วนด้านบนในกรณีที่ช่วยได้ มันสั้นอยู่แล้ว รวมหัวและหางแบบมีเงื่อนไข (หากไม่ว่าง) ส่วนสุดท้ายคือกรณีสาธิต / ทดสอบ

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: ผสานจากstackoverflow.com/questions/275768/…
Shog9

0

วิธีการแก้ปัญหาที่ไร้เดียงสาอย่างมากและไม่มีประสิทธิภาพซึ่งใช้งานได้อย่างไรก็ตามให้แบ่งสองครั้งบนสตริงแล้วเชื่อมต่อสองอาร์เรย์

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

มันไม่หรูหราเกินไป แต่มันจะทำ


แต่ถ้าคุณมีตัวคั่นหลายตัวติดกันล่ะ
กี

FYI: ผสานจากstackoverflow.com/questions/275768/…
Shog9
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.