Updating a variable inside stream in Java 8 - java

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");
}

Related

Collect HashMap<String,Object> from Stream<T>

Using Map of key to iterate and based on condition returning HashMap,need to collect return map below code.
trying to convert below java code in java 8
for (String key : sectionsJson.keySet()) {
Map<String, Object> section = (Map<String, Object>) sectionsJson.get(key);
if (index == (Integer) section.get(SECTION_FIELD_KEY_INDEX)) {
section.put(SECTION_FIELD_KEY_SECTION_KEY, key);
return section;
}
}
any suggestion.
It looks like you want to produce a Map having at most a single entry.
Map<String,Object> map =
sectionsJson.entrySet()
.stream()
.filter(e -> {
Map<String, Object> section = e.getValue ();
return index == (Integer) section.get(SECTION_FIELD_KEY_INDEX);
}
.map(e -> new SimpleEntry<> (SECTION_FIELD_KEY_SECTION_KEY, e.getKey ()))
.limit(1)
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
It looks like your original code is simpler.
Perhaps you can simply search for the desired key:
String value =
sectionsJson.entrySet()
.stream()
.filter(e -> {
Map<String, Object> section = e.getValue ();
return index == (Integer) section.get(SECTION_FIELD_KEY_INDEX);
}
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
since you are producing a Map having (at most) a single value and a constant key, so the value is the only data the Stream pipeline should be searching for.
As per your existing code. You are returning the map as soon as it finds any match. Same thing you can do using java 8 as well.
Optional<Integer> findAny = sectionsJson.keySet().stream().filter(key -> {
Map<String, Object> section = (Map<String, Object>)sectionsJson.get(key);
if (index == (Integer)section.get("SECTION_FIELD_KEY_INDEX")) {
section.put("SECTION_FIELD_KEY_SECTION_KEY", key);
return true;
}
return false;
}).findFirst();
if (findAny.isPresent()) {
System.out.println(sectionsJson.get(findAny.get()));
}
Depending on what you want to achieve following might be also possible solutions:
simplifying the loop
for (Map.Entry<String, Map<String, Object>> entry : sectionsJson.entrySet()) {
Map<String, Object> section = entry.getValue();
if (index == section.get(SECTION_FIELD_KEY_INDEX)) {
section.put(SECTION_FIELD_KEY_SECTION_KEY, entry.getKey());
return section;
}
}
// add the code for the case when no section was found
separate stream processing and mutating the element
// find the section
Optional<Map.Entry<String, Map<String, Object>>> first = sectionsJson.entrySet().stream()
.filter(e -> (Integer) e.getValue().get(SECTION_FIELD_KEY_INDEX) == index)
.findFirst();
// mutate the section
if (first.isPresent()) {
Map.Entry<String, Map<String, Object>> sectionJson = first.get();
sectionJson.getValue().put(SECTION_FIELD_KEY_SECTION_KEY, sectionJson.getKey());
return sectionJson.getValue();
}
// add the code for the case when no section was found

Java 8 | Finding Map Entry with highest size of value

I have model Person[city, name]. I have collected them in Map And Grouped them by city. I need to trace the city that has most no of person staying there and return only that entry as part of Map. I'v tried and also it is working but i was wondering is there any better way of doing.
Comparator<Entry<String, List<Person>>> compareByCityPopulation =
Comparator.comparing(Entry<String, List<Person>>::getValue, (s1, s2) -> {
return s1.size() - s2.size();
});
HashMap mapOfMostPopulatedCity = persons.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(Person::getCity), m -> {
Entry<String, List<Person>> found = m.entrySet().stream().max(compareByCityPopulation).get();
HashMap<String, List<Person>> hMap = new HashMap<>();
hMap.put(found.getKey(), found.getValue());
return hMap;
}));
System.out.println("*City with Most no of people*");
mapOfMostPopulatedCity.forEach((place, peopleDetail) -> System.out.println("Places " + place + "-people detail-" + peopleDetail));
Please Suggest how can we write better in java 8.
After getting the max map entry, you have to convert that into a map which has a single entry. For that you can use Collections.singletonMap()
Map<String, List<Person>> mapOfMostPopulatedCity = persons.stream()
.collect(Collectors.groupingBy(Person::getCity)).entrySet().stream()
.max(Comparator.comparingInt(e -> e.getValue().size()))
.map(e -> Collections.singletonMap(e.getKey(), e.getValue()))
.orElseThrow(IllegalArgumentException::new);
With Java9 you can use Map.of(e.getKey(), e.getValue()) to build the map with a single entry.
Suppose if you have an list of persons
List<Person> persons = new ArrayList<Person>();
Then first group By them based on city and then get the Entry with max values in list max will return Optional of Entry, so i won't make it complicate i will just use HashMap to store the result if it present in optional or else will return the empty Map
Map<String, List<Person>> resultMap = new HashMap<>();
persons.stream()
.collect(Collectors.groupingBy(Person::getCity)) //group by city gives Map<String,List<Person>>
.entrySet()
.stream()
.max(Comparator.comparingInt(value->value.getValue().size())) // return the Optional<Entry<String, List<Person>>>
.ifPresent(entry->resultMap.put(entry.getKey(),entry.getValue()));
//finally return resultMap

