Convert List to Map using streams - java-8

This question is partially answered here.
My map will have trade Ids grouped by trade Type. This link gives me Trades grouped by tradeType. I know I can further manipulate it to get desired result, but just wondering if its possible in one go.
Here is my code.
public static void main(String[] args) {
Trade trade1 = new Trade(1, TradeStatus.NEW, "type1",1);
Trade trade2 = new Trade(2, TradeStatus.FAILED, "type2",1);
Trade trade3 = new Trade(3, TradeStatus.NEW, "type1",1);
Trade trade4 = new Trade(4, TradeStatus.NEW, "type3",1);
Trade trade5 = new Trade(5, TradeStatus.CHANGED, "type2",1);
Trade trade6 = new Trade(6, TradeStatus.EXPIRED, "type1",2);
List<Trade> list = new ArrayList<>();
list.add(trade1);
list.add(trade2);
list.add(trade3);
list.add(trade4);
list.add(trade5);
list.add(trade6);
Map<String, List<Trade>> result =
list.stream().collect(Collectors.groupingBy(Trade::getTradeType));
System.out.println(result);//prints Trades grouped by trade type
Map<String, List<String>> unvaluedtradesMap = new HashMap<>();
for (Trade trade : list) {
String tradeType = trade.getTradeType();
if(unvaluedtradesMap.containsKey(trade.getTradeType())){
List<String> unValuedTrades = unvaluedtradesMap.get(tradeType);
unValuedTrades.add(trade.getId());
unvaluedtradesMap.put(tradeType, unValuedTrades);
}else{
unvaluedtradesMap.put(tradeType, Lists.newArrayList(trade.getId()));
}
}
System.out.println(unvaluedtradesMap);//prints TradeIDS grouped by trade type
}

You can do this by chaining a mapping collector to groupingBy :
Map<String, List<String>> unvaluedtradesMap
= list.stream().collect(Collectors.groupingBy(Trade::getTradeType,
Collectors.mapping(Trade::getId,
Collectors.toList())));

Related

taking time to read the records and save in the other table using java streams and spring boot JPA

