Aggregating a Map object within another Object - java

Edited version! From a PC instead of my phone.
I have a Class defined with following attributes:
Here is the code I have for a sample Map without being part of another class:
List<Map<String,Long>> amountList = new ArrayList<>();
Map<String, Long> amountMap = new HashMap<>();
for(int i=0; i<2;i++ ) {
amountMap.put("AMOUNT1", 12L);
amountMap.put("AMOUNT2", 10L);
amountMap.put("AMOUNT3", 10L);
amountMap.put("AMOUNT4", 12L);
amountMap.put("AMOUNT5", 10L);
amountList.add(amountMap);
}
Map<String, Long> collectset = amountList.stream()
.flatMap(entry -> entry.entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum));
for (String str : collectset.keySet()){
System.out.println( "output: " + str + " -> " + collectset.get(str));
}
I need a result from this where the output is just as below:
output: AMOUNT3 -> 20
output: AMOUNT2 -> 20
output: AMOUNT1 -> 24
output: AMOUNT5 -> 20
output: AMOUNT4 -> 24
What I get as a result of the code above is that the values are repeating twice.
Is there a way to only output the Sum equivalent once. For instance, if the loop is changed to produce 5 Maps - I see the output printed 5 times.

Make an info object containing three strings and use that as your key value (don't forget to override hashCode if needed). Or simply use a format (such as CSV) to concatinate your strings together and then use that string as a key.

I was able to find the issue. There was a for loop just before the Stream implementation which caused to print the output based on how many times I was looping through the loop.
Here is the updated code:
List<Map<String,Long>> countList = new ArrayList<>();
Map<String, Long> countMap = new HashMap<>();
Random random = new Random();
for(int i=0; i<500;i++ ) {
countMap.put("COUNT" + random.nextInt(10), 12L);
countMap.put("COUNT" + random.nextInt(10), 10L);
countMap.put("COUNT" + random.nextInt(10), 10L);
countMap.put("COUNT" + random.nextInt(10), 12L);
countMap.put("COUNT" + random.nextInt(10), 10L);
countList.add(countMap);
}
Map<String, Long> collectset = countList.stream()
.flatMap(entry -> entry.entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum));
System.out.println( "CollectSet Size: " + collectset.size());

Related

Java Stream - groupingBy() and counting() when a curtain Condition is met

Given the following class Test
class Test {
String testName;
String studName;
String status;
}
and a list of tests
List<Test> tests = List.of(
new Test("English", "John", "passed"),
new Test("English", "Dave", "passed"),
new Test("Science", "Alex", "failed"),
new Test("Science", "Jane", "failed"),
new Test("History", "Dave", "passed"),
new Test("Mathematics", "Anna", "passed"),
new Test("Mathematics", "Lisa", "passed"),
new Test("Mathematics", "Paul", "failed"),
new Test("Geography", "Mark", "passed"),
new Test("Physics", "John", "failed"));
I need to group by testName and count only where status equals "passed". I need to do the equivalent of below code with streams :
Map<String, Long> result2 = new HashMap<>();
for (Test t : tests) {
result2.putIfAbsent(t.getTestName(), 0L);
if (t.getStatus().equals("passed")) {
result2.computeIfPresent(t.getTestName(), (k, v) -> v + 1);
}
}
The correct and desired output:
{Geography=1, English=2, Science=0, Mathematics=2, History=1, Physics=0}
I'm looking for a stream approach, but couldn't find a solution yet. A simple Collectors.counting will count all, regardless of status "failed/passed":
Map<String, Long> resultCounting = tests.stream()
.collect(Collectors.groupingBy(
Test::getTestName,
Collectors.counting()
));
Output:
{Geography=1, English=2, Science=2, Mathematics=3, History=1, Physics=1}
I thought about filtering beforehand, but then I will loose those subjects where all statuses are "failed".
Map<String, Long> resultFilter = tests.stream()
.filter(t -> t.getStatus().equals("passed"))
.collect(Collectors.groupingBy(
Test::getTestName,
Collectors.counting()
));
Output:
{Geography=1, English=2, Mathematics=2, History=1}
How can I group all tests by testName, but count only those where status is "passed" ?
Is it possible to wrap Collectors.counting() in some kind of condition?
You can achieve the desired result by using collector toMap(keyMapper,valueMapper,mergeFunction).
valueMapper function would either produce 1 or 0 depending on on the status.
Map<String, Integer> passCountByTestName = tests.stream()
.collect(Collectors.toMap(
Test::getTestName,
test -> test.getStatus().equals("passed") ? 1 : 0,
Integer::sum
));
passCountByTestName.forEach((k, v) -> System.out.println(k + " -> " + v));
Output:
Geography -> 1
English -> 2
Science -> 0
Mathematics -> 2
History -> 1
Physics -> 0
Sidenote: it would be better to use boolean or enum as type for the status property instead of relying on string values.

