Bir dize nasıl bölünür, aynı zamanda sınırlayıcıları nasıl saklanır?


243

Farklı sınırlayıcılar kümesi tarafından sınırlandırılmış çok satırlı bir dize var:

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

Kullanarak bu dize parçalarına bölebilirim String.split, ama görünüyor ki sınırlayıcı regex ile eşleşen gerçek dize alamıyorum.

Başka bir deyişle, elde ettiğim şey bu:

  • Text1
  • Text2
  • Text3
  • Text4

İstediğim bu

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

Sınırlayıcı regex kullanarak dizeyi bölmenin ancak sınırlayıcıları korumanın herhangi bir JDK yolu var mı?


Düşünmeye gelin, sınırlayıcıları nerede tutmak istersiniz? Kelimelerle birlikte veya ayrı? İlk durumda, bunları önceki veya sonraki kelimeye ekler misiniz? İkinci durumda, cevabım ihtiyacınız olan şey ...
PhiLho

Sadece aradığınızı elde etmenize yardımcı olacak bir sınıf uyguladık. Aşağıya bakın
VonC

Yanıtlar:


366

Lookahead ve Lookbehind'i kullanabilirsiniz. Bunun gibi:

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

Ve alacaksınız:

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

Sonuncusu istediğin şey.

((?<=;)|(?=;))önce ;veya sonra boş bir karakter seçmek için eşittir ;.

Bu yardımcı olur umarım.

EDIT Fabian Steeg okunabilirlik hakkındaki yorumları geçerlidir. Okunabilirlik RegEx'in sorunudur. Bir şey, bunu kolaylaştırmak için yapmak regex ne yaptığını temsil eden bir değişken oluşturmak ve bunu yapmak için Java String biçimini kullanmaktır. Bunun gibi:

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

Bu biraz yardımcı olur. :-D


2
Çok hoş! Burada tekrar düzenli ifadelerin gücünü görebilirsiniz !!
George

1
StringTokenizer için olduğu gibi sınırlayıcıları dahil etmenin bir yolu olsaydı, String # split ile bunu yapmanın bir yolu olduğunu görmek güzel - split(";", true)çok daha okunabilir olurdu split("((?<=;)|(?=;))").
Fabian Steeg

3
Şöyle olmalıdır: String.format(WITH_DELIMITER, ";");format statik bir yöntemdir.
john16384

8
Yeni karşılaştığım bir komplikasyon, [\\s,]+tamamen eşleştirmek istediğiniz değişken uzunluklu sınırlayıcılardır (diyelim ). Gerekli regex'ler daha da uzar, çünkü onları ortada eşleştirmekten kaçınmak için ek {{ileride, arkasında} s negatif bakıma ihtiyacınız vardır, örn. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
iki ayırıcıya bölünmek istersem ne olur? diyelimki ';' veya '.'
miracle-doh

78

Lookarounds kullanmak ve sıfır genişlikli eşleşmelere bölmek istiyorsunuz. İşte bazı örnekler:

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

Ve evet, bu son modelde üçlü yuvalanmış bir iddia.

İlgili sorular

Ayrıca bakınız


1
Bunun yalnızca nispeten basit ifadeler için işe yarayacağını unutmayın; Bunu tüm gerçek sayıları temsil eden bir regex ile kullanmaya çalışan bir "Arkasında grup belirgin bir maksimum uzunluğu yok" var.
daveagp


30

Regex içermeyen çok naif bir çözüm, sınırlayıcılarınızda (sınırlayıcı için virgül varsayarak) satır boyunca bir dize değiştirme gerçekleştirmek olacaktır:

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

Burada tilda'yı (~) uygun bir benzersiz sınırlayıcı ile değiştirebilirsiniz.

Sonra yeni sınırlayıcı üzerinde bir bölünme yaparsanız o zaman istenen sonucu elde inanıyorum.


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

Önde ve arkada boş bir elemanın olduğu diğer yolu gerçekten sevmiyorum. Bir sınırlayıcı genellikle dizenin başında veya sonunda değildir, bu nedenle çoğu zaman iki iyi dizi yuvası harcarsınız.

Düzenleme: Sabit sınır durumlar. Test senaryolarıyla birlikte yorumlanmış kaynak burada bulunabilir: http://snippets.dzone.com/posts/show/6453