From the below code, i want to save the details from PersonInfoEntity table and for each personInfo, I want to store the records in ResearchInfoEntity.
I have around 100,000 records to insert from PersonInfoEntity to ResearchInfoEntity. Issue is with the below code, it is talking lot of time to save the records in the ResearchInfo table. In almost 3 hours it just stored around 2000 records. Please let me know where it is taking time to execute or if any code optimzation is required to insert bulk records.
Sample code :
List<PersonInfoEntity> PersonInfoEntityList = personInfoRepository.findAll();
Map<Long, List<PersonInfoEntity>> personInfoEntityMap = PersonInfoEntityList.stream().
collect(Collectors.groupingBy(
personInfoResponse -> personInfoResponse.getPerson().getPersonId()
));
List<ResearchEntity> researchEntityList = researchRepository.findAll();
List<ResearchInfoEntity> researchInfoEntityList = new ArrayList<>();
for(ResearchEntity researchEntity : researchEntityList){
List<PersonInfoEntity> personInfoResponseList1 = personInfoEntityMap.get(researchEntity.getPerson().getPersonId());
if(Objects.nonNull(personInfoResponseList1)) {
for (PersonInfoEntity PersonInfoEntity : personInfoResponseList1) {
ResearchInfoEntity researchInfoEntity = new ResearchInfoEntity();
researchInfoEntity.setRecovery(ResearchEntity);
researchInfoEntity.setMilestoneGroupId(PersonInfoEntity.getMilestoneGroupId());
researchInfoEntity.setMilestoneId(PersonInfoEntity.getMilestoneId());
researchInfoEntity.setMilestoneStepId(PersonInfoEntity.getMilestoneStepId());
researchInfoEntity.setMilestoneStepValue(PersonInfoEntity.getMilestoneStepValue());
researchInfoEntity.setCreateBy(PersonInfoEntity.getCreateBy());
researchInfoEntity.setCreateTime(PersonInfoEntity.getCreateTime());
researchInfoEntity.setUpdateBy(PersonInfoEntity.getUpdateBy());
researchInfoEntity.setUpdateTime(PersonInfoEntity.getUpdateTime());
researchInfoEntityList.add(researchInfoEntity);
// researchInfoEntityRepository.save(recoveryMilestoneStep);
}
researchInfoEntityRepository.saveAll(researchInfoEntityList);
}
}
Your mapping of PersonInfoEntity to researchInfoEntity could be done asynchronously.
You could also try to use parallelStream:
public void yourMethod() {
List<PersonInfoEntity> PersonInfoEntityList = personInfoRepository.findAll();
Map<Long, List<PersonInfoEntity>> personInfoEntityMap = PersonInfoEntityList.stream().
collect(Collectors.groupingBy(
personInfoResponse -> personInfoResponse.getPerson().getPersonId()
));
List<ResearchEntity> researchEntityList = researchRepository.findAll();
List<ResearchInfoEntity> researchInfoEntityList = new ArrayList<>();
for (ResearchEntity researchEntity : researchEntityList) {
List<PersonInfoEntity> personInfoResponseList1 = personInfoEntityMap.get(researchEntity.getPerson().getPersonId());
if (Objects.nonNull(personInfoResponseList1)) {
List<ResearchInfoEntity> researchInfoListFromPerson = personInfoResponseList1
.parallelStream() // <--
.map(this::toResearchInfoEntity)
.collect(Collectors.toList());
researchInfoEntityList.addAll(researchInfoListFromPerson);
}
}
researchInfoEntityRepository.saveAll(researchInfoEntityList);
}
private ResearchInfoEntity toResearchInfoEntity(PersonInfoEntity personInfoEntity) {
ResearchInfoEntity researchInfoEntity = new ResearchInfoEntity();
researchInfoEntity.setRecovery(ResearchEntity);
researchInfoEntity.setMilestoneGroupId(PersonInfoEntity.getMilestoneGroupId());
researchInfoEntity.setMilestoneId(PersonInfoEntity.getMilestoneId());
researchInfoEntity.setMilestoneStepId(PersonInfoEntity.getMilestoneStepId());
researchInfoEntity.setMilestoneStepValue(PersonInfoEntity.getMilestoneStepValue());
researchInfoEntity.setCreateBy(PersonInfoEntity.getCreateBy());
researchInfoEntity.setCreateTime(PersonInfoEntity.getCreateTime());
researchInfoEntity.setUpdateBy(PersonInfoEntity.getUpdateBy());
researchInfoEntity.setUpdateTime(PersonInfoEntity.getUpdateTime());
return researchInfoEntity;
}
Also, trying to work with 100 000 elements at once takes up a lot of memory. You could try processing your elements in batches.
For example:
public void export(int batchSize) {
int numberOfElementFetched;
int pageCount = 0;
do {
// entityManager.clear(); // Needed only if you are in a transactional state, you need to clear the entity manager.
// Otherwise, for every iteration, it will keep previous fetched elements in memory
PageRequest requestByBatch = PageRequest.of(pageCount, batchSize, Sort.by(Sort.Direction.ASC));
numberOfElementFetched = yourMethod(requestByBatch);
pageCount++;
}
while (numberOfElementFetched == batchSize);
}
public void yourMethod(PageRequest pageRequest) {
List<PersonInfoEntity> PersonInfoEntityList = personInfoRepository.findAll();
Map<Long, List<PersonInfoEntity>> personInfoEntityMap = PersonInfoEntityList.stream().
collect(Collectors.groupingBy(
personInfoResponse -> personInfoResponse.getPerson().getPersonId()
));
List<ResearchEntity> researchEntityList = researchRepository.findAll(pageRequest).getContent();
List<ResearchInfoEntity> researchInfoEntityList = new ArrayList<>();
for (ResearchEntity researchEntity : researchEntityList) {
List<PersonInfoEntity> personInfoResponseList1 = personInfoEntityMap.get(researchEntity.getPerson().getPersonId());
if (Objects.nonNull(personInfoResponseList1)) {
for (PersonInfoEntity PersonInfoEntity : personInfoResponseList1) {
ResearchInfoEntity researchInfoEntity = new ResearchInfoEntity();
researchInfoEntity.setRecovery(ResearchEntity);
researchInfoEntity.setMilestoneGroupId(PersonInfoEntity.getMilestoneGroupId());
researchInfoEntity.setMilestoneId(PersonInfoEntity.getMilestoneId());
researchInfoEntity.setMilestoneStepId(PersonInfoEntity.getMilestoneStepId());
researchInfoEntity.setMilestoneStepValue(PersonInfoEntity.getMilestoneStepValue());
researchInfoEntity.setCreateBy(PersonInfoEntity.getCreateBy());
researchInfoEntity.setCreateTime(PersonInfoEntity.getCreateTime());
researchInfoEntity.setUpdateBy(PersonInfoEntity.getUpdateBy());
researchInfoEntity.setUpdateTime(PersonInfoEntity.getUpdateTime());
researchInfoEntityList.add(researchInfoEntity);
}
}
}
researchInfoEntityRepository.saveAll(researchInfoEntityList);
return researchEntityList.getSize();
}

