Transform a map from daily data to weekly data in Java - java

I have a map of LocalDate and Integer with daily data. Now I want to create a new map with the weekly data, which means new map will contain the cumulative count when we sum up the integers which falls under previous entry and current entry. I am stuck in this. Can anyone please help me in designing an algorithm for this. I am new to Java Stream api, if it is doable using Stream API it will
Example data:
In the image I have tried traversing the weeklyMap and then inside that traversed the dailyMap. But I am not sure how to make it possible in code(Java).
EDIT
Code snippet:
Map.Entry<LocalDate, Integer> prevEntry = null;
boolean firstTime = true;
for (Map.Entry<LocalDate, Integer> currEntry : weeklyMap.entrySet()) {
if (firstTime) {
prevEntry = currEntry;
firstTime = false;
if (weeklyMap.containsKey(currEntry.getKey())) {
weeklyMap.put(currEntry.getKey(), currEntry.getValue());
}
} else {
for (Map.Entry<LocalDate, Integer> todayEntry : dailyMap.entrySet()) {
if (prevEntry.getKey().equals(todayEntry.getKey())) {
prevEntry.setValue(todayEntry.getValue());
} else if(todayEntry.getKey().isAfter(prevEntry.getKey()) && todayEntry.getKey().isBefore(currEntry.getKey())) {
currEntry.setValue(currEntry.getValue() + todayEntry.getValue());
}
}
}
}

It seems easiest to first build a daily map of cumulative sums, then filter out only the mondays:
public static Map<LocalDate, Integer> cumulativeWeeklySum(SortedMap<LocalDate, Integer> data) {
AtomicInteger cumulativeSum = new AtomicInteger(0);
return data.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> cumulativeSum.addAndGet(e.getValue())))
.entrySet().stream()
.filter(e -> e.getKey().getDayOfWeek() == DayOfWeek.MONDAY || e.getKey().equals(data.lastKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
edit:
If you want to retain the order of the resulting map, you can modify the last collect() call:
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(v1, v2) -> { throw new RuntimeException("Duplicate key - can't happen"); },
TreeMap::new));

I think you should include the edge case which verifies the currentEntry.key == todayEntry.key and then sum up the previous values.
The code may look somewhat like this:
if(todayEntry.getKey().equals(currEntry.getKey())) {
currEntry.setValue(currEntry.getValue() + todayEntry.getValue() + prevEntry.getValue());
}

Related

Java streams average

