Goldbones
Goldbones

Reputation: 1457

Filtering between specific values creating sublists

I have this list:

List<String> names= Arrays.asList(new String[]{"Tom", "louis", "martha", "Tom", "john", "Carol", "Tom", "Simon"});

I want to create three different lists where I put the values between Tom word:

"Tom", "louis", "martha";
"Tom", "john", "Carol";
"Tom", "Simon"

I was doing with indexes in a traditional way

My way was just;

List<Integer> positionsWithTom = new ArrayList<>();

for(int i=0; i<names.size; i++){
...
positionsWithTom = [0,3,6]
}

and then put the values between in different lists

There is not a much elegant by doing this, maybe with stream filtering somehow?

Upvotes: 1

Views: 227

Answers (2)

WJS
WJS

Reputation: 40034

I don't believe this easily lends itself to streams. Nor would a stream approach be any more clear in expressing the problem statement than a simple loop.

As long as you don't care what order the sublists are in, it is pretty straight forward using List.lastIndexOf and a while loop. Note that since this chews up the original list and the fact that these sublists don't overlap, there is no chance of the sublists being altered by changes to the original list or by each other (unless a reference to the original exists). But one can always pass the sublist as an argument to the ArrayList constructor if this is a concern.

List<String> names = new ArrayList<>(List.of("Tom", "louis",
        "martha", "Tom", "john", "Carol", "Tom", "Simon"));

List<List<String>> lists = new ArrayList<>();
    
int start;
while ((start = names.lastIndexOf("Tom")) >= 0) {
    ists.add(names.subList(start, names.size()));
    names = names.subList(0, start);
}
    
lists.forEach(System.out::println);

Prints

[Tom, Simon]
[Tom, john, Carol]
[Tom, louis, martha]

Note that the order can be reversed by specifying an index of 0 in the add method. But that can be expensive for ArrayLists due to the copying. A LinkedList would alleviate that expense for a bit of overhead.

And just for completion, here is my stream solution.

  • first, stream the names in successive sublists based on where Tom is located. So the first list contains all the names, the second, all but the last "Tom" grouping, and so forth.
  • Then for each of those sublists, pick off the last sublist starting with Tom from the end.
  • and put the lists in a collection.
List<List<String>> result = Stream.iterate(
            names,
            n -> n.size() > 0 && n.lastIndexOf("Tom") >= 0,
            n -> n.subList(0, n.lastIndexOf("Tom")))
        .map(n -> n.subList(n.lastIndexOf("Tom"), n.size()))
        .collect(Collectors.toList());

result.forEach(System.out::println);

Prints

[Tom, Simon]
[Tom, john, Carol]
[Tom, louis, martha]

I still prefer the while loop solution as I think it is cleaner and more succinct. Someone more proficient in streams could probably do it better.

Upvotes: 1

H&#252;lya
H&#252;lya

Reputation: 3433

First I collected the indexes of elements ([0, 3, 6]) that have the value "Tom" to the tomindex list. Then I used subList() method.

This part:

j == tomindex.size() - 1 ? names.subList(tomindex.get(j), names.size())
                        : names.subList(tomindex.get(j), tomindex.get(j + 1))

is not to miss the last one. Last element of tomindex is 6 and we need to fetch values between the indexes 6 and 8 so we use names.size() at the end.

Try this:

List<Integer> tomindex = IntStream.range(0, names.size()).filter(i -> names.get(i).equals("Tom")).boxed()
        .collect(Collectors.toList());

Collection<List<String>> subLists = IntStream.range(0, tomindex.size())
        .mapToObj(j -> j == tomindex.size() - 1 ? names.subList(tomindex.get(j), names.size())
                : names.subList(tomindex.get(j), tomindex.get(j + 1)))
        .collect(Collectors.toList());

System.out.println(subLists);

Output:

[[Tom, louis, martha], [Tom, john, Carol], [Tom, Simon]]

Upvotes: 1

Related Questions