Convert list of complex objects to Map using Java 8

I have an array of subjects
List<String> subjects = Arrays.asList(“physics”, “maths”);
I wanted to create a dummy list of users for each of these subjects and add them to a map with key as subject and value as List
Something like
Map<String,List<User>> userMap = new HashMap<>();
for(String subject: subjects){
List<User> users = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
users.add(user);
}
userMap.put(subject,users);
}
I wanted to try this with the Java 8. Just tried something below, but doesn’t look like the right way.
subjects.stream().map((subjectName)->{
List<User> userList = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
userList.add(user);
}
})
subjects.stream()
.map(subjectName -> {
List<User> users = .... create the users;
return new SimpleEntry<>(subjectName, users);
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
This would be one way if you really wanted to do it with java-8 and streams. One improvement would be to have a method that takes a String subjectName and create that Entry for example:
private static Entry<String, List<User>> createEntry(String subjectName) {
List<User> users = .... create the user;
return new SimpleEntry<>(subjectName, users);
}
And use it with:
subjects.stream()
.map(YourClass::createEntry)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
Just notice that your loop is the cleanest way to do it IMO
One way to do it with java 8:
Map<String,List<User>> userMap = new HashMap<>();
subjects.forEach(s -> {
for (int i = 0; i < 10; i++)
userMap.computeIfAbsent(s, k -> new ArrayList<>())
.add(new User("first name" + i + subject, "last name" + i + subject));
});
Let's do this one step at a time. First, the inner loop for creating 10 users can be written with streams as:
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
List<User> userList = IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject)
.collect(toList());
And the outer loop can be written as
subjects.stream()
.collect(toMap(
subject -> subject, // key for the map is the subject
subject -> ... // whatever should be the value of the map
));
And now we can put it together:
Map<String, List<User>> userMap = subjects.stream()
.collect(toMap(
subject -> subject,
subject -> IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject))
.collect(toList())
));

How to collect map from the Set of objects that has a list using Collectors.toMap

