Podziel ciąg na równe podciągi w Javie

125

Jak podzielić ciąg "Thequickbrownfoxjumps"na podciągi o równej wielkości w Javie. Na przykład. "Thequickbrownfoxjumps"4 równych rozmiarów powinno dać wynik.

["Theq","uick","brow","nfox","jump","s"]

Podobne pytanie:

Podziel ciąg na podciągi o równej długości w Scali

Emil
źródło
4
Czego próbowałeś? Dlaczego to nie zadziałało?
Thilo
2
Czy musisz w tym celu użyć wyrażenia regularnego? Tylko pytam z powodu tagu wyrażenia regularnego ...
Tim Pietzcker
@Thilo link, który zamieścił jest dla Scali, pyta o to samo w Javie
Jaydeep Patel
@Thilo: Pytałem, jak to zrobić w javie, jak odpowiedź na scala.
Emil

Odpowiedzi:

226

Oto jednowierszowa wersja wyrażenia regularnego:

System.out.println(Arrays.toString(
    "Thequickbrownfoxjumps".split("(?<=\\G.{4})")
));

\Gjest asercją o zerowej szerokości, która pasuje do pozycji, w której zakończyło się poprzednie dopasowanie. Jeśli nie było poprzedniego dopasowania, dopasowuje początek wejścia, tak samo jak\A . Obejmujący lookbehind dopasowuje pozycję, która jest cztery znaki dalej od końca ostatniego dopasowania.

Zarówno lookbehind, jak i \Gzaawansowane funkcje regex, nie są obsługiwane przez wszystkie odmiany . Co więcej, \Gnie jest konsekwentnie wdrażany we wszystkich smakach, które go obsługują. Ta sztuczka zadziała (na przykład) w Javie , Perlu, .NET i JGSoft, ale nie w PHP (PCRE), Ruby 1.9+ lub TextMate (oba Oniguruma). JavaScript /y(flaga sticky) nie jest tak elastyczna jak \Gi nie mogłaby być używana w ten sposób, nawet gdyby JS obsługiwał lookbehind.

Powinienem wspomnieć, że niekoniecznie polecam to rozwiązanie, jeśli masz inne opcje. Rozwiązania inne niż wyrażenia regularne w innych odpowiedziach mogą być dłuższe, ale są też samodokumentujące; ten jest po prostu przeciwieństwem tego. ;)

Ponadto to nie działa w systemie Android, który nie obsługuje użycia \Gw lookbehinds.

Alan Moore
źródło
2
W PHP 5.2.4 działa następujący kod: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Igor
5
Dla przypomnienia, użycie String.substring()zamiast wyrażenia regularnego, wymagające kilku dodatkowych linii kodu, będzie działać gdzieś 5x szybciej ...
drew moore
2
W Javie to nie działa dla łańcucha z znakami nowej linii. Sprawdza tylko do pierwszej nowej linii, a jeśli zdarzy się, że ta nowa linia znajduje się przed podzielonym rozmiarem, to ciąg nie zostanie podzielony. A może coś przegapiłem?
joensson
5
Dla kompletności wywodu: dzielenie tekstu na multilinie potrzebuje prefiksem (?s)w regex: (?s)(?<=\\G.{4}).
bobbel
1
Java nie radzi sobie z tym całkowicie w czasie kompilacji:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Jeffrey Blattman
132

Cóż, dość łatwo jest to zrobić za pomocą prostych operacji arytmetycznych i na łańcuchach znaków:

public static List<String> splitEqually(String text, int size) {
    // Give the list the right capacity to start with. You could use an array
    // instead if you wanted.
    List<String> ret = new ArrayList<String>((text.length() + size - 1) / size);

    for (int start = 0; start < text.length(); start += size) {
        ret.add(text.substring(start, Math.min(text.length(), start + size)));
    }
    return ret;
}

Myślę, że nie warto używać do tego wyrażenia regularnego.

EDYCJA: Moje uzasadnienie dla nieużywania wyrażenia regularnego:

  • To nie używa żadnego z prawdziwego dopasowania wzorców wyrażeń regularnych. To tylko liczy.
  • I podejrzewam, powyższe będzie bardziej skuteczne, choć w większości przypadków nie ma znaczenia
  • Jeśli potrzebujesz użyć zmiennych rozmiarów w różnych miejscach, masz albo powtórzenie, albo funkcję pomocniczą do zbudowania samego wyrażenia regularnego na podstawie parametru - ick.
  • Wyrażenie regularne podane w innej odpowiedzi najpierw nie skompilowało się (nieprawidłowe ucieczki), a następnie nie zadziałało. Mój kod zadziałał za pierwszym razem. To bardziej świadczy o użyteczności wyrażeń regularnych w porównaniu z prostym kodem IMO.
