How to split a String array at every nth word?

Viewed 755

I'm stumped on a (probably very simple) idea. I am reading in a text file with a BufferedReader, and splitting the string at whitespaces. I need to create a new string array and group the new elements in words of 3s from the previous array e.g. {The, quick, brown, fox, jumped, over, the, lazy, cat} ⇒ {The quick brown, fox jumped over, the lazy cat}.

So far I have come up with a very inefficient attempt that iterates over the array and concatenates elements and whitespaces to a new string array. It also leaves nulls in the new array because I am incrementing by i+3 each time.

String line = "";
while ((line = br.readLine()) != null) {
    String words[] = line.split(" ");
    String[] result = new String[words.length - 1];

    for (int i = 0; i < words.length - 3; i += 3) {
        result[i] = words[i] + " " + words[i + 1] + " " + words[i + 2];
    }
    for (int i = 0; i < result.length; i++) {
        System.out.println(result[i]);
    }
}

An example of the output:

a Goose who null null was taking a null null walk by the null null side of the null null "Good heavens!" cried null null the Goose.
4 Answers

Your friends are:

  1. Math#ceil
  2. String#join
  3. Arrays#copyOfRange
  4. Integer#min

Demo:

import java.util.Arrays;

class Main {
    public static void main(String[] args) {
        final int SIZE = 3;
        String[] arr = { "The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "cat", "Hello" };
        String[] result = new String[(int) Math.ceil((double) arr.length / SIZE)];
        for (int i = 0, j = 0; i < arr.length; i += SIZE, j++) {
            result[j] = String.join(" ", Arrays.copyOfRange(arr, i, Integer.min(arr.length, i + SIZE)));
        }

        System.out.println(Arrays.toString(result));
    }
}

Output:

[The quick brown, fox jumped over, the lazy cat, Hello]

How it works

Arrays.copyOfRange copies arr from the index, i to the index, Integer.min(arr.length, i + SIZE) - 1 where Integer.min(arr.length, i + SIZE) returns the minimum of arr.length and i + SIZE. This is required for the last copy for which i + SIZE may go beyond arr.length causing IndexOutOfBoundsException. Given below is an illustration for 10 elements in arr and SIZE = 3:

When i = 0, Arrays.copyOfRange(arr, 0, Integer.min(10, 0 + 3)) => Arrays.copyOfRange(arr, 0, 3) which will return the subarray out of arr from the index, 0 to the index, 2.


When i = 3, Arrays.copyOfRange(arr, 3, Integer.min(10, 3 + 3)) => Arrays.copyOfRange(arr, 3, 6) which will return the subarray out of arr from the index, 3 to the index, 5.


When i = 6, Arrays.copyOfRange(arr, 6, Integer.min(10, 6 + 3)) => Arrays.copyOfRange(arr, 6, 9) which will return the subarray out of arr from the index, 6 to the index, 8.


When i = 9, Arrays.copyOfRange(arr, 9, Integer.min(10, 9 + 3)) => Arrays.copyOfRange(arr, 9, 10) which will return the subarray out of arr from the index, 9 to the index, 9.


You can use the following method:

public String[] splitArray(String[] array, int splitByNumOfItems) {
    String[] newArr = new String[array.length / splitByNumOfItems];
    StringBuilder str = new StringBuilder();
    int newArrPos = 0;
    for (int i = 0; i < array.length; i++) {
        if ((i + 1) % splitByNumOfItems == 0) {
            str.append(array[i]);
            newArr[newArrPos] = str.toString();
            str.setLength(0);
            newArrPos++;
        } else {
            str.append(array[i]);
        }
    }
    return newArr;
}

Test Run

String[] before = {
        "The", "quick", "brown",
        "fox", "jumped", "over",
        "the", "lazy", "cat"};

System.out.println(Arrays.toString(splitArray(before, 3)));

Prints:

[Thequickbrown, foxjumpedover, thelazycat]

Explanation

We can create a new array called newArr which will house the items of the original array called array.

We can loop through the items of array, and everytime it loops, we check whether the current position is divisible by splitByNumOfItems. If it is, we can set it to the corresponding position in newArr, as determined by newArrPos.

Test this code:

String line = "";
while ((line = br.readLine()) != null) {
    String words[] = line.split(" ");
    String[] result = new String[((words.length - 1) / 3) + (((words.length - 1) % 3) > 0 ? 1 : 0)];
    int j = 0;
    for (int i = 0; i < result.length; i++) {
        result[i] = words[j] + (words.length > j + 1 ? (" " + words[j + 1]) : "") + (words.length > j + 2 ? (" " + words[j + 2]) : "");
        j += 3;
    }
    for (int i = 0; i < result.length; i++) {
        System.out.println(result[i]);
    }
}

You can iterate over the indices of this array with a step of three elements and at each step use Arrays.stream(T[],int,int) method to get range of elements from the current one to the next third element and concatenate them using joining with whitespace delimiter:

String[] arr = {
        "The", "quick", "brown",
        "fox", "jumped", "over",
        "the", "lazy", "cat"};
String[] arr2 = IntStream
        // every third element 0, 3, 6
        .iterate(0, i -> i < arr.length, i -> i + 3)
        // stream over the elements of the array with
        // a range from the current element to the next
        // third element, or the end of the array
        .mapToObj(i -> Arrays.stream(arr, i, Math.min(i + 3, arr.length))
                // join elements with whitespace delimiter
                .collect(Collectors.joining(" ")))
        // return an array
        .toArray(String[]::new);
// output line by line
Arrays.stream(arr2).forEach(System.out::println);
// The quick brown
// fox jumped over
// the lazy cat
Related