Java streams: return results to custom object - java

I have a piece of code that has list of objects as follows.
List<PivotMapEgModel> pivotMapList = List.of(new PivotMapEgModel(1L, "1"), new PivotMapEgModel(1L, "2"), new PivotMapEgModel(1L, "3"), new PivotMapEgModel(2L, "5"));
It is guaranteed that there will always be a maximum of 3 codes per value.
I have a class that looks like this:
#Data
#AllArgsConstructor
class ResultSet {
long value;
String code_1;
String code_2;
String code_3;
}
I am currently doing the stream operation in this way:
pivotMapList.stream().collect(Collectors.groupingBy(PivotMapEgModel::getValue, Collectors.mapping(PivotMapEgModel::getCode, Collectors.toList())))
This is producing the output in the following way: {1=[1, 2, 3], 2=[5]}
I need to perform stream operations on the pivotMapList to get the output to show in List<ResultSet> as follows:
[{value=1, code_1=1, code_2=2, code_3=3},
{value=2, code_1=1, code_2=null, code_3=null}]
I am not sure how I can get List<ResultSet> from stream operations
Any help to achieve the desired output would be greatly appreciated! Thank you

You have already mapped value to its codes. You can just continue by streaming the entry set of the resulting map and map entries to ResultSet.
List<ResultSet> result = pivotMapList.stream()
.collect(Collectors.groupingBy(PivotMapEgModel::getValue, Collectors.mapping(PivotMapEgModel::getCode, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new ResultSet(entry.getKey(), getCode(entry.getValue(), 0), getCode(entry.getValue(), 1), getCode(entry.getValue(), 2)))
.collect(Collectors.toList());
getCode() is simple method taking care not to get exception when retrieving values from the list.
private static String getCode(List<String> codes, int index) {
if (index >= codes.size()) {
return null;
}
return codes.get(index);
}

Here is one way.
class ResultSet {
long value;
String code_1;
String code_2;
String code_3;
public ResultSet(long value, String code_1,String code_2, String code_3) {
this.value = value;
this.code_1 = code_1;
this.code_2 = code_2;
this.code_3 = code_3;
}
public String toString() {
return "{%d, %s, %s, %s}".formatted(value, code_1, code_2, code_3);
}
}
Use your existing map to build the list. Stream the entrySet of the map and use map to instantiate a ResultSet instance. The forloop will fill the list with nulls if it isn't fully populated.
List<ResultSet> resultSet = map.entrySet().stream()
.<ResultSet>map(ent-> {
List<String> lst = ent.getValue();
for( int i = lst.size(); i < 3; i++) {
lst.add(null);
}
return new ResultSet(ent.getKey(), lst.get(0),
lst.get(1), lst.get(2));
}).toList();
resultSet.forEach(System.out::println);
prints
{1, 1, 2, 3}
{2, 5, null, null}
Note that you could simply stream the existing entrySet from the original map to combine the process, returning the desired List<ResultSet>

Related

Calculate and classify the data in the collection list in java

There are the following entity classes:
#NoArgsConstructor
#AllArgsConstructor
#Data
class Log {
private String platform;
private LocalDateTime gmtCreate;
private Integer enlistCount;
private Integer dispatcherCount;
private Integer callbackCount;
}
Now, I have a list of 10,000 Log entity classes. I want to achieve the following effect. Group the data according to the gmtCreate field by each hour. At the same time, in each group, group by the platform field. Finally, , find the sum of the individual values ​​(enlistCount, dispatcherCount, callbackCount) in these groups. The result looks like this:
Map<Integer, Map<String, Map<String, Integer>>> result = new HashMap<>();
/*
{
"23": {
"platform1": {
"callbackTotal": 66,
"dispatcherTotal": 77,
"enlistTotal": 33
},
"platform2": {
"callbackTotal": 13,
"dispatcherTotal": 5,
"enlistTotal": 64
}
},
"24": {
"platform2": {
"callbackTotal": 64,
"dispatcherTotal": 47,
"enlistTotal": 98
},
"platform7": {
"callbackTotal": 0,
"dispatcherTotal": 3,
"enlistTotal": 21
}
}
}
*/
The way I can think of is to use the stream to traverse and group multiple times, but I am worried that the efficiency is very low. Is there any efficient way to do it?
You can do it all in one stream by using groupingBy with a downstream collector and a mutable reduction with collect. You need a helper class to sum the values:
public class Total {
private int enlistCount = 0;
private int dispatcherCount = 0;
private int callbackCount = 0;
public Total addLog(Log log) {
this.enlistCount += log.enlistCount();
this.dispatcherCount += log.dispatcherCount();
this.callbackCount += log.callbackCount();
return this;
}
public Total add(Total that) {
this.enlistCount += that.enlistCount;
this.dispatcherCount += that.dispatcherCount;
this.callbackCount += that.callbackCount;
return this;
}
public Map<String, Integer> toMap() {
Map<String, Integer> map = new HashMap<>();
map.put("enlistTotal", enlistCount);
map.put("dispatcherTotal", dispatcherCount);
map.put("callbackTotal", callbackCount);
return map;
}
#Override
public String toString() {
return String.format("%d %d %d", enlistCount, dispatcherCount, callbackCount);
}
}
Then the stream, grouping, and collection looks like this:
logs.stream().collect(Collectors.groupingBy(Log::getPlatform,
Collectors.groupingBy(log -> log.getGmtCreate().getHour(),
Collector.of(Total::new, Total::addLog, Total::add, Total::toMap))))
To break that down, there's a groupingBy on the platform, and inside that a groupingBy on the hour of the day. Then all the log entries are summed by a collector which does a mutable reduction:
Collector.of(Total::new, Total::addLog, Total::add, Total::toMap)
This collector uses a supplier that provides a new Total with zeros for the counts, an accumulator that adds each Log's counts to the Total, a combiner that knows how to sum two Totals (this would only be used in a parallel scenario), and finally a finishing function that transforms the Total object to a Map<String, Integer>.
I would use a loop and make use of Map.computeIfAbsent and Map.merge. ComputeIfAbsent will determine if an entry is available for the given key. If so, it will return the value for that entry, otherwise it will create an entry with the supplied key and value and return that value. In this case, the value is another map. So the first time the hour is added. But it also returns the map just created for that hour. So another computeIfAbsent can be appended and the process repeated to get the platform map.
Map.merge is similar except that it will take a key, a value, and a merge function. If the value is not present for the key, the supplied value will be used. Otherwise, the merge function will be used to apply the new value to the map, merging with the existing one. In this case, the desired function is to add them so Integer::sum is used for this process.
I ran this for 100_000 copies of the supplied test data and it took about a second to run.
Here is some data
LocalDateTime ldt = LocalDateTime.now();
List<Log> list = new ArrayList<>(List.of(
new Log("platform1", ldt.plusHours(1), 66, 77, 33),
new Log("platform1", ldt.plusHours(1), 66, 77, 33),
new Log("platform2", ldt.plusHours(1), 13, 5, 64),
new Log("platform2", ldt.plusHours(2), 64, 47, 98),
new Log("platform7", ldt.plusHours(2), 0, 3, 21),
new Log("platform7", ldt.plusHours(2), 10, 15, 44)));
And here is the process.
first create Map to hold the results.
Map<Integer, Map<String, Map<String, Integer>>> result =
new HashMap<>();
Now loop thru the list of logs and create the maps as you need them, summing up the values as you go.
for (Log log : list) {
Map<String, Integer> platformMap = result
.computeIfAbsent(log.getGMTCreate().getHour(),
v -> new HashMap<>())
.computeIfAbsent(log.getPlatform(),
v -> new HashMap<>());
platformMap.merge("callbackTotal", log.getCallbackCount(),
Integer::sum);
platformMap.merge("dispatcherTotal",
log.getDispatcherCount(), Integer::sum);
platformMap.merge("enlistTotal", log.getEnlistCount(),
Integer::sum);
}
Print the results.
result.forEach((k, v) -> {
System.out.println(k);
v.forEach((kk, vv) -> {
System.out.println(" " + kk);
vv.entrySet().forEach(
e -> System.out.println(" " + e));
});
});
prints
12
platform2
callbackTotal=64
enlistTotal=13
dispatcherTotal=5
platform1
callbackTotal=66
enlistTotal=132
dispatcherTotal=154
13
platform2
callbackTotal=98
enlistTotal=64
dispatcherTotal=47
platform7
callbackTotal=65
enlistTotal=10
dispatcherTotal=18

Is there a way of using a for loop to sum integer that have a string associated with them?

I have read a CSV file into a ArrayList but need to use a for loop to sum all the values that have a specific name with them, then return the top strings, in this case letters, in a string array. For example,
"A", 2
"B", 3
"C", 4
"A", 1
"B", 3
I have a class which reads the csv into objects so i have getters if that is of any help.
The result would give back a String [] that would have, in order, [B, C, A] as B totals 6, C totals 4 and A totals 3. Thank you.
Code I have so far,
public ArrayList<String> getTopRooms(int n){
ArrayList<String> roomNames = new ArrayList<>();
for (int i =0; i<recordList.size();i++){
if(!roomNames.contains(recordList.get(i).getRoomName()))
roomNames.add(recordList.get(i).getRoomName());
}
recordList contains data from the csv file, in this case i am trying to get the top rooms that have been booked. all rooms have a length of time which is shown by an int so for example, kitchen would have the length of 2.
Just use a map to keep track of the tallies for each letter/key.
Map<String, Integer> map = new HashMap<>();
for (String line : yourList) {
String[] parts = line.split(",\\s*");
String key = parts[0];
Integer value = Integer.parseInt(parts[1]);
Integer currValue = map.get(key);
map.put(key, Objects.isNull(currValue) ? value : currValue + value);
}
map.entrySset().stream().forEach(e -> System.out.println(e));
I am assuming here that your flat file actually looks like:
A, 2
B, 3
C, 4
A, 1
B, 3
and that each entry in your record list would be one CSV tuple.
Create a plain old java object from the String and Integer values, then store those objects in a list. We then take that list of objects and group them based on their identifier, and find the sum of each of the subsequent matching pojos with that identifier.
class Pojo {
final String identifier;
final int value;
Pojo(String identifier, int value) {
this.identifier = identifier;
this.value = value;
}
public String getIdentifier() {
return identifier;
}
public int getValue() {
return value;
}
}
List<Pojo> pojos = new ArrayList<>(
Arrays.asList(
new Pojo("A", 2),
new Pojo("B", 3),
new Pojo("C", 4),
new Pojo("A", 1),
new Pojo("B", 3)));
Map<String, Integer> map =
pojos.stream().collect(Collectors.groupingBy(
Pojo::getIdentifier, Collectors.summingInt(Pojo::getValue)));
Output
{A=3, B=6, C=4}

Java 8 Map a List to another list and count

I am looking to create a list of history values for an existing list so that I can save it in DB to be displayed later in a table
Class Data {
Date date;
int int1;
int int2;
}
class DataHistory {
Date date;
int sum_Int1_beforeOrEqualDate;
int sum_Int2_beforeOrEqualDate;
String someOtherValues;
}
For example I have several lines perDate with all values. What I would like to achieve is :
My input :
date, int1, int2
01/01/18, 2, 3
01/01/18, 0, 1
02/01/18, 0, 1
02/01/18, 3, 0
03/01/18, 1, 3
...
My output :
date, sum_Int1_beforeOrEqualDate, sum_Int2_beforeOrEqualDate
01/01/18, 2, 4
02/01/18, 3, 1
03/01/18, 1, 3
...
I have tried several things, mainly with Map, but has never been able to do it with List-->List.
What I have tried to do is :
Edit: My lastAttempt, which clearly shows I don't know what i am doing..
List<OutputList> outputList =
inputlist
.stream()
.map( e -> new DataHistory())
.collect(Collectors.groupingBy(int1));
I believe you're trying to simply sum the values grouping by date. So assuming you have parsed data as a List
List<Data> list = getDataAsList();
List<DataHistory> historyList = list.stream()
.collect(Collectors.groupingBy(data -> data.date)).entrySet().stream()
.map((entry) -> {
DataHistory history = new DataHistory();
history.date = entry.getKey();
List<Data> dataList = entry.getValue();
history.sum_Int1_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int1).sum();
history.sum_Int2_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int2).sum();
return history;
})
.collect(Collectors.toList());
Tell me if I got the logic correct.
What you could do is use Collections.reducing which works pretty good.
List<DataHistory> dataHistories =
list.stream()
.collect(Collectors.groupingBy(Data::getDate,
Collectors.reducing(DataHistory::new,
DataHistoryHelper::merge)))
.values();
This solution assumes you have a constructor in DataHistory taking a Data as parameter.
public DataHistory(Data o) {
this.date = o.getDate();
// and so on
}
And that you have a method (anywhere) that takes care of merging two DataHistory objects
public DataHistory merge(DataHistory o1, DataHistory o2) {
DataHistory merged = new DataHistory();
merged.setSum_Int1_beforeOrEqualDate(o1.getSum_Int1_beforeOrEqualDate + o2.getSum_Int1_beforeOrEqualDate);
// and so on
return merged;
}
You can accomplish the task at hand using the toMap collector:
Collection<DataHistory> resultSet =
myList.stream()
.collect(Collectors.toMap(Data::getDate,
e -> new DataHistory(e.getDate(), e.getInt1(), e.getInt2(), null),
DataHistory::merge)).values();
This assumes you have a constructor defined as follows in your DataHistory class:
public DataHistory(Date date, int sum_Int1_beforeOrEqualDate,
int sum_Int2_beforeOrEqualDate, String someOtherValues) {
this.date = date;
this.sum_Int1_beforeOrEqualDate = sum_Int1_beforeOrEqualDate;
this.sum_Int2_beforeOrEqualDate = sum_Int2_beforeOrEqualDate;
this.someOtherValues = someOtherValues;
}
and a merge function defined as such:
public DataHistory merge(DataHistory other){
this.sum_Int1_beforeOrEqualDate += other.getSum_Int1_beforeOrEqualDate();
this.sum_Int2_beforeOrEqualDate += other.getSum_Int2_beforeOrEqualDate();
return this;
}
in the DataHistory class.
Further, if you explicitly require a List<DataHistory> as opposed to a Collection<DataHistory> then you can do:
List<DataHistory> historyList = new ArrayList<>(resultSet);
Note that I am passing null to the DataHistory constructor for the fourth parameter simply because I don't know what data to pass, so I'll leave that for you to decide upon.

Count and remove similar elements in a list while iterating through it

I used many references in the site to build up my program but I'm kind of stuck right now. I think using iterator will do the job. Sadly even though I went through questions which had iterator, I couldn't get the way of using it properly to implement it on my code.
I want to,
1. remove the similar elements found in the list fname
2. count & add the that count of each element found in fname to
counter.
Please help me do the above using iterator or with any other method. Following is my code,
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split(""))); //Assigning the string to a list//
int count = 1;
ArrayList<Integer> counter = new ArrayList<>();
List<String> holder = new ArrayList<>();
for(int element=0; element<=fname.size; element++)
{
for(int run=(element+1); run<=fname.size; run++)
{
if((fname.get(element)).equals(fname.get(run)))
{
count++;
holder.add(fname.get(run));
}
counter.add(count);
}
holder.add(fname.get(element));
fname.removeAll(holder);
}
System.out.println(fname);
System.out.println(counter);
Thanks.
From your questions, you basically want to:
1. Eliminate duplicates from given String List
You can simply convert your List to HashSet (it doesn't allow duplicates) and then convert it back to list (if you want the end result to be a List so you can do something else with it...)
2. Count all occurences of unique words in your list
The fastest coding is to use Java 8 Streams (code borrowed frome here: How to count the number of occurrences of an element in a List)
Complete code
public static void main(String[] args) {
String fullname = "a b c d a b c"; //something
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split(" ")));
// Convert input to Set, and then back to List (your program output)
Set<String> uniqueNames = new HashSet<>(fname);
List<String> uniqueNamesInList = new ArrayList<>(uniqueNames);
System.out.println(uniqueNamesInList);
// Collects (reduces) your list
Map<String, Long> counts = fname.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(counts);
}
I do not think that you need iterators here. However, there are many other possible solutions you could use, like recursion. Nevertheless, I have just modified your code as the following:
final List<String> fname = new ArrayList<String>(Arrays.asList(fullname.split("")));
// defining a list that will hold the unique elements.
final List<String> resultList = new ArrayList<>();
// defining a list that will hold the number of replication for every item in the fname list; the order here is same to the order in resultList
final ArrayList<Integer> counter = new ArrayList<>();
for (int element = 0; element < fname.size(); element++) {
int count = 1;
for (int run = (element + 1); run < fname.size(); run++) {
if ((fname.get(element)).equals(fname.get(run))) {
count++;
// we remove the element that has been already counted and return the index one step back to start counting over.
fname.remove(run--);
}
}
// we add the element to the resulted list and counter of that element
counter.add(count);
resultList.add(fname.get(element));
}
// here we print out both lists.
System.out.println(resultList);
System.out.println(counter);
Assuming String fullname = "StringOfSomeStaff"; the output will be as the following:
[S, t, r, i, n, g, O, f, o, m, e, a]
[3, 2, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1]
You can try something like this:
Set<String> mySet = new HashSet<>();
mySet.addAll( fname ); // Now you have unique values
for(String s : mySet) {
count = 0;
for(String x : fname) {
if( s.equals(x) ) { count++; }
}
counter.add( count );
}
This way we don't have a specific order. But I hope it helps.
In Java 8, there's a one-liner:
List<Integer> result = fname
.stream()
.collect(Collectors.groupingBy(s -> s))
.entrySet()
.stream()
.map(e -> e.getValue().size())
.collect(Collectors.toList());
I was using LinkedHashMap to preserve order of elements. Also for loop, which I am using, implicitly uses Iterator. Code example is using Map.merge method, which is available since Java 8.
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split("")));
/*
Create Map which will contain pairs kay=values
(in this case key is a name and value is the counter).
Here we are using LinkedHashMap (instead of common HashMap)
to preserve order in which name occurs first time in the list.
*/
Map<String, Integer> countByName = new LinkedHashMap<>();
for (String name : fname) {
/*
'merge' method put the key into the map (first parameter 'name').
Second parameter is a value which we that to associate with the key
Last (3rd) parameter is a function which will merge two values
(new and ald) if map already contains this key
*/
countByName.merge(name, 1, Integer::sum);
}
System.out.println(fname); // original list [a, d, e, a, a, f, t, d]
System.out.println(countByName.values()); // counts [3, 2, 1, 1, 1]
System.out.println(countByName.keySet()); // unique names [a, d, e, f, t]
Also same might be done using Stream API but it would be probably hard for understanding if you are not familiar with Streams.
Map<String, Long> countByName = fname.stream()
.collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));