Wahoo ... Katıldığınız için teşekkür ederiz! İlginç bir yaklaşım. Tutarlı bir şekilde yardımcı olabileceğinden emin değilim (bununla, bazen bir sınırlayıcı vardır, bazen yoktur), ancak çaba için +1. Bununla birlikte, hala limit durumlarını (boş veya null değerler) doğru bir şekilde ele almanız gerekir
VonC

Sizi bu sınıfı düzgün bir şekilde güçlendirmeye, iyice belgelemeye, findbugs ve checkstyle ile bir geçiş yapmaya ve daha sonra bir snippet web sitesinde yayınlamaya davet ediyorum (bu sayfayı tonlarca kodla karıştırmaktan kaçınmak için)
VonC

Meydan okumayı kazandınız! Hata ... tebrikler! Bildiğiniz gibi, kod zorlama iş parçacığından, bunun için özel bir nokta veya rozetler olmaz ... (iç çekme): stackoverflow.com/questions/172184 . Ama bu katkı için teşekkür ederim.
VonC

@VonC Çoğu zaman, NPE'yi nulltartışmaya atmak doğru yoludur. Sessizce ele alınması daha sonra ortaya çıkacak hatalara yol açar.
maaartinus

@maaartinus Kabul ediyorum, ama kesinlikle NPE'den daha kullanıcı dostu bir mesaj atmak istediğiniz örnek var, değil mi?
VonC

11

Buraya geç geldim, ama orijinal soruya dönersek, neden sadece etrafları kullanmıyorsunuz?

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

çıktı:

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

EDIT: Yukarıda gördüğünüz, bu kodu çalıştırdığınızda komut satırında görünen, ama şimdi biraz kafa karıştırıcı olduğunu görüyorum. Hangi virgüllerin sonucun bir parçası olduğunu ve hangilerinin eklendiğini takip etmek zordur Arrays.toString(). SO'nun sözdizimi vurgulaması da yardımcı olmuyor. İşe vurgulama alma umuduyla ile beni yerine bana karşı, bu bilgileri buraya diziler Ben kaynak kodunda bunları ilan edildi bunu nasıl görüneceğini var:

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

Umarım okumak daha kolaydır. Uyarı için teşekkürler, @finnw.


Yanlış göründüğünü biliyorum - gerçeğe bir yıl sonra geri döndüğümde yanlış görünüyordu. Numune girişi kötü seçilmiş; Gönderiyi düzenleyeceğim ve açıklığa kavuşturmaya çalışacağım.
Alan Moore


10

Bunun çok eski bir soru olduğunu biliyorum ve cevap da kabul edildi. Ama yine de orijinal soruya çok basit bir cevap vermek istiyorum. Bu kodu düşünün:

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

ÇIKTI:

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"

Metnin başlangıcı hariç\b , sözcükleri sınırlamak için yalnızca sözcük sınırını kullanıyorum .


1
+1 Benim için en iyi cevap. ancak alfasayısal bir dizede alfasayısal sınırlayıcılar için çalışmaz
Casimir et Hippolyte

@CasimiretHippolyte: Oyunuz için teşekkürler. Lütfen işe yaramadığı bir örnek giriş sağlayabilir misiniz?
anubhava

