I have a map of the format
Map<String, List<TableDTO>>
public class TableDTO {
private String countryName;
private String sourceName;
private int year;
private Double usageValue;
private Double powerUsers;
//Setter & Getters
}
I want to find the average of usageValues and powerUsers and still maintain the TableDTO structure and the usageValue can be null, if it null ignore that object completely.
<Chrome, <UK, Lorem, 2013, 2.90, 5.4>>
<Chrome, <US, Lorem, 2013, 4.10, 1.5>>
<Chrome, <EU, Lorem, 2013, 1.20, 0.22>>
<Chrome, <Asia, Lorem, 2013, 3.90, -1.10>>
<IE, <UK, Lorem, 2013, 1.40, 24.4>>
<IE, <US, Lorem, 2013, 0.90, 14.4>>
<IE, <EU, Lorem, 2013, 2.10, 0>>
<IE, <Asia, Lorem, 2013, 0.90, 0.4>>
<FF, <UK, Lorem, 2013, 0.10, 2.14>>
<FF, <US, Lorem, 2013, 1.10, 4.0>>
<FF, <EU, Lorem, 2013, , 4.4>>
<FF, <Asia, Lorem, 2013, 2.90, 4.4>>
Result expected
<1, <UK, Lorem, 2013, 1.47, 10.65>>
<2, <US, Lorem, 2013, 2.03, 6.63>>
<3, <Asia, Lorem, 2013, 2.57, 1.23>>
For now in the results I have replaced the keys with index, which is fine for now. You will notice that since FF for EU has a null value the entire EU has been ignored, but for the rest I have the average calculated.
How can this be done using Lambda expressions in Java 8, or do I have to iterate through?
Update 1: This is as far as I got for now:
1.
Map<String, List<TableDTO>> dump = mapOfAllData.values()
.stream()
.flatMap(list -> list.stream())
.collect(Collectors.groupingBy(TableDTO::getCountryName));
Which give me a map with country names and the DTO orderd
2.
dump.values().stream().flatMap(list -> list.stream())
.filter((o -> !o.getUsageValue().isEmpty()))
.collect(Collectors.mapping(TableDTO::getUsageValue, Collectors.averagingDouble(Double::parseDouble)));
Basically gets the average, but does not remove the DTO in which the usageValue is empty, which I am trying at the moment to resolve.
Update 2:
I managed to remove the unwanted countries from my map.
I am trying to figure out how to find the average of two elements, I have this expression
newMap.values().stream().flatMap(list -> list.stream())
.collect(Collectors.mapping(TableDTO::usageValue, Collectors.averagingDouble(s -> s.isEmpty() ? Double.NaN : Double.parseDouble(s))));
// Collectors.mapping(TableDTO::powerUsers, Collectors.averagingDouble(c -> c.isEmpty() ? Double.NaN : Double.parseDouble(c))));
but am unable to get the average for powerUsers.
To understand, you want an average over each List<TableDTO>
with a groupBy
countryName
, sourceName
, year
but average are on distinct field ?
I will expect usagePower
and powerUsers
to be Double
, and not String like your code and your use of Double.parseDouble
suggests.
This code should do it:
package stackoverflow;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
public class TableDTO {
private final String countryName;
private final String sourceName;
private final int year;
@Nullable
private final Double usageValue;
private final Double powerUsers;
public TableDTO(final String countryName, final String sourceName, final int year, final Double usageValue,
final Double powerUsers) {
this.countryName = countryName;
this.sourceName = sourceName;
this.year = year;
this.usageValue = usageValue;
this.powerUsers = powerUsers;
}
public String getCountryName() {return countryName;}
public String getSourceName() {return sourceName;}
public int getYear() {return year;}
@Nullable public Double getUsageValue() {return usageValue;}
public Double getPowerUsers() {return powerUsers;}
@Override
public String toString() {
return "TableDTO [countryName=" + countryName + ", sourceName=" + sourceName + ", year=" + year + ", usageValue="
+ usageValue + ", powerUsers=" + powerUsers + "]";
}
public static void main(final String[] args) {
final java.util.Map<String, java.util.List<TableDTO>> data = new LinkedHashMap<>();
final List<TableDTO> chrome = new ArrayList<>();
chrome.add(new TableDTO("UK", "Lorem", 2013, 2.90, 5.4));
chrome.add(new TableDTO("US", "Lorem", 2013, 4.10, 1.5));
chrome.add(new TableDTO("EU", "Lorem", 2013, 1.20, 0.22));
chrome.add(new TableDTO("Asia", "Lorem", 2013, 3.90, -1.10));
data.put("Chrome", chrome);
final List<TableDTO> ie = new ArrayList<>();
ie.add(new TableDTO("UK", "Lorem", 2013, 1.40, 24.4));
ie.add(new TableDTO("US", "Lorem", 2013, 0.90, 14.4));
ie.add(new TableDTO("EU", "Lorem", 2013, 2.10, 0.));
ie.add(new TableDTO("Asia", "Lorem", 2013, 0.90, 0.4));
data.put("IE", ie);
final List<TableDTO> fx = new ArrayList<>();
fx.add(new TableDTO("UK", "Lorem", 2013, 0.10, 2.14));
fx.add(new TableDTO("US", "Lorem", 2013, 1.10, 4.0));
fx.add(new TableDTO("EU", "Lorem", 2013, null, 4.4));
fx.add(new TableDTO("Asia", "Lorem", 2013, 2.90, 4.4));
data.put("FX", fx);
data.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(dto -> Arrays.asList(dto.getCountryName(), dto.getSourceName(), dto.getYear())))
.values()
.stream()
.filter(list -> list.stream().map(TableDTO::getUsageValue).noneMatch(Objects::isNull))
.map(
values -> {
final TableDTO root = values.iterator().next();
final double usageValueAvg = values.stream().map(TableDTO::getUsageValue).filter(Objects::nonNull)
.collect(Collectors.averagingDouble(Double::doubleValue));
final double powerUsersAvg = values.stream().map(TableDTO::getPowerUsers)
.collect(Collectors.averagingDouble(Double::doubleValue));
return new TableDTO(root.getCountryName(), root.getSourceName(), root.getYear(), usageValueAvg,
powerUsersAvg);
}).forEach(System.out::println);
;
}
}
The result is:
TableDTO [countryName=UK, sourceName=Lorem, year=2013, usageValue=1.4666666666666666, powerUsers=10.646666666666667]
TableDTO [countryName=US, sourceName=Lorem, year=2013, usageValue=2.033333333333333, powerUsers=6.633333333333333]
TableDTO [countryName=Asia, sourceName=Lorem, year=2013, usageValue=2.5666666666666664, powerUsers=1.2333333333333334]
And the explanation: I've taken some of your code to do it.
Do a flatMap
over the values of data
:
data.values() .stream() .flatMap(List::stream)
Group your TableDTO
by some keys: we don't care about the key, the only important thing is that it correctly implements hashCode
and equals
. Arrays.asList
does the job. Otherwise, create a class Tuple which take an array and use Arrays.hashCode
/ equals
.
.collect(Collectors.groupingBy(dto -> Arrays.asList(dto.getCountryName(), dto.getSourceName(), dto.getYear()))) .values() .stream()
Since we don't want the list, we select the values and use a stream.
We filter TableDTO
which contains an empty usageValue
:
.filter(list -> list.stream().map(TableDTO::getUsageValue).noneMatch(Objects::isNull))
Then we do a map, and that where you were failing at finding a solution: because of the group, all TableDTO
share the same countryName
, sourceName
and year
value. But not the usageValue
and powerUsers
.
Because the list can't be empty, we get the first element.
.map( values -> { final TableDTO root = values.iterator().next();
On the other result, we compute the two averages filtering any null values for usageValue
.
final double usageValueAvg = values.stream().map(TableDTO::getUsageValue).filter(Objects::nonNull) .collect(Collectors.averagingDouble(Double::doubleValue)); final double powerUsersAvg = values.stream().map(TableDTO::getPowerUsers) .collect(Collectors.averagingDouble(Double::doubleValue));
Then we return a new TableDTO
based on the three grouping key, and the two averages.
return new TableDTO(root.getCountryName(), root.getSourceName(), root.getYear(), usageValueAvg, powerUsersAvg); })
And we print it, and voilà! :)
.forEach(System.out::println);
I hope it resolve your question.
I tested it in Eclipse, it compile, but it may fails with javac since the compiler does not the same work with Lambdas.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.