Розділити рядок на підряди рівних за довжиною в Java


125

Як розділити рядок "Thequickbrownfoxjumps"на підрядки однакового розміру в Java. Напр. "Thequickbrownfoxjumps"з 4 рівних розмірів слід дати вихід.

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

Подібне запитання:

Розбийте рядок на підрядки однакової довжини у Scala


4
Що ви спробували? Чому це не вийшло?
Тіло

2
Чи потрібно для цього використовувати регулярний вираз? Просто запитую через тег регулярного вираження ...
Тім Піцкер

@Thilo посилання, яке він опублікував, призначене для Scala, він запитує про те саме на Java
Jaydeep Patel

@Thilo: Я запитував, як це зробити в Java, як відповідь, яку дав скала.
Еміль

Відповіді:


226

Ось версія з однорівковою підкладкою з регулярним виразом:

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

\G- твердження нульової ширини, яке відповідає позиції, де закінчився попередній матч. Якщо не було ніякого попереднього матчу, він збігається з початком введення, так само , як \A. Приклад, що додається, відповідає списку позиції, яка складається з чотирьох символів разом із кінцем останнього матчу.

І дивлячись позаду, і \Gвдосконалені функції регулярного виведення, не підтримувані всіма смаками Крім того, \Gне застосовується послідовно в ароматах, які його підтримують. Цей трюк буде працювати (наприклад) у Java , Perl, .NET та JGSoft, але не в PHP (PCRE), Ruby 1.9+ або TextMate (обидва Oniguruma). JavaScript /y(липкий прапор) не такий гнучкий, як \Gі не може бути використаний таким чином, навіть якщо JS підтримує погляд позаду.

Слід зазначити, що я не обов'язково рекомендую це рішення, якщо у вас є інші варіанти. Рішення без повторного вираження в інших відповідях можуть бути довшими, але вони також самостійно документують; це якраз навпаки . ;)

Крім того, це не працює в Android, який не підтримує використання \Gв непридатності.


2
У PHP 5.2.4 працює наступний код: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Ігор

5
Для запису, використовуючи String.substring()замість регулярного вираження, вимагаючи ще декількох зайвих рядків коду, буде працювати десь на 5 разів швидше ...
звернув мур

2
У Java це не працює для рядка з новими рядками. Він перевірятиме лише до першого нового рядка, і якщо цей новий рядок буде до розміру розділеного розміру, то рядок не розбивається. Або я щось пропустив?
joensson

5
Для повноти картини : Розщеплення тексту поверх мультиліній потребує префікс (?s)в регулярному виразі: (?s)(?<=\\G.{4}).
бубон

1
Java перешкоджає цьому повністю під час компіляції:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Джефрі Блатман

132

Що ж, зробити це досить просто за допомогою простих арифметичних і рядкових операцій:

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

Я не думаю, що дійсно варто використовувати для цього регулярний вираз.

EDIT: Мої міркування щодо використання регулярного вираження:

  • Тут не використовується жодне реальне узгодження регексів. Це просто підрахунок.
  • Я підозрюю, що вищесказане буде більш ефективним, хоча в більшості випадків це не має значення
  • Якщо вам потрібно використовувати різні розміри в різних місцях, у вас є або повторення, або допоміжна функція, щоб створити сам регулярний вираз на основі параметра - ick.
  • Регекс, наданий в іншій відповіді, спершу не компілював (недійсний пропуск), а потім не працював. Мій код працював перший раз. Це більше свідчить про зручність використання регулярних виразів та звичайного коду, IMO.

8
@Emil: Насправді ви не просили регулярного виразу. Це в тегах, але нічого в самому питанні не вимагає регулярного вираження. Ви розміщуєте цей метод в одному місці, а потім можете розділити рядок лише на одне дуже читабельне твердження в будь-якому місці коду.
Джон Скіт

3
Еміль - це не те, чим призначений регулярний вираз. Період.
Кріс

3
@Emil: Якщо ви хочете однолінійку для розбиття рядка, я рекомендую Guava, Splitter.fixedLength(4)як це запропонував seanizer.
ColinD

2
@Jay: давай, ти не повинен бути такою саркастичною. Я впевнений, що це можна зробити за допомогою регулярного вираження лише в одній рядку. Підрядка з фіксованою довжиною також є шаблоном. Що ви говорите про цю відповідь. stackoverflow.com/questions/3760152/… .
Еміль

4
@Emil: Я не мав наміру бути грубим, просто примхливим. Серйозна частина моєї точки зору полягала в тому, що, хоча так, я впевнений, що ви можете придумати Regex, щоб зробити це - я бачу, у Алана Мура є такий, який, як він стверджує, працює, - це загадково і тому для подальшого програміста важко розуміти і підтримувати. Рішення підрядків може бути інтуїтивно зрозумілим і читабельним. Дивіться четверту кулю Джона Скіта: Я згоден з цим 100%.
Джей

71

З Google Guava це дуже просто :

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

Вихід:

Theq
uick
brow
nfox
jump
s

Або якщо вам потрібен результат у вигляді масиву, ви можете використовувати цей код:

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

Довідка:

Примітка: Конструкція спліттерів показана в рядку вище, але оскільки сплітери незмінні та багаторазові, їх можна зберігати в константах:

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