2
Örneğin bunun için çalışmaz abcdefile deayırıcı olarak, ancak kullandığınız sorunu çözebilir(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte

1
Dize sınırlayıcı ile sona erdiğinde sonuçta boş bir dizeden kaçınmak için ilk iddiayı not edin, yani(?!^|$)
Casimir et Hippolyte


9

Yukarıdaki cevaplara bir göz attım ve dürüst olmak gerekirse hiçbiri tatmin edici bulmuyorum. Yapmak istediğiniz şey aslında Perl bölünme işlevini taklit etmektir. Java neden buna izin vermiyor ve bir yerde bir join () yöntemine sahip olmak beni aşıyor ama konuya geçiyorum. Bunun için gerçekten bir sınıfa bile ihtiyacınız yok. Bu sadece bir işlev. Bu örnek programı çalıştırın:

Daha önceki cevaplardan bazıları, son zamanlarda burada bir soruya yanıt yazdığım aşırı boş denetime sahip:

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

Her neyse, kod:

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

Kafam karıştı: Java'nın Perl'lerde modellenen split () yöntemi var, ancak çok daha az güçlü. Buradaki sorun, Java'nın split () yönteminin, parantez yakalamadaki normal ifadeyi içine alarak Perl'de elde edebileceğiniz sınırlayıcıları döndürmenin bir yolunu sağlamasıdır.
Alan Moore


7

Enumerable çünkü StringTokenizer fikrini seviyorum.
Ama aynı zamanda eski ve sıkıcı bir String [] döndüren String.split ile değiştirilir (ve ayırıcıları içermez).

Bu yüzden bir Yinelenebilir ve bir dize bölmek için gerçek bir regexp alır bir StringTokenizerEx uyguladı.

Gerçek bir normal ifade, sınırlayıcıyı oluşturmak için tekrarlanan bir 'Karakter dizisi' olmadığı anlamına gelir:
'o' yalnızca 'o' ile eşleşir ve 'ooo'yu içinde iki boş dize olacak şekilde üç ayırıcıya böler:

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

Ancak regexp o +, "aooob" kelimesini böldüğünde beklenen sonucu döndürür

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

Bu StringTokenizerEx'i kullanmak için:

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

Bu sınıfın kodu DZone Snippet'lerinde mevcuttur .

Her zaman olduğu gibi, bir kod meydan okuma yanıtı (test senaryoları dahil bir bağımsız sınıf), kopyalayıp yapıştırın ('src / test' dizininde) ve çalıştırın . Onun main () yöntemi farklı kullanımları gösterir.


Not: (2009 sonu)

Son Düşünceler: Java Puzzler: Splits Hairs makalesinde tuhaf davranışı açıklamak için iyi bir iş çıkarıyor String.split().
Josh Bloch bu makaleye yanıt olarak bile yorum yaptı:

Evet, bu bir acı. FWIW, çok iyi bir nedenden dolayı yapıldı: Perl ile uyumluluk.
Bunu yapan kişi şu anda Google'da bizimle çalışan Mike "madbot" McCloskey. Mike, Java'nın düzenli ifadelerinin neredeyse 30K Perl düzenli ifade testlerinin her birini geçtiğinden (ve daha hızlı çalıştığından) emin oldu.

Google ortak kütüphanesi Guava ayrıca bir Ayırıcı içerir:

  • kullanımı daha basit
  • Google tarafından korunur (sizin tarafınızdan değil)

Bu yüzden kontrol edilmeye değer olabilir. Bunların kaynaktan ilk kaba belgeleri (pdf) :

JDK'da şunlar var:

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

Tam olarak ne yaptığını istiyorsanız bunu kullanmak iyi olur: - normal ifade - bir dizi olarak sonuç - boş parçaları işleme şekli

Mini bilinmez: ", a ,, b,". Split (",") döndürür ...

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

Cevap: (e) Yukarıdakilerin hiçbiri.

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

Sadece sondaki boşluklar atlanır! (Atlamayı önlemek için geçici çözümü kim bilebilir? Eğlenceli bir çözüm ...)

Her durumda, Splitter'ımız sadece daha esnektir: Varsayılan davranış basittir:

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

Ekstra özellikler istiyorsanız, bunları isteyin!

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

Yapılandırma yöntemlerinin sırası önemli değildir - bölme sırasında, boşaltma olup olmadığını kontrol etmeden önce kırpma yapılır.



6

3. aurgumu "doğru" olarak verin. Sınırlayıcıları da döndürecektir.

StringTokenizer(String str, String delimiters, true);

4

İşte Pattern#splitarkasında duramayan ve destekleyemeyen değişken uzunluklu desenlerle uyumlu ve çalışan basit ve temiz bir uygulama ve kullanımı daha kolay. @Cletus tarafından sağlanan çözüme benzer .

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

Burada null kontrol yapmam Pattern#split, yapmamalıyım, neden I. ifSonunda hoşlanmıyorum ama ile tutarlılık için gereklidir Pattern#split. Aksi takdirde, girdi dizesi desenle biterse sonucun son öğesi olarak boş bir dize ile sonuçlanan koşulsuz olarak eklenirdim.

Ben tutarlılık için String [] dönüştürmek Pattern#split, new String[0]yerine kullanmak new String[result.size()]için buraya bakın niçin.

İşte benim testlerim:

@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

Çalışma versiyonlarımı da göndereceğim (ilk olarak Markus'a gerçekten benzer).

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

Ve işte ikinci çözüm ve ilk çözümden% 50 daha hızlı:

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

Normal ifade kullanan başka bir aday çözüm. Simge sırasını korur, aynı türdeki birden çok jetonu arka arkaya doğru şekilde eşleştirir. Olumsuz, normal regex tür kötü olmasıdır.

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

Örnek çıktı:

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

1

Java API'de bunu yapan mevcut bir işlev bilmiyorum (ki bu mevcut değil demek değildir), ama burada kendi uygulama (bir veya daha fazla sınırlayıcı tek bir belirteç olarak iade edilecektir; isterseniz her sınırlayıcı ayrı bir jeton olarak döndürülecek, biraz adaptasyona ihtiyaç duyacaktır):

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

İstediğinizi neredeyse kesinlikle elde edecek Desen ve Eşleştirici'yi kullanmanızı öneririm. Normal ifadenizin String.split içinde kullandığınızdan biraz daha karmaşık olması gerekir.


+1, Bu doğru yol. StringTokenizer, yakalama gruplarına yerleştirirseniz sınırlayıcılar çıkarır, ancak aslında kullanımdan kaldırılır. Kabul edilen cevabın yorumlarında özetlenen nedenlerle lookahead'i split () kullanmak, esas olarak birden fazla sınırlayıcı olduğunda karışıklık haline gelir. Ancak Pattern ve Matcher ile birkaç satırda gerçek bir tokenizatöre sahip olabilirsiniz.
johncipt

1

Bunun mümkün olduğunu düşünmüyorum String#split, ancak a'yı kullanabilirsiniz StringTokenizer, ancak bu, sınırlayıcınızı bir normal ifade olarak tanımlamanıza izin vermez, ancak yalnızca tek haneli karakterler sınıfı olarak tanımlar:

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

Orada, sınırlayıcılarımı belirtmek için bir normal ifade tanımlayamıyorum.
Daniel Rikowski

1
StringTokenizer yalnızca tek karakterli sınırlayıcılara izin verir.
Michael Borgwardt

1

Ödeyebiliyorsanız Java'nın replace (CharSequence target, CharSequence replacement) yöntemini kullanın ve bölmek için başka bir sınırlayıcı doldurun. Örnek: "boo: and: foo" dizesini bölmek ve ':' dizesini sağda tutmak istiyorum.

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

Önemli not: Bu yalnızca Dizenizde daha fazla "newdelimiter" yoksa çalışır! Dolayısıyla, genel bir çözüm değildir. Ancak, dizede asla görünmeyeceğinden emin olabileceğiniz bir CharSequence biliyorsanız, bu çok basit bir çözümdür.



0

Hızlı cevap: bölmek için \ b gibi fiziksel olmayan sınırlar kullanın. Çalışıp çalışmadığını görmek için deneyeceğim (PHP ve JS'de kullanıldı).

Mümkün ve bir çeşit iş, ama çok fazla bölünebilir. Aslında, bölmek istediğiniz dizeye ve ihtiyacınız olan sonuca bağlıdır. Daha fazla ayrıntı verin, size daha iyi yardımcı olacağız.

Başka bir yol da kendi bölmenizi yapmak, ayırıcıyı (değişken olduğunu varsayarak) yakalamak ve daha sonra sonuca eklemektir.

Hızlı testim:

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

Sonuç:

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

Biraz fazla... :-)



0

Listeye eşleşen deseni eklemek için Tweaked Pattern.split ()

Katma

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

Tam kaynak

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

İşte yardımcı olması durumunda yukarıdaki kodlardan bazılarına dayanan harika bir sürüm. Her neyse, kısa. Şartlı olarak baş ve kuyruğu içerir (boş değilse). Son bölüm bir demo / test senaryosudur.

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

Yine de çalışan son derece naif ve verimsiz bir çözüm. Dizeye iki kez bölün ve sonra iki diziyi birleştirin

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

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

0

Bu sorudaki inceliklerden biri "önde gelen sınırlayıcı" sorusunu içerir: bir dizi belirteç ve sınırlayıcıya sahip olacaksanız, bunun bir belirteç veya sınırlayıcı ile başlayıp başlamadığını bilmeniz gerekir. Elbette, önde gelen bir sınırın atılması gerektiğini varsayabilirsiniz, ancak bu haksız bir varsayım gibi görünüyor. Ayrıca sondaki bir sınırın olup olmadığını bilmek isteyebilirsiniz. Bu, buna göre iki boole bayrağı koyar.

Groovy ile yazılmış ancak bir Java sürümü oldukça açık olmalıdır:

            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'yı çok iyi bilmiyorum, ancak bunu yapan bir Split yöntemi bulamazsanız, sadece kendinizinkini yapmanızı öneririm.

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

Çok zarif değil, ama yapacak.


ancak arka arkaya birden fazla sınırlayıcınız varsa ne olur?
Kip

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.