Jon Skeet
źródło
8
@Emil: Właściwie nie prosiłeś o wyrażenie regularne. Jest w tagach, ale w samym pytaniu nic nie wymaga podania wyrażenia regularnego. Umieszczasz tę metodę w jednym miejscu, a następnie możesz podzielić ciąg w jednej bardzo czytelnej instrukcji w dowolnym miejscu kodu.
Jon Skeet
3
Emil, nie do tego służy regex. Kropka.
Chris,
3
@Emil: Jeśli chcesz mieć jedną linijkę do dzielenia sznurka, Splitter.fixedLength(4)poleciłbym guawę zgodnie z sugestią seanizera.
ColinD
2
@Jay: daj spokój, nie musisz być tak sarkastyczny. Jestem pewien, że da się to zrobić używając wyrażenia regularnego w jednej linii. Podciąg o stałej długości to także wzorzec. Co powiesz o tej odpowiedzi. stackoverflow.com/questions/3760152/… .
Emil
4
@Emil: Nie chciałem, żeby to było niegrzeczne, po prostu kapryśne. Poważną częścią mojej uwagi było to, że chociaż tak, jestem pewien, że mógłbyś wymyślić Regex, aby to zrobić - widzę, że Alan Moore ma taki, który, jak twierdzi, działa - jest to tajemnicze i dlatego trudne dla późniejszego programisty zrozumieć i utrzymać. Rozwiązanie podciągowe może być intuicyjne i czytelne. Zobacz czwarty punkt Jona Skeeta: Zgadzam się z tym w 100%.
Jay
71

Z Google Guava jest to bardzo łatwe :

for(final String token :
    Splitter
        .fixedLength(4)
        .split("Thequickbrownfoxjumps")){
    System.out.println(token);
}

Wynik:

Theq
uick
brow
nfox
jump
s

Lub jeśli potrzebujesz wyniku jako tablicy, możesz użyć tego kodu:

String[] tokens =
    Iterables.toArray(
        Splitter
            .fixedLength(4)
            .split("Thequickbrownfoxjumps"),
        String.class
    );

Odniesienie:

Uwaga: konstrukcja rozdzielacza jest pokazana w wierszu powyżej, ale ponieważ rozdzielacze są niezmienne i wielokrotnego użytku, dobrą praktyką jest przechowywanie ich w stałych:

private static final Splitter FOUR_LETTERS = Splitter.fixedLength(4);

// more code

for(final String token : FOUR_LETTERS.split("Thequickbrownfoxjumps")){
    System.out.println(token);
}
Sean Patrick Floyd
źródło
Dzięki za post (za poinformowanie mnie o metodzie biblioteki guava) .Ale będę musiał zaakceptować odpowiedź wyrażenia regularnego stackoverflow.com/questions/3760152/… ponieważ nie wymaga to żadnej biblioteki innej firmy ani jednej linijki.
Emil
1
Dołączanie setek KB kodu biblioteki tylko po to, aby wykonać to proste zadanie, prawie na pewno nie jest właściwe.
Jeffrey Blattman
2
@JeffreyBlattman, w tym guawa tylko z tego powodu, to prawdopodobnie przesada, prawda. Ale i tak używam go jako biblioteki ogólnego przeznaczenia w całym moim kodzie Java, więc dlaczego nie skorzystać z tej jednej dodatkowej funkcjonalności
Sean Patrick Floyd
jakikolwiek sposób na ponowne połączenie z separatorem?
Aquarius Power
1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger
14

Jeśli używasz bibliotek ogólnego przeznaczenia Google guava (i szczerze mówiąc, każdy nowy projekt Java prawdopodobnie powinien ), jest to niesamowicie trywialne w przypadku klasy Splitter :

for (String substring : Splitter.fixedLength(4).split(inputString)) {
    doSomethingWith(substring);
}

i to wszystko . Proste jak!

