Java 8 summarise by multiple predicate - java-8

Consider the following pojo class annotated using Lombok annotations
#Setter
#Getter
#Builder
#ToString
public class User {
private String firstName;
private String lastName;
private Gender gender;
private Integer age;
private Integer points;
}
requirement is to get LongSummaryStatistics of 'points' attribute for following predicates:
Predicate<User> adultMenPredicate = user -> Gender.MALE == user.getGender && user.getAge()>18
Predicate<User> adultWomenPredicate = user -> Gender.FEMALE == user.getGender && user.getAge()>18
Predicate<User> minorPredicate = user -> user.getAge()<18
My current Implementation is :
private LongSummaryStatistics getPointStats(List<User> users, Predicate<User> predicate) {
return users.stream().filter(predicate).mapToLong(User::getPoints).summaryStatistics();
}
System.out.println("point stats for adult men: " + getPointStats(users, adultMenPredicate));
System.out.println("point stats for adult women: " + getPointStats(users, adultWomenPredicate));
System.out.println("point stats for minors: " + getPointStats(users, minorPredicate));
Here we are iterating the users collection thrice. Is it possible to get this in just one iteration ?

I've figured out something like that:
public static void main(String [] args) {
List<User> users = ImmutableList.of(new User("a", "s", MALE, 19, 22),
new User("a", "s", MALE, 15, 49),
new User("a", "s", MALE, 22, 11),
new User("a", "s", FEMALE, 19, 1),
new User("a", "s", MALE, 12, 22));
Map<Type, Integer> collect = users.stream()
.map(u -> Tuple.tuple(u, resolveType(u)))
.collect(Collectors.groupingBy(Tuple::right, Collectors.summingInt(t -> t.left().points)));
System.out.println(collect);
}
public static Type resolveType(final User user) {
if (user.gender == MALE && user.age > 18) {
return Type.ADULT_MALE;
} else if (user.gender == FEMALE && user.age > 18) {
return Type.ADULT_FEMALE;
} else {
return Type.MINOR;
}
}
public enum Type {
ADULT_MALE, ADULT_FEMALE, MINOR
}
I guess it's a balanced solution - quite efficient and readable.
I don't like if-else statements so you can replace it with Map like:
private static final Map<Predicate<User>, Type> predicates = ImmutableMap.of(
user -> user.getGender() == MALE && user.getAge() >= 18, Type.ADULT_MALE,
user -> user.getGender() == FEMALE && user.getAge() >= 18, Type.ADULT_FEMALE,
user -> user.getAge() < 18, Type.MINOR
);
public static Type resolveType(final User user) {
return predicates.entrySet().stream()
.filter(entry -> entry.getKey().test(user))
.findFirst()
.map(Map.Entry::getValue)
.orElseThrow(RuntimeException::new);
}
It prints:
{ADULT_MALE=33, MINOR=71, ADULT_FEMALE=1}
I guess you don't have to worry about performance unless you're dealing with huge collections.
// edit
Just to make it clear. My tuple implementation looks like that:
#ToString
#EqualsAndHashCode
public class Tuple<L, R> {
public static <L, R> Tuple<L, R> tuple(L left, R right) {
return new Tuple<>(left, right);
}
private final L left;
private final R right;
private Tuple(L left, R right) {
this.left = left;
this.right = right;
}
public L left() {
return left;
}
public R right() {
return right;
}
}

Related

#Requestbody with List of objects with numbers initialized to 0