I need to create two methods using streams. A method that returns an average score of each task.
public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {}
and a method that returns a task with the highest average score.
public String easiestTask(Stream<CourseResult> results) {}
I can only modify those 2 methods.
Here is CourseResult class
public class CourseResult {
private final Person person;
private final Map<String, Integer> taskResults;
public CourseResult(final Person person, final Map<String, Integer> taskResults) {
this.person = person;
this.taskResults = taskResults;
}
public Person getPerson() {
return person;
}
public Map<String, Integer> getTaskResults() {
return taskResults;
}
}
And methods that create CourseResult objects.
private final String[] programTasks = {"Lab 1. Figures", "Lab 2. War and Peace", "Lab 3. File Tree"};
private final String[] practicalHistoryTasks = {"Shieldwalling", "Phalanxing", "Wedging", "Tercioing"};
private Stream<CourseResult> programmingResults(final Random random) {
int n = random.nextInt(names.length);
int l = random.nextInt(lastNames.length);
return IntStream.iterate(0, i -> i + 1)
.limit(3)
.mapToObj(i -> new Person(
names[(n + i) % names.length],
lastNames[(l + i) % lastNames.length],
18 + random.nextInt(20)))
.map(p -> new CourseResult(p, Arrays.stream(programTasks).collect(toMap(
task -> task,
task -> random.nextInt(51) + 50))));
}
private Stream<CourseResult> historyResults(final Random random) {
int n = random.nextInt(names.length);
int l = random.nextInt(lastNames.length);
AtomicInteger t = new AtomicInteger(practicalHistoryTasks.length);
return IntStream.iterate(0, i -> i + 1)
.limit(3)
.mapToObj(i -> new Person(
names[(n + i) % names.length],
lastNames[(l + i) % lastNames.length],
18 + random.nextInt(20)))
.map(p -> new CourseResult(p,
IntStream.iterate(t.getAndIncrement(), i -> t.getAndIncrement())
.map(i -> i % practicalHistoryTasks.length)
.mapToObj(i -> practicalHistoryTasks[i])
.limit(3)
.collect(toMap(
task -> task,
task -> random.nextInt(51) + 50))));
}
Based on these methods I can calculate an average of each task by dividing sum of scores of this task by 3, because there are only 3 Persons tho I can make it so it divides by a number equal to number of CourseResult objects in a stream if these methods get their .limit(3) changed.
I don't know how to access keys of taskResults Map. I think I need them to then return a map of unique keys. A value for each unique key should be an average of values from taskResults map assigend to those keys.
For your first question: map each CourseResult to taskResults, flatmap to get all entries of each taskResults map form all CourseResults, group by map keys (task names) and collect averaging the values for same keys:
public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.averagingInt(Map.Entry::getValue)));
}
You can use the same approach for your second question to calculate the average for each task and finaly stream over the entries of the resulting map to find the task with the highest average.
public String easiestTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.averagingInt(Map.Entry::getValue)))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("No easy task found");
}
To avoid code duplication you can call the first method within the second:
public String easiestTask(Stream<CourseResult> results) {
return averageScoresPerTask(results).entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("No easy task found");
}
EDIT
To customize the calculation of the average regardless how many items your maps contain, don't use the inbuilt operations like Collectors.averagingInt or Collectors.averagingDouble. Instead wrap your collector in collectingAndThen and sum the scores using Collectors.summingInt and finally after collecting divide using a divisor according if the task name starts with Lab or not:
public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(Map.Entry::getValue)),
map -> map.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getKey().startsWith("Lab") ? e.getValue() / 3. : e.getValue() / 4.))
));
}
To create a map containing an average score for each task, you need to flatten the map taskResults of every CourseResult result object in the stream and group the data by key (i.e. by task name).
For that you can use collector groupingBy(), as its downstream collector that would be responsible for calculation the average from the score-values mapped to the same task you can use averagingDouble().
That's how it might look like:
public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {
return results
.map(CourseResult::getTaskResults) // Stream<Map<String, Integer>> - stream of maps
.flatMap(map -> map.entrySet().stream()) // Stream<Map.Entry<String, Integer>> - stream of entries
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.averagingDouble(Map.Entry::getValue)
));
}
To find the easiest task, you can use this map instead of passing the stream as an argument because the logic of this method requires applying the same operations. It would make sense in the real life scenario when you're retrieving the data that is stored somewhere (it would be better to avoid double-processing it) and more over in your case you can't generate a stream from the source twice and pass into these two methods because in your case stream data is random. Passing the same stream into both method is not an option because you can execute a stream pipeline only once, when it hits the terminal operation - it's done, you can't use it anymore, hence you can't pass the same stream with random data in these two methods.
public String easiestTask(Map<String, Double> averageByTask) {
return averageByTask.entrySet().stream()
.max(Map.Entry.comparingByValue()) // produces result of type Optianal<Map.Entry<String, Double>>
.map(Map.Entry::getKey) // transforming into Optianal<String>
.orElse("no data"); // or orElseThrow() if data is always expected to be present depending on your needs
}

How to filter based on list returned by map param using Java 8 streams

