Set created from complex List - java

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());

Related

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

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

How to return the entire list if filter is false

I have this list
List<String> lstStr = new ArrayList<>();
lstStr.add("1");
lstStr.add("2");
lstStr.add("3");
lstStr.add("4");
lstStr.add("5");
When I search for the string "1" its should return a List<String> = ["1"] and if search string is not in the list for example "0" it should return the entire List <String> =["1","2","3","4","5"]. Can this be achieved using java stream? Please show an example.
I have tried this using the code below but I could get the entire list when I search for say "0"
List<String> filteredLst = lstStr.stream()
.filter(data-> "1".equalsIgnoreCase(data))
.collect(Collectors.toList());
filteredLst.forEach(data2 -> System.out.println(data2));
Thanks in advance.
You can partition on a predicate and return the non-empty list:
Map<Boolean, List<String>> split = lstStr.stream()
.collect(Collectors.partitioningBy("1"::equalsIgnoreCase));
List<String> filteredLst = split.get(Boolean.TRUE).isEmpty() ?
split.get(Boolean.FALSE) : //can also use lstStr instead
split.get(Boolean.TRUE);
Collectors.partitioningBy("1"::equals) will return a 2-entry map, where true will be the key of entries that meet your filter, and false the key of the rest.
filteredLst should contain the value mapped to true if that is not empty, or the value of false otherwise (which would surely be the same as he original list)
If you don't need to handle duplicates then you can do the following :
static List<String> getOneOrAll(List<String> list, String element) {
return list.stream()
.filter(element::equalsIgnoreCase)
.findFirst()
.map(Collections::singletonList)
.orElse(list);
}
...
List<String> result = getOneOrAll(lstStr, "1");
Otherwise you can pass in a predicate and filter the duplicates:
static <T> List<T> getOneOrAll(List<T> list, Predicate<T> predicate) {
List<T> filteredList = list.stream()
.filter(predicate)
.collect(toList());
return filteredList.isEmpty() ? list : filteredList;
}
...
List<String> result = getOneOrAll(lstStr, "1"::equals);
// or
List<String> resultIgnoringCase = getOneOrAll(lstStr, "1"::equalsIgnoreCase);
A possible simple util for this would be using contains :
List<String> findAndReturnValue(List<String> lstStr, String value) {
return lstStr.contains(value) ? Arrays.asList(value) : lstStr;
}
and for possible duplicates in the list:
List<String> findAndReturnValue(List<String> lstStr, String value) {
return lstStr.contains(value) ?
lstStr.stream()
.filter(a -> a.equalsIgnoreCase(value)) // condition as in question
.collect(Collectors.toList()) : lstStr;
}
To reduce the complexity for the cases where the element would be present in the list, the rather simpler solution would be collecting to a list and then checking for the size :
List<String> findAndReturn(List<String> lstStr, String value) {
List<String> filteredLst = lstStr.stream()
.filter(data -> data.equalsIgnoreCase(value))
.collect(Collectors.toList());
return filteredLst.isEmpty() ? lstStr : filteredLst;
}
You can use custom Collector for this purpose.
li.stream().filter(d->"0".equalsIgnoreCase(d)).collect(
Collector.of(
ArrayList::new,
ArrayList::add,
(a, b) -> {
a.addAll(b);
return a;
},
a -> a.isEmpty() ? li : a
)
);
Look for Collector docs: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html
First Check if your list contains that value .
If yes then go for filtering the list
or else just print the Previous list
if(!lstStr.contains("0")) {
lstStr.forEach(data2 -> System.out.println(data2));
}else {
List<String> filteredLst = lstStr.stream()
.filter(data-> "1".equalsIgnoreCase(data))
.collect(Collectors.toList());
filteredLst.forEach(data2 -> System.out.println(data2));
}

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 : Map Lambda expression

I have a Map<String, List<Object>> multiFieldMap and I need to itereate over its value set and add the value to multiFieldsList as below
public List<Object> fetchMultiFieldsList() {
List<Object> multiFieldsList = new ArrayList<Object>();
for (Entry<String, List<Object>> entry : multiFieldMap.entrySet()) {
String entityName = entry.getKey();
List<Object> ids = entry.getValue();
for (Object id : ids) {
Object entity = queryService.query(entityName, queryService.property("id").eq(id));
multiFieldsList.add(entity);
}
}
return multiFieldsList;
}
Am wondering can this method be simplified further?
You can use the Streams API :
List<Object> multiFieldsList =
multiFieldMap.entrySet()
.stream()
.flatMap(e -> e.getValue()
.stream()
.map(o -> queryService.query(e.getKey(), queryService.property("id").eq(o))))
.collect(Collectors.toList());
You can indeed use a stream to simplify you inner loop.
You can replace:
List<Object> ids = entry.getValue();
for (Object id : ids) {
Object entity = queryService.query(entityName, queryService.property("id").eq(id));
multiFieldsList.add(entity);
}
with:
entry.getValue().map(
id -> queryService.query(entityName, queryService.property("id").eq(id))
).forEach(multiFieldsList::add);
But you don't really gain much from that. Your choice...
See #Eran's answer for a "full stream" solution.
You can simplify it like this:
public List<Object> fetchMultiFieldsList() {
List<Object> multiFieldsList = new ArrayList<>();
multiFieldMap.forEach( (entityName, ids ) ->
ids.forEach( id -> multiFieldsList.add(
queryService.query(entityName, queryService.property("id").eq(id)))
)
);
return multiFieldsList;
}
Unless you want to use the Stream API, the method Map.forEach might be the biggest win regarding code simplification as you don’t need to deal with Map.Entry and its generic signature anymore…

Categories

Resources