Cowan
źródło
8
public static String[] split(String src, int len) {
    String[] result = new String[(int)Math.ceil((double)src.length()/(double)len)];
    for (int i=0; i<result.length; i++)
        result[i] = src.substring(i*len, Math.min(src.length(), (i+1)*len));
    return result;
}
Saul
źródło
Ponieważ src.length()i lensą oba int, twoje wywołanie ceiling nie spełnia tego, czego chcesz - sprawdź, jak robią to niektóre inne odpowiedzi: (src.length () + len - 1) / len
Michael Brewer-Davis
@Michael: Słuszna uwaga. Nie testowałem tego z ciągami o innej długości. To jest teraz naprawione.
Saul
6
public String[] splitInParts(String s, int partLength)
{
    int len = s.length();

    // Number of parts
    int nparts = (len + partLength - 1) / partLength;
    String parts[] = new String[nparts];

    // Break into parts
    int offset= 0;
    int i = 0;
    while (i < nparts)
    {
        parts[i] = s.substring(offset, Math.min(offset + partLength, len));
        offset += partLength;
        i++;
    }

    return parts;
}
Grodriguez
źródło
6
Nie interesuje cię, czy masz coś przeciwko forpętlom?
Jon Skeet
forPętla jest rzeczywiście bardziej „naturalny” Zastosowanie wyborem dla tego :-) Dzięki za wskazanie tego.
Grodriguez
3

Możesz użyć substringfrom String.class(obsługa wyjątków) lub z Apache lang commons (obsługuje wyjątki za Ciebie)

static String   substring(String str, int start, int end) 

Umieść go w pętli i gotowe.

pakore
źródło
1
Co jest nie tak z substringmetodą w Stringklasie standardowej ?
Grodriguez
Wersja commons unika wyjątków (poza granicami i tym podobnych)
Thilo
7
Widzę; Powiedziałbym, że wolę „unikać wyjątków”, zamiast tego kontrolując parametry w kodzie wywołującym.
Grodriguez
2

Wolę to proste rozwiązanie:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);
Cheetah Coder
źródło
Nie rób tego! Ciąg jest niezmienny, więc Twój kod musi kopiować cały pozostały ciąg co 4 znaki. Twój fragment zajmuje zatem kwadratowy, a nie liniowy czas w rozmiarze String.
Tobias
@Tobias: Nawet jeśli String był zmienny, ten fragment kodu tworzy wspomnianą nadmiarową kopię, z wyjątkiem złożonych procesów kompilacji, które go dotyczą. Jedynym powodem użycia tego fragmentu kodu jest prostota kodu.
Cheetah Coder
Czy zmieniłeś kod od czasu jego opublikowania? Najnowsza wersja w rzeczywistości nie tworzy kopii - funkcja substring () działa wydajnie (stały czas, przynajmniej na starszych wersjach Javy); zachowuje odniesienie do znaku [] całego łańcucha (przynajmniej w starszych wersjach Javy), ale w tym przypadku jest to w porządku, ponieważ zachowujesz wszystkie znaki. Tak więc najnowszy kod, który tu masz, jest w rzeczywistości w porządku (modulo, że twój kod wypisuje pusty wiersz, jeśli treść zaczyna się jako pusty ciąg, co może nie być tym, co zamierzasz).
Tobias
@Tobias: Nie pamiętam żadnej zmiany.
Cheetah Coder
@Tobias substringimplementacja zmieniła się wraz z Javą 7, aktualizacją 6 w połowie 2012 roku, kiedy pola offseti countzostały usunięte z Stringklasy. Tak więc złożoność substringzmieniła się w liniową na długo przed udzieleniem tej odpowiedzi. Ale dla małej struny, takiej jak na przykładzie, nadal działa wystarczająco szybko, a dla dłuższych ... cóż, to zadanie rzadko występuje w praktyce.
Holger
2

Oto implementacja z jedną linijką przy użyciu strumieni Java8:

String input = "Thequickbrownfoxjumps";
final AtomicInteger atomicInteger = new AtomicInteger(0);
Collection<String> result = input.chars()
                                    .mapToObj(c -> String.valueOf((char)c) )
                                    .collect(Collectors.groupingBy(c -> atomicInteger.getAndIncrement() / 4
                                                                ,Collectors.joining()))
                                    .values();

Daje następujący wynik:

[Theq, uick, brow, nfox, jump, s]
Pankaj Singhal
źródło
1
To okropne rozwiązanie, walczące z intencją API, wykorzystujące funkcje stanowe i znacznie bardziej skomplikowane niż zwykła pętla, nie mówiąc już o narzutach związanych z boksowaniem i łączeniem ciągów. Jeśli chcesz rozwiązanie Stream, użyj czegoś takiegoString[] result = IntStream.range(0, (input.length()+3)/4) .mapToObj(i -> input.substring(i *= 4, Math.min(i + 4, input.length()))) .toArray(String[]::new);
Holger
2

