Suppose I have an array or a list of status, and want to filter a list for elements whose status matches any of the given. So I go on creating a predicate. I started by initializing it with the comparison to the first, then adding more conditions with or, which resulted in the minimal predicate but was a lot of code:
Predicate<Rec> predicate = null;
for (SendStatus status : statuss) {
Predicate<Rec> innerPred = nr -> nr.getStatus() == status;
if (predicate == null)
predicate = innerPred;
else
predicate = predicate.or(innerpred);
}
More elegantly, I came up with the following code:
Predicate<Rec> predicate = nr -> false;
for (SendStatus status : statuss) {
predicate = predicate.or(nr -> nr.getStatus() == status);
}
This looks nicer, but has a useless predicate at the beginning of the chain. Apache Collections had an AnyPredicate that could be composed of any number of predicates, and I’m basically looking for a replacement.
Is this superfluous predicate acceptable? Is there an even more elegant way to write this?
How about this, assuming statuss is a Collection<SendStatus>:
Predicate<Rec> predicate = nr -> statuss.stream().anyMatch(status -> nr.getStatus() == status);
Or this, if statuss is a SendStatus[]:
Predicate<Rec> predicate = nr -> Arrays.stream(statuss).anyMatch(status -> nr.getStatus() == status);
Or do as suggested by #Jerry06 in a comment, which is faster if statuss is a Set<SendStatus>, and simpler than streaming collection solution above:
Predicate<Rec> predicate = nr -> statuss.contains(nr.getStatus());
Related
I have a Vaadin 8 UI with several columns and each column can be filtered.
I want to extend one TextField so I can filter for multiple values.
The input would be like "ABC|XYC" and I want to build an OR predicate.
My current code for building the predicate looks like:
final BooleanBuilder booleanBuilder = new BooleanBuilder();
for (final PropertyFilter<?> propertyFilter : filterList) {
final Predicate p = propertyFilter.getPredicate();
if (p != null) {
booleanBuilder.and(p);
}
}
PropertyFilter extends Vaadins PredicateProvider and builds one predicate to search for the data.
My current approach is to get arguments from the full predicate. The original predicate would look like "name LIKE ABC|XYC", which I want to split.
for (final PropertyFilter<?> propertyFilter : filterList) {
BooleanBuilder internBuilder = new BooleanBuilder();
Predicate p = propertyFilter.getPredicate();
if (p != null) {
BooleanOperation bop = (BooleanOperation) p;
String arg = bop.getArg(1).toString();
String[] split = arg.split("\\|");
for(String s : split) {
internBuilder.or(Expressions.operation(bop.getType(), bop.getArg(0), ConstantImpl.create(s)));
}
}
if (p != null) {
booleanBuilder.and(p);
}
}
I have to issues with my approach.
First:
Expressions.operation shows me an error which I don't know how to fix it
The method operation(Class, Operator, Expression...) in the type Expressions is not applicable for the arguments (Class<capture#57-of ? extends Boolean>, Expression<capture#58-of ?>, Constant)
Second:
I have the feeling, that my approach is dirty and not the best way.
It is my first time, that I'm "digging" so deep into querydsl, so I'll gladly take any help or hint on how to get this clean and working.
This question already has answers here:
Filter Java Stream to 1 and only 1 element
(24 answers)
Closed 2 years ago.
I have this brainer.
I need to iterate over a Map<UUID, List<Items>> groups and return a single value!
Can I do this with Lambda and how? (how do I get the id of the group that has an item of TYPE_12? The item of TYPE_12 is going to be only one across all groups)
Thanks in advance this is my for code:
String theId = null;
for(Map.Entry<UUID, List<Item>> group : groupsOfItems) {
for (Item item : group.getValue()) {
if (item.getType() == Types.TYPE_12) {
theId = group.getKey().toString();
break;
}
}
}
If you want to use functional style, you can create a stream from you map entry set, then expand it to get a stream of each item in underlying lists :
Optional<String> result = groupOfItems.entrySet().stream()
.flatMap(entry -> entry.getValue().stream())
.filter(item -> Types.TYPE_12.equals(item.getType))
.map(Item::getId)
.findAny();
result.ifPresent(id -> System.out.println("A match has been extracted: "+id));
As is, the functional style way is not more performant than the imperative one, but is more easily adaptable. Let's say you want to know if there's more than one match, you can replace findAny by a collector with a limit :
List<String> matchedIds = groupOfItems.entrySet().stream()
.flatMap(entry -> entry.getValue().stream())
.filter(item -> Types.TYPE_12.equals(item.getType))
.map(Item::getId)
.limit(2)
.collect(Collectors.toList());
if (matched.isEmpty()) System.out.println("No match found");
else if (matched.size() == 1) System.out.println("Exactly one match found: "+matched.get(0));
else System.out.println("At least two matches exist");
Stream usage also allow parallelization if necessary, by simply adding parallel() step to your pipeline.
Here is a solution using lambdas. The difference is this one does not use flatMap and throws an exception if the required value is not found (based on the question stating that there should be one and only one TYPE_12 in the whole value set of the map).
UUID result = groupsOfItems.entrySet().stream()
.filter(e -> e.getValue().stream()
.anyMatch(item -> item.getType() == TYPE.TYPE_12))
.findAny().orElseThrow().getKey();
String theId = null;
find:
for (Map.Entry<UUID, List<Item>> group : groupsOfItems.entrySet()) {
for (Item item : group.getValue()) {
if (item.getType() == Types.TYPE_12) {
theId = group.getKey().toString();
break find;
}
}
}
No don't use lambda for this.
Just use java labels to break out of both for loops after you found your entry. (Notice the find: before the first for loop. That's a label and once you call break find; it will break out of the block marked with that label)
Also you need to use .entrySet in the first for loop. Just passing groupsOfItems won't be enough
I have found the example of solution in java:
things.stream()
.filter(
filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
I tried to rewrite it using groovy (v2.5.4):
List<System> systems = ... //load from db
WellApplicationType appType = params['appType']
Contract contract = params['contract']
List<Predicate> filterCollection = []
if (appType != null)
filterCollection.add({SystemRunlife systemRl -> systemRl.getApplicationType() == appType })
if (contract != null)
filterCollection.add({SystemRunlife systemRl -> systemRl.getContract() == contract})
...
systems.stream()
.map{system -> system.getSystemRunlife()}
.filter(filterCollection.stream().reduce{Predicate.&and}.orElse{t -> false})
It work only if I have 1 element in predicate's list. If I have more when one then I get an error:
No signature of method: Script1$_run_closure3$_closure5.doCall() is
applicable for argument types: (Script1$_run_closure1,
Script1$_run_closure2) values: [Script1$_run_closure1#7d9367fa,
Script1$_run_closure2#70c1c430] Possible solutions: doCall(),
doCall(java.lang.Object), findAll(), findAll()
Can someone tell me where is a problem?
UPD:
.filter{systemRl -> filterCollection.stream().allMatch{p -> p.test(systemRl)}}
- is not working too.
No signature of method: Script1.test() is applicable for argument
types: (com.borets.wedb.entity.SystemRunlife) values:
[com.borets.wedb.entity.SystemRunlife-5c0d826a-7f63-1ef5-7b74-bde707a1d814
[new]]
Predicate<SystemRunlife> filter = { systemRl -> true }
for (Predicate<SystemRunlife> systemRunlifePredicate : filterCollection) {
filter = (filter & systemRunlifePredicate)
}
I had combined predicates that way. Maybe someone can propose more convineint way.
I have been developing in Java 6 and using guava predicates. But I want to switch to Java 8 and use java util predicates instead. I can simply convert the below method to use the predicate but is there a smart way to use Lambda expressions and reduce the number of lines of code ? Preferably remove the temp list I am creating ? I am googling for examples but all of them are very simple ones. Thanks for you help!
private Predicate<Objt1> getLocalAttributesPredicate() {
return new Predicate<Objt1>() {
#Override
public boolean apply(Objt1 input) {
AttributeType attr = cache.get(input.getAttributeID());
List<String> attrGroupids = Lists.newArrayList();
for (AttributeGroupLinkType group : attr.getAttributeGroupLink()) {
attrGroupids.add(group.getAttributeGroupID());
}
return attrGroupids.contains(localAttrGroupId) && !attrGroupids.contains(exclustionAttrGroupId);
}
};
}
Something like the following:
private Predicate<Objt1> getLocalAttributesPredicate() {
return input -> cache.get(input.getAttributeID())
.stream()
.map(group -> group.getAttributeGroupID())
.filter(id -> id.equals(localAttrGroupId))
.filter(id -> !id.equals(exclustionAttrGroupId))
.limit(1)
.count() > 0;
}
So the predicate is returned as a lambda function, and it utilises the Stream API to traverse the list and convert its contents.
Edit: applied the optimisation suggested by #Aominè, thanks.
This is how you'd do it as of Java-8:
private Predicate<Objt1> getLocalAttributesPredicate() {
return input -> {
Set<String> accumulator = ...
AttributeType attr = cache.get(input.getAttributeID());
for(AttributeGroupLinkType group : attr.getAttributeGroupLink())
accumulator.add(group.getAttributeGroupID());
return accumulator.contains(localAttrGroupId) &&
!accumulator.contains(exclustionAttrGroupId);
};
}
Note, that I've also used a Set for the accumulator as the Contains method is much faster for a Set implementation than for a List implementation.
Is there a 'best practice' for mutating elements within a Stream? I'm specifically referring to elements within the stream pipeline, not outside of it.
For example, consider the case where I want to get a list of Users, set a default value for a null property and print it to the console.
Assuming the User class:
class User {
String name;
static User next(int i) {
User u = new User();
if (i % 3 != 0) {
u.name = "user " + i;
}
return u;
}
}
In java 7 it'd be something along the lines of:
for (int i = 0; i < 7; i++) {
User user = User.next(i);
if(user.name == null) {
user.name = "defaultName";
}
System.out.println(user.name);
}
In java 8 it would seem like I'd use .map() and return a reference to the mutated object:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> {
if (user.name == null) {
user.name = "defaultName";
}
return user;
})
//other non-terminal operations
//before a terminal such as .forEach or .collect
.forEach(it -> System.out.println(it.name));
Is there a better way to achieve this? Perhaps using .filter() to handle the null mutation and then concat the unfiltered stream and the filtered stream? Some clever use of Optional? The goal being the ability to use other non-terminal operations before the terminal .forEach().
In the 'spirit' of streams I'm trying to do this without intermediary collections and simple 'pure' operations that don't depend on side effects outside the pipeline.
Edit: The official Stream java doc states 'A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.' Given that this would be a non-interfering operation, what specifically makes it dangerous? The examples I've seen reach outside the pipeline, which is clearly sketchy.
Don't mutate the object, map to the name directly:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> user.name)
.map(name -> name == null ? "defaultName" : name)
.forEach(System.out::println);
It sounds like you're looking for peek:
.peek(user -> {
if (user.name == null) {
user.name = "defaultName";
}
})
...though it's not clear that your operation actually requires modifying the stream elements instead of just passing through the field you want:
.map(user -> (user.name == null) ? "defaultName" : user.name)
It would seem that Streams can't handle this in one pipeline. The 'best practice' would be to create multiple streams:
List<User> users = IntStream.range(0, 7)
.mapToObj(User::next)
.collect(Collectors.toList());
users.stream()
.filter(it -> it.name == null)
.forEach(it -> it.name = "defaultValue");
users.stream()
//other non-terminal operations
//before terminal operation
.forEach(it -> System.out.println(it.name));