I'm trying to use Java stream to filter some values based on certain conditions. I am able to achieve the same using traditional for loops and a little bit of streams, but I want to rewrite the same logic fully in streams.
Original code:
public List <String> getProductNames(Hub hub, String requestedGroup) {
List <SupportedProduct> configuredProducts = repo.getSupportedProducts(hub);
List <String> productNames = new ArrayList <> ();
for (SupportedProduct supportedProduct: configuredProducts) {
List < String > categoryNameList = new ArrayList <> ();
String activeCategoryName = supportedProduct.getCategoryDetails().getActiveCategoryName();
if (activeCategoryName == null) {
Optional.ofNullable(supportedProduct.getCategoryDetails().getCategories())
.orElse(Collections.emptyList())
.forEach(category - > categoryNameList.add(category.getName()));
} else {
categoryNameList.add(activeCategoryName);
}
for (String catName: categoryNameList) {
Division division = divisionRepo.getDivisionByCatName(catName);
if (division != null && division.getGroup() == requestedGroup) {
productNames.add(supportedProduct.getProductName());
}
}
}
return productNames;
}
My try:
return Optional.ofNullable(configuredProducts).orElse(Collections.emptyList()).stream()
.map(supportedProduct -> {
List<String> categoryNameList = new ArrayList<>();
String activeCategoryName = supportedProduct.getCategoryDetails().getActiveCategoryName();
if (activeCategoryName == null) {
Optional.ofNullable(supportedProduct.getCategoryDetails().getCategories())
.orElse(Collections.emptyList())
.forEach(category -> categoryNameList.add(category.getName()));
} else {
categoryNameList.add(activeCategoryName);
}
return categoryNameList;
})
.filter(catName ->{
Division division = divisionRepo.getDivisionByCatName(catName);
return division != null && division.getGroup() == requestedGroup;
})........
But I'm lost beyond this.
Please help me to write the same using streams.
EDIT: Added IDEOne for testing - Link
The logic inside is quite complicated, however, try this out:
public List <String> getProductNames(Hub hub, String requestedGroup) {
List<SupportedProduct> configuredProducts = repo.getSupportedProducts(hub);
// extract pairs:
// key=SupportedProduct::getProductName
// values=List with one activeCategoryName OR names of all the categories
Map<String, List<String>> namedActiveCategoryNamesMap = configuredProducts.stream()
.collect(Collectors.toMap(
SupportedProduct::getProductName,
p -> Optional.ofNullable(p.getCategoryDetails().getActiveCategoryName())
.map(Collections::singletonList)
.orElse(Optional.ofNullable(p.getCategoryDetails().getCategories())
.stream()
.flatMap(Collection::stream)
.map(Category::getName)
.collect(Collectors.toList()))));
// look-up based on the categories' names, group equality comparison and returning a List
return namedActiveCategoryNamesMap.entrySet().stream()
.filter(entry -> entry.getValue().stream()
.map(catName -> divisionRepo.getDivisionByCatName(catName))
.filter(Objects::nonNull)
.map(Division::getGroup)
.anyMatch(requestedGroup::equals))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
I recommend splitting into separate methods for sake of readability (the best way to go).
The verbose logics of Optional chains including two orElse calls can be surely simplified, however, it gives you the idea.
You can perform within one Stream using Collectors.collectingAndThen. In that case, I'd extract the Function finisher elsewhere, example:
public List<String> getProductNames(Hub hub, String requestedGroup) {
return repo.getSupportedProducts(hub).stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
SupportedProduct::getProductName,
categoryNamesFunction()),
productNamesFunction(requestedGroup)));
}
private Function<Map<String, List<String>>, List<String>> productNamesFunction(String requestedGroup) {
return map -> map.entrySet().stream()
.filter(entry -> entry.getValue().stream()
.map(divisionRepo::getDivisionByCatName)
.filter(Objects::nonNull)
.map(Division::getGroup)
.anyMatch(requestedGroup::equals))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
private Function<SupportedProduct, List<String>> categoryNamesFunction() {
return p -> Optional.ofNullable(p.getCategoryDetails().getActiveCategoryName())
.map(Collections::singletonList)
.orElse(Optional.ofNullable(p.getCategoryDetails().getCategories())
.stream()
.flatMap(Collection::stream)
.map(Category::getName)
.collect(Collectors.toList()));
}

Java 8 Stream List<Foo> to Map<Date, Map<String,Long>> with conditional groupingBy

Following class:
public class Foo {
private Date date;
private String name;
private Long number;
}
I now have a List<Foo> which I want to convert to Map<Date, Map<String,Long>> (Long should be a sum of numbers). What makes this hard is that I want exactly 26 entries in the inner map, where the 26th is called "Others" which sums up everything that has a number lower than the other 25.
I came up with following code:
data.stream().collect(Collectors.groupingBy(e -> e.getDate(), Collectors.groupingBy(e -> {
if (/*get current size of inner map*/>= 25) {
return e.getName();
} else {
return "Other";
}
}, Collectors.summingLong(e -> e.getNumber()))));
As you can see, I have no idea how to check the number of elements which are already in the inner map. How can I get the current size of the inner map or is there another way to achieve what I want?
My Java 7 code:
Map<Date, Map<String, Long>> result = new LinkedHashMap<Date, Map<String, Long>>();
for (Foo fr : data) {
if (result.get(fr.getDate()) == null) {
result.put(fr.getDate(), new LinkedHashMap<String, Long>());
}
if (result.get(fr.getDate()) != null) {
if (result.get(fr.getDate()).size() >= 25) {
if (result.get(fr.getDate()).get("Other") == null) {
result.get(fr.getDate()).put("Other", 0l);
}
if (result.get(fr.getDate()).get("Other") != null) {
long numbers= result.get(fr.getDate()).get("Other");
result.get(fr.getDate()).replace("Other", numbers+ fr.getNumbers());
}
} else {
result.get(fr.getDate()).put(fr.getName(), fr.getNumbers());
}
}
}
Edit:
The map should help me to realize a table like this:
But I need to sum the "Others" first.
If you need any more infos feel free to ask
I don’t think that this operation will benefit from using the Stream API. Still, you can improve the operation with Java 8 features:
Map<Date, Map<String, Long>> result = new LinkedHashMap<>();
for(Foo fr : data) {
Map<String, Long> inner
= result.computeIfAbsent(fr.getDate(), date -> new LinkedHashMap<>());
inner.merge(inner.size()>=25?"Other":fr.getAirlineName(), fr.getNumbers(), Long::sum);
}
This code assumes that the airline names are already unique for each date. Otherwise, you would have to extend the code to
Map<Date, Map<String, Long>> result = new LinkedHashMap<>();
for(Foo fr : data) {
Map<String, Long> inner
= result.computeIfAbsent(fr.getDate(), date -> new LinkedHashMap<>());
inner.merge(inner.size() >= 25 && !inner.containsKey(fr.getAirlineName())?
"Other": fr.getAirlineName(), fr.getNumbers(), Long::sum);
}
to accumulate the values for the airline correctly.
For completeness, here is how to implement it as a stream operation.
Since the custom collector has some complexity, it’s worth writing it as reusable code:
public static <T,K,V> Collector<T,?,Map<K,V>> toMapWithLimit(
Function<? super T, ? extends K> key, Function<? super T, ? extends V> value,
int limit, K fallBack, BinaryOperator<V> merger) {
return Collector.of(LinkedHashMap::new, (map, t) ->
mergeWithLimit(map, key.apply(t), value.apply(t), limit, fallBack, merger),
(map1,map2) -> {
if(map1.isEmpty()) return map2;
if(map1.size()+map2.size() < limit)
map2.forEach((k,v) -> map1.merge(k, v, merger));
else
map2.forEach((k,v) ->
mergeWithLimit(map1, k, v, limit, fallBack, merger));
return map1;
});
}
private static <T,K,V> void mergeWithLimit(Map<K,V> map, K key, V value,
int limit, K fallBack, BinaryOperator<V> merger) {
map.merge(map.size() >= limit && !map.containsKey(key)? fallBack: key, value, merger);
}
This is like Collectors.toMap, but supporting a limit and a fallback key for additional entries. You may recognize the Map.merge call, similar to the loop solution as the crucial element.
Then, you may use the collector as
Map<Date, Map<String, Long>> result = data.stream().collect(
Collectors.groupingBy(Foo::getDate, LinkedHashMap::new,
toMapWithLimit(Foo::getAirlineName, Foo::getNumbers, 25, "Other", Long::sum)));
A bit too late :) But I come with this Java 8 solution without using the for loop or the custom collector. It is based on collectingAndThen which allows you to transform the result of collecting operation.
It allows me to divide the stream in finisher operation based on treshold.
However, I am not sure about the performance.
int treshold = 25
Map<Date, Map<String, Long>> result = data.stream().collect(groupingBy(Foo::getDate,
collectingAndThen(Collectors.toList(), x -> {
if (x.size() >= treshold) {
Map<String, Long> resultMap = new HashMap<>();
resultMap.putAll(x.subList(0, treshold).stream().collect(groupingBy(Foo::getName, Collectors.summingLong(Foo::getNumber))));
resultMap.putAll(x.subList(treshold, x.size()).stream().collect(groupingBy(y -> "Other", Collectors.summingLong(Foo::getNumber))));
return resultMap;
} else {
return x.stream().collect(groupingBy(Foo::getName, Collectors.summingLong(Foo::getNumber)));
}
})));
First of all, let's simplify the original problem by adapting it to java 8 without using Streams.
Map<Date, Map<String, Long>> result = new LinkedHashMap();
for (Foo fr : data) {
Map<String, Long> map = result.getOrDefault(fr.getDate(), new LinkedHashMap());
if (map.size() >= 25) {
Long value = map.getOrDefault("Other", 0L); // getOrDefault from 1.8
map.put("Other", value + 1);
} else {
map.put(fr.getName(), fr.getNumber());
}
result.put(fr.getDate(), map);
}
And now using Stream
int limit = 25;
Map<Date, Map<String, Long>> collect = data.stream()
.collect(Collectors.groupingBy(Foo::getDate))
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, v -> {
Map<String, Long> c = v.getValue().stream()
.limit(limit)
.collect(Collectors.toMap(Foo::getName, Foo::getNumber));
long remaining = v.getValue().size() - limit;
if (remaining > 0) {
c.put("Other", remaining);
}
return c;
}));