Mapping List objects using lambdas and streams

To start with, I have the following list of invoices. Each list object has a part number, a description, quantity and a price.
Invoice[] invoices = new Invoice[8];
invoices[0] = new Invoice("83","Electrische schuurmachine",7,57.98);
invoices[1] = new Invoice("24","Power zaag", 18, 99.99);
invoices[2] = new Invoice("7","Voor Hamer", 11, 21.50);
invoices[3] = new Invoice("77","Hamer", 76, 11.99);
invoices[4] = new Invoice("39","Gras maaier", 3, 79.50);
invoices[5] = new Invoice("68","Schroevendraaier", 16, 6.99);
invoices[6] = new Invoice("56","Decoupeer zaal", 21, 11.00);
invoices[7] = new Invoice("3","Moersleutel", 34, 7.50);
List<Invoice> list = Arrays.asList(invoices);
What's asked: Use lambdas and streams to map every Invoice on PartDescription and Quantity, sort by Quantity and show the results.
So what I do have now:
list.stream()
.map(Invoice::getQuantity)
.sorted()
.forEach(System.out::println);
I mapped it on quantity and sorted it on quantity as well and I get below results:
3
7
11
16
18
21
34
76
But how do I map on PartDescription as well, so that's showed in my results in front of the shown quantities too? I can't do this:
list.stream()
.map(Invoice::getPartDescription)
.map(Invoice::getQuantity)
.sorted()
.forEach(System.out::println);
You don't use map. You sort the original Stream of Invoices, and then print whatever properties you wish.
list.stream()
.sorted(Comparator.comparing(Invoice::getQuantity))
.forEach(i -> System.out.println(i.getgetQuantity() + " " + i.getPartDescription()));
EDIT: If you want to sort by quantity * price:
list.stream()
.sorted(Comparator.comparing(i -> i.getQuantity() * i.getPrice()))
.forEach(i -> System.out.println(i.getgetQuantity() * i.getPrice() + " " + i.getPartDescription()));

How to convert HashMap(String,ArrayList)to 3d array?

My output in Hashmap> is:
House cleaning = [Jack, Maria]
Computer lessons = [Leon, Maria]
Data recovery service = [Leon]
Computer repair = [Jack, Leon]
Handyman = [Jack]
Expected Output is
[[["Computer lessons"],["Leon","Maria"]],
[["Computer repair"],["Jack","Leon"]],
[["Data recovery service"],["Leon"]],
[["Handyman"],["Jack"]],
[["House cleaning"],["Jack","Maria"]]]
Order doesn't matter.
Try this.
Map<String, List<String>> map = new HashMap<>();
map.put("House cleaning", Arrays.asList("Jack", "Maria"));
map.put("Computer lessons", Arrays.asList("Leon", "Maria"));
map.put("Data recovery service", Arrays.asList("Leon"));
map.put("Computer repair", Arrays.asList("Jack", "Leon"));
map.put("Handyman", Arrays.asList("Jack"));
String[][][] result = map.entrySet().stream()
.map(e -> new String[][] {
new String[] {e.getKey()},
e.getValue().toArray(new String[0])})
.toArray(String[][][]::new);
for (String[][] row : result)
System.out.println(Arrays.deepToString(row));
result is
[[House cleaning], [Jack, Maria]]
[[Computer lessons], [Leon, Maria]]
[[Data recovery service], [Leon]]
[[Computer repair], [Jack, Leon]]
[[Handyman], [Jack]]

How to generate HBase java Put class from String Array?

