Reputation: 18568
I am interested in a stream
y way to get a sublist out of an ordered list of objects. The sublist should start with an object matching a given condition concerning one of its attributes and it should end with an object matching a different condition.
Let's say, I have the following class Element
:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Element implements Comparable<Element> {
String abbreviation;
LocalDateTime creationTime;
public Element(String abbreviation, LocalDateTime creationTime) {
this.abbreviation = abbreviation;
this.creationTime = creationTime;
}
public String getAbbreviation() {
return abbreviation;
}
public void setAbbreviation(String abbreviation) {
this.abbreviation = abbreviation;
}
public LocalDateTime getCreationTime() {
return creationTime;
}
public void setCreationTime(LocalDateTime creationTime) {
this.creationTime = creationTime;
}
@Override
public int compareTo(Element otherElement) {
return this.creationTime.compareTo(otherElement.getCreationTime());
}
@Override
public String toString() {
return "[" + abbreviation + ", "
+ creationTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "]";
}
}
I receive a List<Element>
which is ordered by creationTime
. Now I want to find out if there are elements between two specific ones only regarding their abbreviation
. I know how to filter
single elements or lists of elements matching a given abbreviation
using findFirst()
or findAny()
and Collectors.toList()
. However, I just don't know how to find a sublist beginning with a certain abbreviation
and ending with another one because the elements in between do (and should) not match the conditions and there are two different conditions.
Assume I have the following List<Element>
(-generating method):
private static List<Element> generateSomeElements() {
List<Element> elements = new ArrayList<Element>();
LocalDateTime now = LocalDateTime.now();
Element elementOne = new Element("ABC", now.minus(14, ChronoUnit.DAYS));
Element elementTwo = new Element("DEF", now.minus(13, ChronoUnit.DAYS));
Element elementThree = new Element("GHI", now.minus(12, ChronoUnit.DAYS));
Element elementFour = new Element("JKL", now.minus(11, ChronoUnit.DAYS));
Element elementFive = new Element("MNO", now.minus(10, ChronoUnit.DAYS));
Element elementSix = new Element("PQR", now.minus(9, ChronoUnit.DAYS));
Element elementSeven = new Element("STU", now.minus(8, ChronoUnit.DAYS));
Element elementEight = new Element("VWX", now.minus(7, ChronoUnit.DAYS));
Element elementNine = new Element("YZ1", now.minus(6, ChronoUnit.DAYS));
Element elementTen = new Element("234", now.minus(5, ChronoUnit.DAYS));
elements.add(elementOne);
elements.add(elementTwo);
elements.add(elementThree);
elements.add(elementFour);
elements.add(elementFive);
elements.add(elementSix);
elements.add(elementSeven);
elements.add(elementEight);
elements.add(elementNine);
elements.add(elementTen);
return elements;
}
I need the sublist from (including) "MNO"
to (including) "YZ1"
keeping the order it has in the source list, leaving the creationTime
aside.
I know how to do that without using streams (like finding indexes of start and end and then getting the elements from index start
to index end
), which might be sufficient but is somehow old-fashioned and not really satisfying my greed for utilizing modern APIs.
I could add the ways I made it work, but they would just be another form of describing the problem which I am not able to resolve using a single stream-statement. In addition, those ways are explained in a multitude of different questions asking for a general possibility and not targeting stream API.
In general:
There are two conditions and aList<Element>
where a sublist is to be collected whose first element is the one matching condition 1 and whose last element it the one matching condition 2 while all elements between those are collected, too, and the order stays as it is in the origin.
So if you know a possibility of getting the desired sublist with a single stream API statement, please add it as an answer.
I will have a look at the reduce()
method in the meantime, maybe that is the answer to the mystery…
EDIT
I found a solution which gives me the desired result using stream()
calls, but I need three of them. One which fetches the element that matches the first condition, a second one that finds the matching element for the second condition and then one which fetches the desired sublist using the creationTime
s of the two elements that were found in the first two stream()
s:
public static void main(String[] args) {
List<Element> elements = generateSomeElements();
// print origin once followed by a horizontal separator for the human eye
elements.forEach(element -> System.out.println(element.toString()));
System.out.println("————————————————————————————————");
// find the reference object which should be the first element of the desired
// sublist
Element fromIncluding = elements.stream()
.filter(element -> element.getAbbreviation().equals("MNO")).findFirst()
.get();
Element toIncluding = elements.stream()
.filter(element -> element.getAbbreviation().equals("YZ1")).findFirst()
.get();
List<Element> mnoToYz1 = elements.stream()
.filter(element ->
(element.getCreationTime().isAfter(fromIncluding.getCreationTime())
&& element.getCreationTime().isBefore(toIncluding.getCreationTime()))
|| element.getCreationTime().isEqual(fromIncluding.getCreationTime())
|| element.getCreationTime().isEqual(toIncluding.getCreationTime()))
.collect(Collectors.toList());
mnoToYz1.forEach(element -> System.out.println(element.toString()));
}
Upvotes: 3
Views: 1615
Reputation: 31878
If the input list is ordered, another way to get the subList
would be using the range instead of the object comparison in your example as :
int fromIncluding = IntStream.range(0, elements.size())
.filter(i -> elements.get(i).getAbbreviation().equals("MNO"))
.findFirst().orElse(0);
int toIncluding = IntStream.range(fromIncluding, elements.size())
.filter(i -> elements.get(i).getAbbreviation().equals("YZ1"))
.findFirst().orElse(elements.size() - 1);
List<Element> mnoToYz1 = elements.subList(fromIncluding, toIncluding+1);
Upvotes: 1
Reputation: 10661
Looks like you need takeWhile
/ dropWhile
methods on the stream. Take a look at StreamEx library by Tagir Valeev
Upvotes: 1