I have class Element with a list, my intended output is like this:
Map<String , List<Element>>
{
1 = [Element3, Element1],
2 = [Element2, Element1],
3 = [Element2, Element1], 4=[Element2]
}
And my input is set of element objects, I used forEach to get the desired outcome, but I'm looking for how to collect it using collectors.toMap. Any inputs are much appreciated
Set<Element> changes = new HashSet();
List<String> interesetList = new ArrayList();
interesetList.add("1");
interesetList.add("2");
interesetList.add("3");
Element element = new Element(interesetList);
changes.add(element);
interesetList = new ArrayList();
interesetList.add("2");
interesetList.add("3");
interesetList.add("4");
element = new Element(interesetList);
changes.add(element);
Map<String, List<Element>> collect2 = new HashMap();
changes.forEach(element -> {
element.getInterestedList().forEach(tracker -> {
collect2.compute(tracker, ( key , val) -> {
List<Element> elementList = val == null ? new ArrayList<Element>() : val;
elementList.add(Element);
return elementList;
});
});
});
class Element {
List<String> interestedList;
static AtomicInteger sequencer = new AtomicInteger(0);
String mName;
public Element(List<String> aList) {
interestedList = aList;
mName = "Element" + sequencer.incrementAndGet();
}
public List<String> getInterestedList() {
return interestedList;
}
#Override
public String toString() {
return mName;
}
}
You can do it by using Collectors.groupingBy instead of Collectors.toMap, along with Collectors.mapping, which adapts a collector to another collector:
Map<String, List<Element>> result = changes.stream()
.flatMap(e -> e.getInterestedList().stream().map(t -> Map.entry(t, e)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
You need to use the Stream.flatMap method first and then pair the elements of the inner lists with the current Element instance. I did this via the new Java 9's Map.entry(key, value) method. If you're not on Java 9 yet, you could change it to new AbstractMap.SimpleEntry<>(key, value).
After flatmapping, we need to collect instances of Map.Entry. So I'm using Collectors.groupingBy to classify entries by key (where we had previously stored each element of the inner lists, aka what you call tracker in your code). Then, as we don't want to have instances of List<Map.Entry<String, Element>> as the values of the map, we need to transform each Map.Entry<String, Element> of the stream to just Element (that's why I'm using Map.Entry::getValue as the first argument of Collectors.mapping). We also need to specify a downstream collector (here Collectors.toList()), so that the outer Collectors.groupingBy collector knows where to place all the adapted elements of the stream that belong to each group.
A shorter and surely more efficient way to do the same (similar to your attempt) could be:
Map<String, List<Element>> result = new HashMap<>();
changes.forEach(e ->
e.getInterestedList().forEach(t ->
result.computeIfAbsent(t, k -> new ArrayList<>()).add(e)));
This uses Map.computeIfAbsent, which is a perfect fit for your use case.

Java 8 sort Map<String, List<Double>>

How to sort a Map > according to average of all the values in the list using Java8 streams?
I couldn't figure out how to collect the sorted map to another instance of map. Thanks for the help in advance.
Here is the code that I tried
Map<String,List<Double>> map = new HashMap<String,List<Double>>();
LinkedList<Double> list1 = new LinkedList<Double>(Arrays.asList(12.5,45.67));
map.put("1",list1);
LinkedList<Double> list2 = new LinkedList<Double>(Arrays.asList(13.5,49.67));
map.put("2", list2);
LinkedList<Double> list3 = new LinkedList<Double>(Arrays.asList(10.5,9.67));
map.put("3", list3);
LinkedList<Double> list4 = new LinkedList<Double>(Arrays.asList(1.5,40.67));
map.put("4", list4);
map.entrySet().stream().sorted(new Comparator<Map.Entry<String, List<Double>>>() {
#Override
public int compare(Entry<String, List<Double>> arg0, Entry<String, List<Double>> arg1) {
return (int)(((LinkedList<Double>)arg1.getValue()).stream().mapToDouble(Double::doubleValue).sum() - ((LinkedList<Double>)arg0.getValue()).stream().mapToDouble(Double::doubleValue).sum());
}
});
System.out.println(map);
The output I get is still the same Map with out sorting. The expected output here is a map with the entries in this order
<"2", list2>
<"1", list1>
<"4", list4>
<"3", list3>
Edit:
Here is the solution I got to
Map<String,List<Double>> map = new HashMap<String,List<Double>>();
LinkedList<Double> list1 = new LinkedList<Double>(Arrays.asList(12.5,45.67));
map.put("1",list1);
LinkedList<Double> list2 = new LinkedList<Double>(Arrays.asList(13.5,49.67));
map.put("2", list2);
LinkedList<Double> list3 = new LinkedList<Double>(Arrays.asList(10.5,9.67));
map.put("3", list3);
LinkedList<Double> list4 = new LinkedList<Double>(Arrays.asList(1.5,40.67));
map.put("4", list4);
LinkedHashMap<String,List<Double>> orderedMap = new LinkedHashMap<String,List<Double>>();
Iterator<Entry<String, List<Double>>> iterator = map.entrySet().stream().sorted(new Comparator<Map.Entry<String, List<Double>>>() {
#Override
public int compare(Entry<String, List<Double>> arg0,
Entry<String, List<Double>> arg1) {
return (int)((((LinkedList<Double>) arg1.getValue()).stream().mapToDouble(Double::doubleValue).sum() - ((LinkedList<Double>)arg0.getValue()).stream().mapToDouble(Double::doubleValue).sum()));
}
}).iterator();
while(iterator.hasNext()){
Entry<String, List<Double>> next = iterator.next();
orderedMap.put(next.getKey(), next.getValue());
}
System.out.println(orderedMap);
I am not happy with this solution. Is there a precise and better way of doing this?
Your comparator doesn’t do the job you want, or at least not always. It works only if all the lists have the same size because you are comparing the sum not the average values for each list. I created a new Comparator based on lists average values and simplified the code. Below is my code:
Comparator<Map.Entry<String,List<Double>>> byAverange =
(Entry<String,List<Double>> o1, Entry<String,List<Double>> o2)->
{
return ((Double)o2.getValue().stream().mapToDouble(a -> a).average().getAsDouble())
.compareTo(
o1.getValue().stream().mapToDouble(a -> a).average().getAsDouble());
};
LinkedHashMap<String, List<Double>> ordered = new LinkedHashMap<>();
map.entrySet().stream().sorted(byAverange).forEach(entry -> ordered.put(entry.getKey(), entry.getValue()));
System.out.println(ordered);
output:
{2=[13.5, 49.67], 1=[12.5, 45.67], 4=[1.5, 40.67], 3=[10.5, 9.67]}
hope this can help.

Get random value from HashMap

I want to get random value of HashMap, but only from one key.
This is how i build my HashMap. Every Key has multiple values.
ArrayList<HashMap<String, String>> items = new ArrayList<HashMap<String, String>>();
for (.....) {
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
Element e = (Element) nl.item(i);
map.put(KEY_1, parser.getValue(e, KEY_1));
map.put(KEY_2, parser.getValue(e, KEY_2));
// adding HashList to ArrayList
items.add(map);
}
So key "KEY_1" has multiple values so do "KEY_2".
Now i want to get random value using key "KEY_1". How to do that ?
EDIT: I think i know where is problem, although dont know yet how to fix that.
Problem is defining size(); I need somehow get size not from all HashMap items like "items.size();" but something more similar to "items.KEY_1.size();"
With this code getting NullPointerException
int index = random.nextInt(items.size());
HashMap<String, String> randomitem = new HashMap<String, String>();
randomitem = items.get(index);
System.out.println(randomitem.get(KEY_1));
With this code im able to get value not random:
int index = 2;
HashMap<String, String> randomitem = new HashMap<String, String>();
randomitem = items.get(index);
System.out.println(randomitem.get(KEY_1));
EDIT: With this code i can get random values but i get values from all keys. How to get values only from one key ?
Random rand = new Random();
indexx = rand.nextInt(items.size());
System.out.println(items.get(indexx));
Solved With this code i can get random value from certain key.
Random rand = new Random();
indexx = rand.nextInt(items.size());
HashMap<String, String> randomitems = items.get(indexx);
System.out.println(randomitems.get(KEY_1));

Resources