Set created from complex List

So I have a list containing duplicated entities from database with the same "Id" (it's not the real Id but kind of) but a different CreatedDate.
So I would like to have the latest entity from duplicates with the latest CreatedDate.
Example I have a list of created users :
RealId|CreatedDate|Id|Name
1|20170101|1|User1
2|20170102|1|User1Modified
3|20170103|2|User2
4|20170104|2|User2Modified
From that list what is the best way to obtain :
RealId|CreatedDate|Id|Name
2|20170102|1|User1Modified
4|20170104|2|User2Modified
This is my first idea
List<T> r = query.getResultList();
Set<T> distinct = r.stream().filter(x -> {
List<T> clones = r.stream()
.filter(y -> y.getId() == x.getId())
.collect(Collectors.toList());
T max = clones.stream()
.max(Comparator.comparing(AbstractEntityHistory::getEntryDate))
.get();
return max.getNumber() == x.getNumber();
}).collect(Collectors.toSet());
An other idea I have is to make it order descending by the date then do distinct().collect() like :
Set<T> distinct2 = r.stream().sorted((x,y) -> {
if(x.getEntryDate().isBefore(y.getEntryDate())) {
return 1;
} else if(x.getEntryDate().isAfter(y.getEntryDate())) {
return -1;
} else {
return 0;
}
}).distinct().collect(Collectors.toSet());
Here, T overrides equals which watch for the RealId if they are equal else use reflection to watch every other field.
Try this:
List<YourObject> collect = activities
.stream()
.collect(Collectors.groupingBy(
YourObject::getId,
Collectors.maxBy(Comparator.comparing(YourObject::getCreatedDate))))
.entrySet()
.stream()
.map(e -> e.getValue().get())
.collect(Collectors.toList());
Here is used Collectors.groupingBy to create a Map<Integer, Optional<YourObject>>, grouped by id and most recent createDate. The you get the entrySet for this map and collect it to a List.
Without java8 functional stuff:
Map<Long, Item> map = new HashMap<>();
for (Item item: items) {
Item old = map.get(item.getId());
if (old == null || old.getDate().before(item.getDate())) {
map.put(item.getId(), item);
}
}
List<Item> result = new ArrayList<Item>(map.values());

Updating a variable inside stream in Java 8

I'm just playing around with Java 8.
For now I'm trying to understand stream.
So far I haven't seen an example about looping through a map or list then populating a variable that is not part of the map or list.
Here is an example:
class RandomObject {
private int i1;
private String s1;
//setter
//getter
}
// Java 7
RandomObject randomObj = new RandomObject();
Map<Integer, String> mappy = new HashMap<Integer, String>();
Map<Integer, String> collect = new HashMap<Integer, String>();
mappy.put(1, "good map");
mappy.put(2, "nice map");
mappy.put(3, "wow");
mappy.put(4, "mappy the best map");
for (Map.Entry<Integer, String> entry : mappy.entrySet()) {
if (entry.getKey() == 2) {
randomObj.seti1(entry.getKey());
randomObj.sets1(entry.getValue());
collect.put(entry.getKey(), entry.getValue());
}
}
// Java 8
RandomObject randomObj = new RandomObject();
Map<Integer, String> mappy = new HashMap<Integer, String>();
mappy.put(1, "good map");
mappy.put(2, "nice map");
mappy.put(3, "wow");
mappy.put(4, "mappy the best map");
Map<Integer, String> collect = mappy.entrySet().stream()
.filter(map -> map.getKey() == 2)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
// Hmmm i don't know where to put randomObj
Functional programming depends on immutability.
You aren't updating the stream; you're operating on the one you have to create a new one using operations like map, reduce, filter etc.
Since you are using a Map, it makes no sense to use such a boilerplate code except for "using streams", because a Map can't have the same key twice.
This is the best solution
String s = mappy.get(2);
if (s==null) {
throw new IllegalStateException("No value with key = 2 were present");
}
new RandomObject(2, s);
But if you realy whant to use stream, i see three solutions :
The ugly one (even if it is faster) :
Map<Integer, String> collect = mappy.entrySet().stream()
.filter(map -> map.getKey() == 2)
.peek(entry -> {
randomObj.seti1(entry.getKey());
randomObj.sets1(entry.getValue());
})
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
peek() is used to peek each elements of the stream. And it returns the same stream. (But i would not recommend this way).
A better one (require twice as iteration):
Map<Integer, String> collect = mappy.entrySet().stream()
.filter(map -> map.getKey() == 2)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
collect.entrySet().stream()
.forEach(entry -> {
randomObj.seti1(entry.getKey());
randomObj.sets1(entry.getValue());
});
The better one :
Optional<RandomObject> randomObj = mappy.entrySet().stream()
.filter(map -> map.getKey() == 2)
.mapToObj(entry -> new RandomObject(entry.getKey(), entry.getValue()))
.findFirst();
if (!randomObj.isPresent()) {
throw new IllegalStateException("No value with key = 2 were present");
}

Categories

Resources