Oto jeden-liner wersja wykorzystująca Java 8 IntStream określić indeksy początków plastrów:

String x = "Thequickbrownfoxjumps";

String[] result = IntStream
                    .iterate(0, i -> i + 4)
                    .limit((int) Math.ceil(x.length() / 4.0))
                    .mapToObj(i ->
                        x.substring(i, Math.min(i + 4, x.length())
                    )
                    .toArray(String[]::new);
Marko Previsic
źródło
1

Jeśli chcesz podzielić ciąg równo do tyłu, tj. Od prawej do lewej, na przykład, aby podzielić 1010001111na [10, 1000, 1111], oto kod:

/**
 * @param s         the string to be split
 * @param subLen    length of the equal-length substrings.
 * @param backwards true if the splitting is from right to left, false otherwise
 * @return an array of equal-length substrings
 * @throws ArithmeticException: / by zero when subLen == 0
 */
public static String[] split(String s, int subLen, boolean backwards) {
    assert s != null;
    int groups = s.length() % subLen == 0 ? s.length() / subLen : s.length() / subLen + 1;
    String[] strs = new String[groups];
    if (backwards) {
        for (int i = 0; i < groups; i++) {
            int beginIndex = s.length() - subLen * (i + 1);
            int endIndex = beginIndex + subLen;
            if (beginIndex < 0)
                beginIndex = 0;
            strs[groups - i - 1] = s.substring(beginIndex, endIndex);
        }
    } else {
        for (int i = 0; i < groups; i++) {
            int beginIndex = subLen * i;
            int endIndex = beginIndex + subLen;
            if (endIndex > s.length())
                endIndex = s.length();
            strs[i] = s.substring(beginIndex, endIndex);
        }
    }
    return strs;
}
Ivan Huang
źródło
1

używam następującego rozwiązania java 8:

public static List<String> splitString(final String string, final int chunkSize) {
  final int numberOfChunks = (string.length() + chunkSize - 1) / chunkSize;
  return IntStream.range(0, numberOfChunks)
                  .mapToObj(index -> string.substring(index * chunkSize, Math.min((index + 1) * chunkSize, string.length())))
                  .collect(toList());
}
rloeffel
źródło
0

Java 8 rozwiązanie (jak to ale nieco prostsze):

public static List<String> partition(String string, int partSize) {
  List<String> parts = IntStream.range(0, string.length() / partSize)
    .mapToObj(i -> string.substring(i * partSize, (i + 1) * partSize))
    .collect(toList());
  if ((string.length() % partSize) != 0)
    parts.add(string.substring(string.length() / partSize * partSize));
  return parts;
}
Timofey Gorshkov
źródło
-1

Zapytałem @Alan Moore w komentarzu do przyjętego rozwiązania jaki sposób można obsługiwać ciągi znaków z nowymi . Zasugerował użycie DOTALL.

Korzystając z jego sugestii, stworzyłem małą próbkę tego, jak to działa:

public void regexDotAllExample() throws UnsupportedEncodingException {
    final String input = "The\nquick\nbrown\r\nfox\rjumps";
    final String regex = "(?<=\\G.{4})";

    Pattern splitByLengthPattern;
    String[] split;

    splitByLengthPattern = Pattern.compile(regex);
    split = splitByLengthPattern.split(input);
    System.out.println("---- Without DOTALL ----");
    for (int i = 0; i < split.length; i++) {
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    }
    /* Output is a single entry longer than the desired split size:
    ---- Without DOTALL ----
    [Idx: 0, length: 26] - [B@17cdc4a5
     */


    //DOTALL suggested in Alan Moores comment on SO: https://stackoverflow.com/a/3761521/1237974
    splitByLengthPattern = Pattern.compile(regex, Pattern.DOTALL);
    split = splitByLengthPattern.split(input);
    System.out.println("---- With DOTALL ----");
    for (int i = 0; i < split.length; i++) {
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    }
    /* Output is as desired 7 entries with each entry having a max length of 4:
    ---- With DOTALL ----
    [Idx: 0, length: 4] - [B@77b22abc
    [Idx: 1, length: 4] - [B@5213da08
    [Idx: 2, length: 4] - [B@154f6d51
    [Idx: 3, length: 4] - [B@1191ebc5
    [Idx: 4, length: 4] - [B@30ddb86
    [Idx: 5, length: 4] - [B@2c73bfb
    [Idx: 6, length: 2] - [B@6632dd29
     */

}

Ale podoba mi się również rozwiązanie @Jon Skeets w https://stackoverflow.com/a/3760193/1237974 . Aby zapewnić łatwość obsługi w większych projektach, w których nie wszyscy są jednakowo doświadczeni w wyrażeniach regularnych, prawdopodobnie użyłbym rozwiązania Jons.

joensson
źródło
-1

Innym rozwiązaniem brutalnej siły może być:

    String input = "thequickbrownfoxjumps";
    int n = input.length()/4;
    String[] num = new String[n];

    for(int i = 0, x=0, y=4; i<n; i++){
    num[i]  = input.substring(x,y);
    x += 4;
    y += 4;
    System.out.println(num[i]);
    }

Gdzie kod po prostu przechodzi przez ciąg z podciągami

Hubbly
źródło
-1
    import static java.lang.System.exit;
   import java.util.Scanner;
   import Java.util.Arrays.*;


 public class string123 {

public static void main(String[] args) {


  Scanner sc=new Scanner(System.in);
    System.out.println("Enter String");
    String r=sc.nextLine();
    String[] s=new String[10];
    int len=r.length();
       System.out.println("Enter length Of Sub-string");
    int l=sc.nextInt();
    int last;
    int f=0;
    for(int i=0;;i++){
        last=(f+l);
            if((last)>=len) last=len;
        s[i]=r.substring(f,last);
     // System.out.println(s[i]);

      if (last==len)break;
       f=(f+l);
    } 
    System.out.print(Arrays.tostring(s));
    }}

Wynik

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

 ["Theq","uick","brow","nfox","jump","s"]
Ravichandra
źródło
-1
@Test
public void regexSplit() {
    String source = "Thequickbrownfoxjumps";
    // define matcher, any char, min length 1, max length 4
    Matcher matcher = Pattern.compile(".{1,4}").matcher(source);
    List<String> result = new ArrayList<>();
    while (matcher.find()) {
        result.add(source.substring(matcher.start(), matcher.end()));
    }
    String[] expected = {"Theq", "uick", "brow", "nfox", "jump", "s"};
    assertArrayEquals(result.toArray(), expected);
}
Adrian-Bogdan Ionescu
źródło
-1

Oto moja wersja oparta na strumieniach RegEx i Java 8. Warto wspomnieć, że Matcher.results()metoda jest dostępna od wersji Java 9.

Zawiera test.

public static List<String> splitString(String input, int splitSize) {
    Matcher matcher = Pattern.compile("(?:(.{" + splitSize + "}))+?").matcher(input);
    return matcher.results().map(MatchResult::group).collect(Collectors.toList());
}

@Test
public void shouldSplitStringToEqualLengthParts() {
    String anyValidString = "Split me equally!";
    String[] expectedTokens2 = {"Sp", "li", "t ", "me", " e", "qu", "al", "ly"};
    String[] expectedTokens3 = {"Spl", "it ", "me ", "equ", "all"};

    Assert.assertArrayEquals(expectedTokens2, splitString(anyValidString, 2).toArray());
    Assert.assertArrayEquals(expectedTokens3, splitString(anyValidString, 3).toArray());
}
itachi
źródło
-1
public static String[] split(String input, int length) throws IllegalArgumentException {

    if(length == 0 || input == null)
        return new String[0];

    int lengthD = length * 2;

    int size = input.length();
    if(size == 0)
        return new String[0];

    int rep = (int) Math.ceil(size * 1d / length);

    ByteArrayInputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_16LE));

    String[] out = new String[rep];
    byte[]  buf = new byte[lengthD];

    int d = 0;
    for (int i = 0; i < rep; i++) {

        try {
            d = stream.read(buf);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(d != lengthD)
        {
            out[i] = new String(buf,0,d, StandardCharsets.UTF_16LE);
            continue;
        }

        out[i] = new String(buf, StandardCharsets.UTF_16LE);
    }
    return out;
}
Użytkownik8461
źródło
-1
public static List<String> getSplittedString(String stringtoSplit,
            int length) {

        List<String> returnStringList = new ArrayList<String>(
                (stringtoSplit.length() + length - 1) / length);

        for (int start = 0; start < stringtoSplit.length(); start += length) {
            returnStringList.add(stringtoSplit.substring(start,
                    Math.min(stringtoSplit.length(), start + length)));
        }

        return returnStringList;
    }
Raj Hirani
źródło