I have the following TicketDTO Object:
public class TicketDTO {
private LocalDate date;
private Set<OffenceDTO> offences;
}
And every OffenceDTO has an int field - penalty points.
public class OffenceDTO {
private int penaltyPoints;
}
I would like to add up the penalty points to a single int value by streaming the Set of Offenses of each Ticket. But only if the ticket's date is between the last two years.
I have collected tickets from the last two years, but now I have a problem in how to go through the offenses and count their points.
This is what I've written so far:
tickets().stream()
.filter(ticketEntity -> isDateBetween(LocalDate.now(), ticketEntity.getDate()))
.collect(Collectors.toList());
I would like to collect the penalty points in a single int value by streaming the set of tickets
It can be done in the following steps:
Turn the stream of filtered tickets into a stream of OffenceDTO using flatMap();
Extract penalty points from OffenceDTO with mapToInt(), that will transform a stream of objects into a IntStream;
Apply sum() to get the total.
int totalPenalty = tickets().stream()
.filter(ticketEntity -> isDateBetween(LocalDate.now(), ticketEntity.getDate()))
.flatMap(ticketDTO -> ticketDTO.getOffences().stream())
.mapToInt(OffenceDTO::getPenaltyPoints)
.sum();
Assuming that tickets() is a method that returns a List of TicketDTO, you could stream the List and filter its elements with your custom method isDateBetween (as you were doing).
Then flat the mapping of each ticket to their corresponding offences. This will provide you a stream of OffenceDTO whose TicketDTO is between the last two years (according to your isDateBetween method).
Ultimately, you can collect the points of each OffenceDTO by summing them with the summingInt method of the Collectors class.
int res = tickets().stream()
.filter(ticketEntity -> isDateBetween(LocalDate.now(), ticketEntity.getDate()))
.flatMap(ticketDTO -> ticketDTO.getOffences().stream())
.collect(Collectors.summingInt(OffenceDTO::getPenaltyPoints));
Related
I searched the site and didn't find something similar. I'm newbie to using the Java stream, but I understand that it's a replacement for a loop command. However, I would like to know if there is a way to filter a CSV file using stream, as shown below, where only the repeated records are included in the result and grouped by the Center field.
Initial CSV file
Final result
In addition, the same pair cannot appear in the final result inversely, as shown in the table below:
This shouldn't happen
Is there a way to do it using stream and grouping at the same time, since theoretically, two loops would be needed to perform the task?
Thanks in advance.
You can do it in one pass as a stream with O(n) efficiency:
class PersonKey {
// have a field for every column that is used to detect duplicates
String center, name, mother, birthdate;
public PersonKey(String line) {
// implement String constructor
}
// implement equals and hashCode using all fields
}
List<String> lines; // the input
Set<PersonKey> seen = new HashSet<>();
List<String> unique = lines.stream()
.filter(p -> !seen.add(new PersonKey(p))
.distinct()
.collect(toList());
The trick here is that a HashSet has constant time operations and its add() method returns false if the value being added is already in the set, true otherwise.
What I understood from your examples is you consider an entry as duplicate if all the attributes have same value except the ID. You can use anymatch for this:
list.stream().filter(x ->
list.stream().anyMatch(y -> isDuplicate(x, y))).collect(Collectors.toList())
So what does the isDuplicate(x,y) do?
This returns a boolean. You can check whether all the entries have same value except the id in this method:
private boolean isDuplicate(CsvEntry x, CsvEntry y) {
return !x.getId().equals(y.getId())
&& x.getName().equals(y.getName())
&& x.getMother().equals(y.getMother())
&& x.getBirth().equals(y.getBirth());
}
I've assumed you've taken all the entries as String. Change the checks according to the type. This will give you the duplicate entries with their corresponding ID
I am new to Java 8. I have a list of custom objects of type A, where A is like below:
class A {
int id;
String name;
}
I would like to determine if all the objects in that list have same name. I can do it by iterating over the list and capturing previous and current value of names. In that context, I found How to count number of custom objects in list which have same value for one of its attribute. But is there any better way to do the same in java 8 using stream?
You can map from A --> String , apply the distinct intermediate operation, utilise limit(2) to enable optimisation where possible and then check if count is less than or equal to 1 in which case all objects have the same name and if not then they do not all have the same name.
boolean result = myList.stream()
.map(A::getName)
.distinct()
.limit(2)
.count() <= 1;
With the example shown above, we leverage the limit(2) operation so that we stop as soon as we find two distinct object names.
One way is to get the name of the first list and call allMatch and check against that.
String firstName = yourListOfAs.get(0).name;
boolean allSameName = yourListOfAs.stream().allMatch(x -> x.name.equals(firstName));
another way is to calculate count of distinct names using
boolean result = myList.stream().map(A::getName).distinct().count() == 1;
of course you need to add getter for 'name' field
One more option by using Partitioning. Partitioning is a special kind of grouping, in which the resultant map contains at most two different groups – one for true and one for false.
by this, You can get number of matching and not matching
String firstName = yourListOfAs.get(0).name;
Map<Boolean, List<Employee>> partitioned = employees.stream().collect(partitioningBy(e -> e.name==firstName));
Java 9 using takeWhile takewhile will take all the values until the predicate returns false. this is similar to break statement in while loop
String firstName = yourListOfAs.get(0).name;
List<Employee> filterList = employees.stream()
.takeWhile(e->firstName.equals(e.name)).collect(Collectors.toList());
if(filterList.size()==list.size())
{
//all objects have same values
}
Or use groupingBy then check entrySet size.
boolean b = list.stream()
.collect(Collectors.groupingBy(A::getName,
Collectors.toList())).entrySet().size() == 1;
I was wondering is there is a way to use stream to get a single T object from a List<T> with averages for double values and min for date. I have like 20 properties and maybe there is also a way to go through all of them automatically and with an if the property is a double to get an average, if is a date to get a min?
id date value
3470, 2018-11-15 08:10:00+02, 25,101610.0234375
3467, 2018-11-15 07:53:00+02, 33,101398.984375
3468, 2018-11-15 07:54:00+02, 25,101599.765625
3549, 2018-12-28 18:20:00+02, 29.21
3550, 2018-12-28 18:24:00+02, 29.21
3551, 2018-12-28 18:27:00+02, 42.21
3552, 2019-01-07 09:42:00+02,
3553, 2019-01-07 09:50:00+02, 15.140000343323
3554, 2019-01-07 09:52:00+02, -1.3799999952316
3555, 2019-01-07 10:03:00+02, 14.949999809265
From your description the only thing that pops to my head is reflection.
You can also define a downstream function:
BinaryOperator<T> downstream = (t1, t2) -> new T(t1.getValue() + t2.getValue(), ..., t1.getDate().isBefore(t2.getDate()) ? t1.getDate() : t2.getDate());
And then reduce:
T aggregate = list.stream().reduce(downstream).get();
T result = new T(aggregate.getValue() / list.size(), aggregate.getDate(), ...)
P.S.1: Note, that this has quite an overhead as it creates a new object in BinaryOperator, so this might not be the best solution when list is large.
P.S.2: This is a draft, I assumed you're using LocalDateTime to store date field, this is dependent on what you're using.
P.S.3: You need to define an aggregation method for your fields, for example id.
P.S.4: As #Aaron pointed out - I'm calculating the sum for value field, so that I can then divide it by the length of result, this way the average is correctly calculated.
take a look at teeing collector which was introduced in Java 12. In worst case you can copy/paste the implementation (as was done here)
Result result = stream.collect(Collectors.teeing(
Collectors.mapping(T::getDate, Collectors.minBy(Comparator.naturalOrder())),
Collectors.averagingDouble(T::getValue),
Result::new
));
where result is a class defined by you
class Result {
Optional<LocalDate> minDate;
double avg;
//all args constructor
}
I think the most efficient and clean solution is to use the Stream::reduce method and define a customer reduce function that performs min for specific fields and average for other fields depending on what you need.
list.stream().reduce(initialValue, (accumulator, listElement) -> reduceFunction(accumulator, listElement))
There's a slight difficulty here in that you need to know the length of the list to compute the average. So in order for the reduce function to be generic, the accumulator class has to have an element counter.
In my program I have a List of Plants, each plant has a measurement (String), day (int), camera (int), and replicate number(int). I obtain a List of all plants wanted by using filters:
List<Plant> selectPlants = allPlants.stream().filter(plant -> passesFilters(plant, filters)).collect(Collectors.toList());
What I would like to do now is take all Plants that have the same camera, measurement, and replicate values. And combine them in order of day. So if I have days 1,2,3,5 I would want to find all similar plants and append the values to one plant where the getValues (function).
I added a method to Plant that appends values by just using addAll( new plant values ).
Is there any way of doing this without iterating through the list over and over to find the similar plants, and then sorting each time by day then appending? I'm sorry for the horrible wording of this question.
While Vakh’s answer is correct, it is unnecessarily complex.
Often, the work of implementing your own key class does not pay off. You can use a List as a key which implies a slight overhead due to boxing primitive values but given the fact that we do operations like hashing here, it will be negligible.
And sorting doesn’t have to be done by using a for loop and, even worse, an anonymous inner class for the Comparator. Why do we have streams and lambdas? You can implement the Comparator using a lambda like (p1,p2) -> p1.getDay()-p2.getDay() or, even better, Comparator.comparing(Plant::getDay).
Further, you can do the entire operation in one step. The sort step will create an ordered stream and the collector will maintain the encounter order, so you can use one stream to sort and group:
Map<List<?>, List<Plant>> groupedPlants = allPlants.stream()
.filter(plant -> passesFilters(plant, filters))
.sorted(Comparator.comparing(Plant::getDay))
.collect(Collectors.groupingBy(p ->
Arrays.asList(p.getMeasurement(), p.getCamera(), p.getReplicateNumber())));
That’s all.
Using Collectors.groupBy:
private static class PlantKey {
private String measurement;
private int camera;
private int replicateNumber;
// + constructor, getters, setters and haschode equals
}
Map<PlantKey, List<Plant>> groupedPlants =
allPlants.stream().filter(plant -> passesFilters(plant, filters))
.collect(Collectors.groupBy(p ->
new PlantKey(p.getMeasurement(),
p.getCamera(),
p.getReplicateNumber())));
// order the list
for(List values : groupedPlants.values()) {
Collections.sort(values, new Comparator<Plant>(){
#Override
public int compare(Plant p1, Plant p2) {
return p1.getDay() - p2.getDay();
}
});
}
I would group them by the common characteristics and compare similar results.
for(List<Plant> plantGroup : allPlants.stream().collect(Collectors.groupingBy(
p -> p.camera+'/'+p.measurement+'/'+p.replicate)).values()) {
// compare the plants in the same group
}
There is a function called sorted which operates on a stream
selectPlants.stream().sorted(Comparator.comparingInt(i -> i.day)).collect(Collectors.toList());
I have the following class :
class Students{
int age;
int dept;
}
Lets say i have a List<Students> and I want to manipulate the list by doing simple calculations like : calculate the mean, calculate the middle value (e.g. (age+debt)/2), find the closest value to the mean and so on. How can I do this in a structured way?. I want to be in a position where I can use different combinations on the list. e.g. calculate mean of age // calculate mean of the middle value from age/debt, find the closest value of the age etc.
How should i approach this?. Would appreciate it if someone could point me in the right direction.
Apache Math has a nice Descriptive Statistics package that does this sought of thing.
http://commons.apache.org/proper/commons-math/userguide/stat.html#a1.2_Descriptive_statistics
If you're using Java 8 this works well with Lambdas:
DescriptiveStatistics stats = new DescriptiveStatistics();
students.forEach(s -> stats.add(s.age));
double mean = stats.getMean();
And to filter etc:
//Only students with an age > 18
students.stream.filter(s -> s.age > 18).forEach(s -> stats.add(s.age));
If you're not using Java 8 then simply foreach it.
You can create a separate class (StudentCalculator) that will require a List of Students (perhaps pass the List in the constructor) and have the instance methods perform calculations on the List.
Or you can create a utility (e.g. StudentCalculatorUtility) where you would define a series of methods that would accept a List of Students as a parameter, that would perform all the calculations you would need on the students(middle value,closest to mean, etc.)
There is a concept where you step through a list and perform an operation on each item in turn which may or may not change the item.
In this case, you want a method that takes an item from the list does some stuff and returns a running total.
int sumItems(Student stu, int sum){
return (stu.age + stu.debt)/2;
}
To use this method, either use either a forEach or an iterator.
Iterator itr = Students.iterator(); // assuming List<Student> Students = new List<Student>()
int sum = 0;
while(itr.hasnext()){
sum = sumItems(itr.next(), sum)
}
Now do something with your sum.