In a #PostMapping call, when a list of objects is received via the #RequestBody. And this list contains Int or Double variables, if these variables are not sent in the request body json, the variables are self-initialized to 0.
Instead of this, I understand that it should return bad request
This problem does not happen with the BigDecimal for example and returns bad request with this variables, or if the body of the request is an object instead of a list.
Do you know how to solve this? is it a spring problem?
Example to reproduce the problem:
data class Animal(
val name: String,
val height: Double
)
#PostMapping("/animals")
suspend fun saveAnimals(
#RequestBody request: List<Animal>
): ResponseEntity<Any> {
println(request[0].height)
return ResponseEntity.ok().build()
}
On the example above the print result will be 0 if the height is not sent on the request, but I expected this to return a bad request.
In addition to my other answer, another concept:
Why not work validation via List: https://stackoverflow.com/a/35643761/2625393
Work with this:
implementation("org.springframework.boot:spring-boot-starter-validation:2.7.5")
data class ListAnimal(
#field:Valid
val list: List<Animal>
)
data class Animal(
val name: String,
#field:NotNull
val height: Double?
)
#RestController
class Controller {
#PostMapping("/animals")
suspend fun saveAnimals(#RequestBody #Valid request: ListAnimal): ResponseEntity<Any> {
println(request.list)
return ResponseEntity.ok().build()
}
}
POST http://localhost:8080/animals
Content-Type: application/json
{
"list": [
{
"name": "name"
}
]
}
Because Kotlin doesn't use primitive and wrapper object like Java. Example: int and Integer in Java. If kotlin can optimize, so it will do. So this Double will be double in built version. Ofc, if u call wrapper method, use any genetic (example List), or the attribute is nullable type in your code, Kotlin won't change to primitive double.
In summary: primitive double default value is 0.0
IDEA Java code example from data class Animal:
// Animal.java
package com.example.demo2;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
#Metadata(
mv = {1, 6, 0},
k = 1,
d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0006\n\u0002\b\t\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0002\b\u0086\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\t\u0010\u000b\u001a\u00020\u0003HÆ\u0003J\t\u0010\f\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\r\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u000e\u001a\u00020\u000f2\b\u0010\u0010\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0011\u001a\u00020\u0012HÖ\u0001J\t\u0010\u0013\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\u0014"},
d2 = {"Lcom/example/demo2/Animal;", "", "name", "", "height", "", "(Ljava/lang/String;D)V", "getHeight", "()D", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "", "toString", "demo2"}
)
public final class Animal {
#NotNull
private final String name;
private final double height;
#NotNull
public final String getName() {
return this.name;
}
public final double getHeight() {
return this.height;
}
public Animal(#NotNull String name, double height) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.height = height;
}
#NotNull
public final String component1() {
return this.name;
}
public final double component2() {
return this.height;
}
#NotNull
public final Animal copy(#NotNull String name, double height) {
Intrinsics.checkNotNullParameter(name, "name");
return new Animal(name, height);
}
// $FF: synthetic method
public static Animal copy$default(Animal var0, String var1, double var2, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.name;
}
if ((var4 & 2) != 0) {
var2 = var0.height;
}
return var0.copy(var1, var2);
}
#NotNull
public String toString() {
return "Animal(name=" + this.name + ", height=" + this.height + ")";
}
public int hashCode() {
String var10000 = this.name;
return (var10000 != null ? var10000.hashCode() : 0) * 31 + Double.hashCode(this.height);
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Animal) {
Animal var2 = (Animal)var1;
if (Intrinsics.areEqual(this.name, var2.name) && Double.compare(this.height, var2.height) == 0) {
return true;
}
}
return false;
} else {
return true;
}
}
}

Multiple sorting for file generated from beanshell scripting

