Reputation: 73
I'm trying to filter a resource and exclude some elements based on a field. To exclude I have a set (that contains a id that needs to be excluded) and a list (it contains multiple range of ids that needs to be excluded). I wrote the below logic and I'm not satisfied with the 2nd filter logic. Is there any better way we can do it with Java 8? I need to do the same for including ranges as well.
Set<String> extensionsToExclude = new HashSet<>(Arrays.asList("20","25","60","900"));
List<String> rangesToExclude = new ArrayList<>(Arrays.asList("1-10","20-25","50-70","1000-1000000"));
return directoryRecords.stream()
.filter((directoryRecord) -> !extensionsToExclude.contains(directoryRecord.getExtensionNumber()))
.filter((directoryRecord -> {
Boolean include = true;
for(String s : rangesToExclude) {
String [] rangeArray = s.split("-");
Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());
if(extension <= Integer.parseInt(rangeArray[0]) && extension >= Integer.parseInt(rangeArray[1])) {
include = false;
}
}
return include;
}))
.collect(Collectors.toList());
Thanks :)
Upvotes: 7
Views: 1774
Reputation: 4120
I would suggest similar to ernest_k's answer with Range
.
But in this approach you can use both collection to create List<Range>
(this "20"
can be treated as "20-20"
) and change the filter condition to use negation with anyMatch
.
List<Range> ranges = Stream.concat(extensionsToExclude.stream(), rangesToExclude.stream())
.map(Range::creatRange).collect(Collectors.toList());
return directoryRecords.stream()
.filter(directoryRecord -> !ranges.stream()
.anyMatch(r -> r.isInRange(
Integer.parseInt(directoryRecord.getExtensionNumber()))
))
.collect(Collectors.toList());
class Range {
private int start;
private int end;
Range(String start, String end) {
this.start = Integer.parseInt(start);
this.end = Integer.parseInt(end);
}
static Range creatRange(String range) {
if (range.contains("-")) {
return new Range(range.split("-")[0], range.split("-")[1]);
}
return new Range(range, range);
}
boolean isInRange(int n) {
return start <= n && n <= end;
}
}
Creation of List<Range> ranges
can be change to remove points from Set<String> extensionsToExclude
that are in the range created from List<String> rangesToExclud
. Then unnecessary ranges will not be created.
List<Range> ranges = rangesToExclude.stream().map(Range::creatRange)
.collect(Collectors.toCollection(ArrayList::new));
extensionsToExclude.stream()
.filter(v -> !ranges.stream()
.anyMatch(r -> r.isInRange(Integer.parseInt(v))))
.map(Range::creatRange)
.forEach(ranges::add);
Upvotes: 3
Reputation: 45309
I would do it with a custom Range
class, something like:
class Range {
private long start;
private long end;
Range(String start, String end) {
this.start = Long.parseLong(start);
this.end = Long.parseLong(end);
}
Range(String range) {
this(range.split("-")[0], range.split("-")[1]);
}
boolean inRange(long n) {
returns start <= n && n <= end;
}
}
Which will make something like this possible:
List<Range> ranges = rangesToExclude.stream()
.map(Range::new).collect(Collectors.toList());
return directoryRecords.stream()
.filter((directoryRecord) -> !extensionsToExclude
.contains(directoryRecord.getExtensionNumber()))
.filter(directoryRecord -> ranges.stream()
.noneMatch(r -> r.isInRange(directoryRecord)))
.collect(Collectors.toList());
I personally find your first filter good enough to be preserved as is.
Upvotes: 8
Reputation: 13485
you can do an early break if the range condition is true, rather than wait for all the entries to be evaluated.
if(extension >= Integer.parseInt(rangeArray[0]) && extension <= Integer.parseInt(rangeArray[1])) {
return true;
}
otherwise just return false after the for loop.
Upvotes: 0