Accumulator not working properly in parallel stream

I made collector who can reduce a stream to a map which has the keys as the items that can be bought by certain customers and the names of customers as values, my implementation is working proberly in sequential stream
but when i try to use parallel it's not working at all, the resulting sets always contain one customer name.
List<Customer> customerList = this.mall.getCustomerList();
Supplier<Object> supplier = ConcurrentHashMap<String,Set<String>>::new;
BiConsumer<Object, Customer> accumulator = ((o, customer) -> customer.getWantToBuy().stream().map(Item::getName).forEach(
item -> ((ConcurrentHashMap<String,Set<String>>)o)
.merge(item,new HashSet<String>(Collections.singleton(customer.getName())),
(s,s2) -> {
HashSet<String> res = new HashSet<>(s);
res.addAll(s2);
return res;
})
));
BinaryOperator<Object> combiner = (o,o2) -> {
ConcurrentHashMap<String,Set<String>> res = new ConcurrentHashMap<>((ConcurrentHashMap<String,Set<String>>)o);
res.putAll((ConcurrentHashMap<String,Set<String>>)o2);
return res;
};
Function<Object, Map<String, Set<String>>> finisher = (o) -> new HashMap<>((ConcurrentHashMap<String,Set<String>>)o);
Collector<Customer, ?, Map<String, Set<String>>> toItemAsKey =
new CollectorImpl<>(supplier, accumulator, combiner, finisher, EnumSet.of(
Collector.Characteristics.CONCURRENT,
Collector.Characteristics.IDENTITY_FINISH));
Map<String, Set<String>> itemMap = customerList.stream().parallel().collect(toItemAsKey);
There is certainly a problem in my accumulator implementation or another Function but I cannot figure it out! could anyone suggest what should i do ?
Your combiner is not correctly implemented.
You overwrite all entries that has the same key. What you want is adding values to existing keys.
BinaryOperator<ConcurrentHashMap<String,Set<String>>> combiner = (o,o2) -> {
ConcurrentHashMap<String,Set<String>> res = new ConcurrentHashMap<>(o);
o2.forEach((key, set) -> set.forEach(string -> res.computeIfAbsent(key, k -> new HashSet<>())
.add(string)));
return res;
};

Transform a map from daily data to weekly data in 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());
}

Java 8 Join Map with Collectors.toMap

I'm trying to collect in a Map the results from the process a list of objects and that it returns a map. I think that I should do it with a Collectors.toMap but I haven't found the way.
This is the code:
public class Car {
List<VersionCar> versions;
public List<VersionCar> getVersions() {
return versions;
}
}
public class VersionCar {
private String wheelsKey;
private String engineKey;
public String getWheelsKey() {
return wheelsKey;
}
public String getEngineKey() {
return engineKey;
}
}
process method:
private static Map<String,Set<String>> processObjects(VersionCar version) {
Map<String,Set<String>> mapItems = new HashMap<>();
mapItems.put("engine", new HashSet<>(Arrays.asList(version.getEngineKey())));
mapItems.put("wheels", new HashSet<>(Arrays.asList(version.getWheelsKey())));
return mapItems;
}
My final code is:
Map<String,Set<String>> mapAllItems =
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.collect(Collectors.toMap()); // here I don't know like collect the map.
My idea is to process the list of versions and in the end get a Map with two items: wheels and engine but with a set<> with all different items for all versions. Do you have any ideas as can I do that with Collectors.toMap or another option?
The operator you want to use in this case is probably "reduce"
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.reduce((map1, map2) -> {
map2.forEach((key, subset) -> map1.get(key).addAll(subset));
return map1;
})
.orElse(new HashMap<>());
The lambda used in "reduce" is a BinaryOperator, that merges 2 maps and return the merged map.
The "orElse" is just here to return something in the case your initial collection (versions) is empty.
From a type point of view it gets rid of the "Optional"
You can use Collectors.toMap(keyMapper, valueMapper, mergeFunction). Last argument is used to resolve collisions between values associated with the same key.
For example:
Map<String, Set<String>> mapAllItems =
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(firstSet, secondSet) -> {
Set<String> result = new HashSet<>();
result.addAll(firstSet);
result.addAll(secondSet);
return result;
}
));
To get the mapAllItems, we don't need and should not define processObjects method:
Map<String, Set<String>> mapAllItems = new HashMap<>();
mapAllItems.put("engine", car.getVersions().stream().map(v -> v.getEngineKey()).collect(Collectors.toSet()));
mapAllItems.put("wheels", car.getVersions().stream().map(v -> v.getWheelsKey()).collect(Collectors.toSet()));
Or by AbstractMap.SimpleEntry which is lighter than the Map created byprocessObjects`:
mapAllItems = car.getVersions().stream()
.flatMap(v -> Stream.of(new SimpleEntry<>("engine", v.getEngineKey()), new SimpleEntry<>("wheels", v.getWheelsKey())))
.collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(e -> e.getValue(), Collectors.toSet())));

Categories

Resources