// more code

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

Дякую за публікацію (За те, що мені відомо про метод бібліотеки guava). Але мені доведеться прийняти відповідь на регулярний вимір stackoverflow.com/questions/3760152/…, оскільки для цього не потрібна жодна стороння бібліотека та однолінійний.
Еміль

1
Включення сотень КБ бібліотечного коду просто для виконання цього простого завдання майже точно не є правильним.
Джефрі Блатман

2
@JeffreyBlattman, включаючи Гуаву, саме для цього, мабуть, надмірність, правда. Але я все-таки використовую її як бібліотеку загального призначення у всьому своєму Java-коді, так чому б не використати цей додатковий функціонал
Шон Патрік Флойд

будь-який спосіб приєднатися назад з роздільником?
Сила Водолія

1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger

14

Якщо ви використовуєте бібліотеки гуави загального призначення Google (і, чесно кажучи, будь-який новий проект Java, мабуть, повинен бути), це шалено тривіально для класу Splitter :

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

і це все . Легко як!


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

Оскільки src.length()і lenви обидва int, ваш дзвінок ceiling не відповідає тому, що ви хочете - дізнайтеся, як це роблять деякі інші відповіді: (src.length () + len - 1) / len
Майкл Брюер-Девіс,

@Michael: Добре. Я не перевіряв це за допомогою рядків не множинної довжини. Це зараз виправлено.
Саул

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

6
Не цікаво, чи є щось проти forциклів?
Джон Скіт

forЦикл дійсно є більш «природним» використовувати вибір для цього :-) Дякую за вказівку на це.
Grodriguez

3

Ви можете використовувати substringз String.class(обробка винятків) або з Apache lang commons (він обробляє винятки для вас)

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

Покладіть його всередину петлі, і ви добре підете.


1
Що не так із substringметодом у стандартному Stringкласі?
Grodriguez

Commons версія уникає винятків (поза межами тощо)
Thilo,

7
Я бачу; Я б сказав, що я вважаю за краще «уникати винятків», контролюючи параметри в коді виклику.
Grodriguez

2

Я вважаю за краще це просте рішення:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);

Не робіть цього! Рядок є незмінним, тому ваш код повинен копіювати весь рядок, що залишився кожні 4 символи. Отже, ваш фрагмент має розмір рядка квадратичного, а не лінійного часу.
Тобіас

@Tobias: Навіть якщо String був змінним, цей фрагмент робить згадану зайву копію, за винятком складних процесів компіляції щодо нього. Єдина причина використання цього фрагмента - простота коду.
Coder Cheetah

Ви змінили свій код, коли ви вперше опублікували його? Остання версія насправді не робить копії - substring () працює ефективно (постійний час, принаймні на старих версіях Java); він зберігає посилання на char [] усієї рядка (принаймні, на старих версіях Java), але це добре в цьому випадку, оскільки ви зберігаєте всі символи. Тож останній код, який у вас тут, насправді добре (модуль, що ваш код друкує порожній рядок, якщо вміст починається як порожній рядок, який може бути не тим, що ви маєте намір).
Тобіас

@Tobias: Я не пам'ятаю жодних змін.
Гепард Кодер

@Tobias substringреалізація змінилася з Java 7, оновлення 6 в середині 2012 року, коли поля offsetта countбули видалені з Stringкласу. Тож складність substringперетворилася на лінійну задовго до того, як ця відповідь була зроблена. Але для такої невеликої струни, як приклад, вона все ще працює досить швидко і для довших рядків… ну це завдання рідко трапляється на практиці.
Холгер

2

Ось одна реалізація лайнера з використанням потоків 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();

Він дає такий вихід:

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

1
Це жахливе рішення: боротьба з наміром API, використання стаціонарних функцій і значно складніше, ніж звичайний цикл, не кажучи про накладні боксерські та строкові конкатенації. Якщо ви хочете рішення Stream, використовуйте щось на кшталтString[] 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

Ось один вкладиш версії , яка використовує Java 8 IntStream для визначення показників скибочки початку:

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

1

Якщо ви хочете розділити рядок однаково назад, тобто справа наліво, наприклад, поділити 1010001111на [10, 1000, 1111], ось код:

/**
 * @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;
}

1

я використовую таке рішення 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());
}

0

Рішення Java 8 (як це, але трохи простіше):

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

-1

Я запитав @Alan Moore в коментарі до прийнятого рішення, як можна обробляти рядки з новими рядками. Він запропонував використовувати DOTALL.

Використовуючи його пропозицію, я створив невеликий зразок того, як це працює:

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

}

Але мені подобається рішення @Jon Skeets в https://stackoverflow.com/a/3760193/1237974 також. Для ремонту у великих проектах, де не всі мають однаковий досвід у регулярних висловлюваннях, я б, ймовірно, використовував рішення Джонса.


-1

Іншим рішенням грубої сили може бути,

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

Де код просто переходить через рядок з підрядками


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

Результат

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

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

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

-1

Ось моя версія на основі потоків RegEx та Java 8. Варто згадати, що Matcher.results()метод доступний з Java 9.

Тест включений.

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

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

-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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.