How to use Java streams for the filtering

I have a list of object which has 3 variables(id, version, root_id)
Eg : {(1, 3, 1001),(2,2,1001), (3,1,1001), (4,1,1002), (5,1,1003)}
I want to retain only 1 object having same root_id and having highest version number.
output : {(1, 3, 1001),(4,1,1002), (5,1,1003)}
How can I apply the java stream filter on the list to get the desired output.
Please help. I am getting a bit confused on applying the filter.
you need to group by rootId and take the max version by comparing int value.
maxBy returns Optional data, to unwrap the actaul data collectingAndThen is used
public static void main(String[] args) {
List<Data> objects = Arrays.asList(new Data(1, 3, 1001), new Data(2, 2, 1001), new Data(3, 1, 1001),
new Data(4, 1, 1002), new Data(5, 1, 1003));
Map<Integer, Data> filtered = objects.stream().collect(Collectors.groupingBy(Data::getRootId, Collectors
.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Data::getVersion)), Optional::get)));
System.out.println(filtered.values());
}
static class Data {
int id;
int version;
int rootId;
//getter,setter & constructors
//toString
}
output
[Data [id=1, version=3, rootId=1001], Data [id=4, version=1, rootId=1002], Data [id=5, version=1, rootId=1003]]
You can use
.stream().collect(Collectors.groupingBy(Class::property));
.stream().filter((item) -> {
System.out.println(item); //this is element from your collection
boolean isOk = true; //change true to your conditional
return isOk;
})

Categories

Resources