I am newbie on Java 8 lambda and stream programming. This is simple source which generate HBase Put class from String array.
List<String> list = new ArrayList<>(5);
list.add("1," + "colFamily" + ",a,1");
list.add("2," + "colFamily" + ",a,2");
list.add("3," + "colFamily" + ",a,3");
list.add("4," + "colFamily" + ",a,4");
list.add("5," + "colFamily" + ",a,5");
for (int i=0 ; i<list.size() ; i++) {
String[] cells = list.get(i).split(",");
Put put = new Put(Bytes.toBytes(cells[0]));
put.addColumn(Bytes.toBytes(cells[1]),Bytes.toBytes(cells[2]),Bytes.toBytes(cells[3]));
System.out.println(put);
Results are generated correctly like below,
{"totalColumns":1,"row":"1","families":{"colFamily":[{"qualifier":"a","vlen":1,"tag":[],"timestamp":9223372036854775807}]}}
{"totalColumns":1,"row":"2","families":{"colFamily":[{"qualifier":"a","vlen":1,"tag":[],"timestamp":9223372036854775807}]}}
{"totalColumns":1,"row":"3","families":{"colFamily":[{"qualifier":"a","vlen":1,"tag":[],"timestamp":9223372036854775807}]}}
{"totalColumns":1,"row":"4","families":{"colFamily":[{"qualifier":"a","vlen":1,"tag":[],"timestamp":9223372036854775807}]}}
{"totalColumns":1,"row":"5","families":{"colFamily":[{"qualifier":"a","vlen":1,"tag":[],"timestamp":9223372036854775807}]}}
But by using Java 8 stream, I failed to generate the same results, below is the codes.
System.out.println(list.stream().collect(Collectors.mapping(l -> new Put(Bytes.toBytes(l)), Collectors.toList())));
But definitely the above code brings the wrong results.
[{"totalColumns":0,"row":"1,colFamily,a,1","families":{}}, {"totalColumns":0,"row":"2,colFamily,a,2","families":{}}, {"totalColumns":0,"row":"3,colFamily,a,3","families":{}}, {"totalColumns":0,"row":"4,colFamily,a,4","families":{}}, {"totalColumns":0,"row":"5,colFamily,a,5","families":{}}]
I have no idea how to split comma-seperated-Strings to String array using java 8 stream function.
How about this:
list
.stream()
.map(s -> s.split(","))
.map(cells -> {
Put put = new Put(Bytes.toBytes(cells[0]));
put.addColumn(Bytes.toBytes(cells[1]), Bytes.toBytes(cells[2]), Bytes.toBytes(cells[3]));
return put;
})
.forEach(System.out::println) // or .collect to list

How do I add up BigDecimals contained in a HashMap with Java 8?

What is the easiest way to add to a BigDecimal contained in a HashMap in Java 8?
Prior to Java 8, it would be:
public static void main(String[] args) {
HashMap<String, BigDecimal> bd_map = new HashMap<>();
bd_map.put("Shirts", BigDecimal.ZERO);
bd_map.put("Hats", BigDecimal.ZERO);
bd_map.put("Shoes", BigDecimal.ZERO);
bd_map.put("Shirts", bd_map.get("Shirts").add(new BigDecimal("5.99")));
bd_map.put("Shirts", bd_map.get("Shirts").add(new BigDecimal("4.50")));
bd_map.put("Shoes", bd_map.get("Shoes").add(new BigDecimal("15.99")));
bd_map.put("Hats", bd_map.get("Hats").add(new BigDecimal("8.00")));
bd_map.put("Shirts", bd_map.get("Shirts").add(new BigDecimal("8.99")));
bd_map.put("Shoes", bd_map.get("Shoes").add(new BigDecimal("22.00")));
bd_map.put("Hats", bd_map.get("Hats").add(new BigDecimal("7.00")));
System.out.println("Shirts: " + bd_map.get("Shirts"));
System.out.println("Hats: " + bd_map.get("Hats"));
System.out.println("Shoes: " + bd_map.get("Shoes"));
}
However, Java 8 makes this much easier and less error-prone with the merge() function:
public static void main(String[] args) {
HashMap<String, BigDecimal> bd_map = new HashMap<>();
bd_map.merge("Shirts", new BigDecimal("5.99"), BigDecimal::add);
bd_map.merge("Shirts", new BigDecimal("4.50"), BigDecimal::add);
bd_map.merge("Shoes", new BigDecimal("15.99"), BigDecimal::add);
bd_map.merge("Hats", new BigDecimal("8.00"), BigDecimal::add);
bd_map.merge("Shirts", new BigDecimal("8.99"), BigDecimal::add);
bd_map.merge("Shoes", new BigDecimal("22.00"), BigDecimal::add);
bd_map.merge("Hats", new BigDecimal("7.00"), BigDecimal::add);
System.out.println("Shirts: " + bd_map.get("Shirts"));
System.out.println("Hats: " + bd_map.get("Hats"));
System.out.println("Shoes: " + bd_map.get("Shoes"));
}
Advantages to the Java 8 approach:
No need to initialize the original value (BigDecimal.ZERO)
No need to refer to the old value (HashMap::get) and add it
It's clean

Categories

Resources