I am generating the .txt file from beanshell scripting. Now I am having problem for sorting. If anyone can give any idea that will be great help. The file looks like:
UserId FirstName LastName Status roleId
2025 A B Active 3
2021 C D InActive 2
2036 E F Active 1
3012 G H Active 2
5012 I J InActive 1
Sorting should be done by while writing file by Status in ascending order, then by roleId in ascending order, then by UserId in ascending order.
Assuming
public class User {
private Integer userId;
private String firstName, lastName;
public enum Status{
ACTIVE, INACTIVE;
}
private Status status;
private Integer roleId;
// getter and setter
}
You can use a comparator like this:
public class UserComparator implements Comparator<User> {
#Override
public int compare(User o1, User o2) {
#Override
public int compare(User o1, User o2) {
int bystatus = o1.getStatus().compareTo(o2.getStatus());
if (bystatus != 0) {
return bystatus;
}
int byid = o1.getUserId().compareTo(o2.getUserId());
if (byid != 0) {
return byid;
}
return o1.getRoleId().compareTo(o2.getRoleId());
}
}
Finally:
Collections.sort(list, comparator);

Java 8 collectors groupBy and into a separate class

I have a list of Persons which have some duplicate names.
class Person {
String name;
}
I want to convert it to the list of GroupedPersons which contain the common name and the list of all Persons who have that name.
class GroupedPerson {
String name;
List<A> as;
}
Is it possible to do this with one collector and without any intermediate mapping or extra classes?
I suppose one way would be:
persons
.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(
Person::getName),
map -> {
return map.entrySet()
.stream()
.map(en -> new GroupedPerson(en.getKey(), en.getValue()))
.collect(Collectors.toList());
}));
Or, you could use toMap:
persons
.stream()
.collect(Collectors.toMap(
Person::getName,
x -> {
List<Person> l = new ArrayList<>();
l.add(x);
return new GroupedPerson(x.getName(), l);
},
(left, right) -> {
left.getAs().addAll(right.getAs());
return left;
}))
.values();
Yes, you can do that. Here is a way to do it:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class GroupedStream {
public static void main(String[] args) {
List<GroupedPerson> groupedPersons = Arrays.asList("john harry john harry sam jordon bill steve bill".split(" "))
.stream()
.map(name -> new Person(name))
.map(person -> new Object[] {person.name, new ArrayList<>()})
.collect(Collectors.toMap(tuple-> (String) tuple[0], tuple -> (List<Person>) tuple[1] ))
.entrySet()
.stream()
.map(entry -> new GroupedPerson(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
}
static class Person {
public final String name;
public Person(String name) {
super();
this.name = name;
}
}
static class GroupedPerson {
public final String name;
public final List<Person> person;
public GroupedPerson(String name, List<Person> person) {
this.name = name;
this.person = person;
}
}
}
if you modify your GroupedPerson model to be something along the lines of:
public class GroupedPerson {
private String name;
private List<Person> people;
public GroupedPerson(String name) {
this.name = name;
people = new ArrayList<>();
}
public void addPeople(List<Person> people) {
this.people.addAll(people);
}
public void addPerson(Person person){
people.add(person);
}
public List<Person> getPeople(){
return Collections.unmodifiableList(people);
}
public String getName() {
return name;
}
}
Then you can have a Collection of GroupedPerson objects with the names and all the corresponding people with that specific name like this:
Collection<GroupedPerson> resultSet = peopleList
.stream()
.collect(Collectors.toMap(Person::getName,
p -> {
GroupedPerson groupedPerson = new GroupedPerson(p.getName());
groupedPerson.addPerson(p);
return groupedPerson;
},
(p, p1) -> {
p.addPeople(p1.getPeople());
return p;
}
)).values();
if for some reason you don't want the receiver type to be Collection<T> then you can convert to a specific collection type if deemed appropriate by simply doing.
List<GroupedPerson> result = new ArrayList<>(resultSet);

problems with FindByLocationWithin

I'm trying to query a mongo repository to return data that is within a specified geo circle. I'm using the following code:
Page<Img> findByLocationWithin(Circle circle, Pageable pageable);
and then in my controller I'm using:
Distance distance = new Distance(7.5, Metrics.MILES);
Circle circle = new Circle(location, distance);
Page<Img> results = imgRepository.findByLocationWithin(circle, pageable);
However it definitely doesn't use a radius of 7.5 miles as if I create the circle a few hundred metres away from where the data is located, it returns nothing. I've checked the logs in mongo and it says that the following code is being performed:
"location" : {
"$within" : {
"$center" : [
[
30.198,
-1.695
],
0.0018924144710663706
]
}
}
This means it's not using $geoWithin or $centerSphere. How can I fix these problems?
I had the same problem with spring and Couchbase... but the query is not the problem... Because Spring convert the distance in the geometric values.
In my case I also returned null, but my problem was solved, in the model class, the attribute that specifies the coordinate [x, y] must be of type Library Point org.springframework.data.geo.Point;
package com.webServices.rutas.model;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
import org.springframework.data.geo.Point;
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
public class Parada {
#Id #GeneratedValue(strategy = GenerationStrategy.UNIQUE)
private String id;
#Field
private String type;
#Field
private String nombre;
#Field
private String urlFoto;
#Field
private Point coordenada;
public Parada(String nombre, String urlFoto, Point coordenada) {
super();
this.type = "parada";
this.nombre = nombre;
this.urlFoto = urlFoto;
this.coordenada = coordenada;
}
public Parada() {
super();
this.type = "parada";
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getUrlFoto() {
return urlFoto;
}
public void setUrlFoto(String urlFoto) {
this.urlFoto = urlFoto;
}
public Point getCoordenada() {
return coordenada;
}
public void setCoordenada(Point coordenada) {
this.coordenada = coordenada;
}
#Override
public String toString() {
return "Parada [id=" + id + ", type=" + type + ", nombre=" + nombre + ", urlFoto=" + urlFoto + ", coordenada="
+ coordenada + "]";
}
}
In the service:
public Iterable<Parada> getParadasCercanasRadio(Punto punto){
Point cuadro = new Point(-2.2,-80.9);
Circle circle = new Circle(cuadro,new Distance(300000, Metrics.KILOMETERS));
return paradaRepository.findByCoordenadaWithin(circle);
}
In the Repository:
#Dimensional(designDocument = "paradas", spatialViewName = "paradas")
Iterable<Parada> findByCoordenadaWithin(Circle p);
P.D. Sorry for my English.

Spring Data Neo4j - findAll() doesn't return sorted results

I'm trying to use findAll() of the GraphRepository using PageRequest with sorting properties, but for some reson my data is not returned in a sorted way. Here is my code:
public class Playground {
/**
* #param args
*/
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DefaultApplicationConfig.class);
SocialRescueManager manager = (SocialRescueManager)context.getBean("socialRescueManager");
manager.Civilians.deleteAll();
manager.Civilians.save(new Civilian("B", "lastName1", Nationality.USA, EyeColor.BLUE, HairColor.BLOND, 180, new DateTime(1984, 1 , 9, 0, 0)));
manager.Civilians.save(new Civilian("C", "lastName2", Nationality.USA, EyeColor.GREEN, HairColor.BLACK, 170, new DateTime(1985, 6 , 9, 0, 0)));
manager.Civilians.save(new Civilian("A", "lastName3", Nationality.USA, EyeColor.BLUE, HairColor.BLOND, 175, new DateTime(1990, 10 , 23, 0, 0)));
Pageable pageable = new PageRequest(0, 5, Direction.DESC, "firstName");
Page<Civilian> results = manager.Civilians.findAll(pageable);
for(Iterator<Civilian> i = results.iterator(); i.hasNext(); ) {
Civilian civilian = i.next();
System.out.println(civilian.getFirstName());
System.out.println(civilian.getDateOfBirth());
}
context.close();
}
}
My Civilian class looks like this:
#NodeEntity
public class Civilian extends Profile {
#GraphProperty(propertyType = Long.class)
DateTime dateOfBirth;
public Civilian() {
}
public Civilian(String firstName, String lastName, Nationality nationality, EyeColor eyeColor, HairColor hairColor, int height, DateTime dateOfBirth) {
super(firstName, lastName, nationality, eyeColor, hairColor, height);
this.setDateOfBirth(dateOfBirth);
}
public DateTime getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(DateTime dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
}
The Civilian class extends the Profile class, which looks like this:
public abstract class Profile extends AbstractEntity {
#Indexed(indexType = IndexType.FULLTEXT, indexName = "firstName")
String firstName;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "lastName")
String lastName;
#Indexed
EyeColor eyeColor;
#Indexed
HairColor hairColor;
#Indexed
Nationality nationality;
#Indexed
int height;
public Profile() {
}
public Profile(String firstName, String lastName, Nationality nationality, EyeColor eyeColor, HairColor hairColor, int height) {
this.setFirstName(firstName);
this.setLastName(lastName);
this.setNationality(nationality);
this.setHairColor(hairColor);
this.setEyeColor(eyeColor);
this.setHeight(height);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public EyeColor getEyeColor() {
return eyeColor;
}
public void setEyeColor(EyeColor eyeColor) {
this.eyeColor = eyeColor;
}
public HairColor getHairColor() {
return hairColor;
}
public void setHairColor(HairColor hairColor) {
this.hairColor = hairColor;
}
public Nationality getNationality() {
return nationality;
}
public void setNationality(Nationality nationality) {
this.nationality = nationality;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
So, the results of my Main() method are as follows:
B
1984-01-09T00:00:00.000+02:00
C
1985-06-09T00:00:00.000+03:00
A
1990-10-23T00:00:00.000+02:00
But as you can see, i set my PageRequest object to sort the results by the "firstName" property, in DESC order, so i would expect to get C printed first, then B, and last A.
What am i doing wrong?
Thanks.
The javadoc of CRUDRepository.findAll(Pageable) states:
NOTE: the sorting is not yet implemented
You'll have to contribute a fix, or you might as well consider using a cypher query with sorting enabled. E.g.
START n=node:firstName('firstName:abc')
RETURN n
ORDER BY n.firstName DESC

Resources