Mapping List objects using lambdas and streams - java

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

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.

grouping values with diferent values in columns using java lambda

I have this Object:
QuoteProductDTO with three columns ( name, value1, value2)
List<QuoteProductDTO> lstQuoteProductDTO = new ArrayList<>();
lstQuoteProductDTO.add( new QuoteProductDTO("product", 10, 15.5) );
lstQuoteProductDTO.add( new QuoteProductDTO("product", 05, 2.5) );
lstQuoteProductDTO.add( new QuoteProductDTO("product", 13, 1.0) );
lstQuoteProductDTO.add( new QuoteProductDTO("product", 02, 2.0) );
I need to get a consolidate ( a new object QuoteProductDTO ):
the firts column name,I have to get the first value "product".
the second one (value1) I have to get the biggest value 13.
and third column I heve to get the sum of all values 20.
This takes the current data provided and generates a new object with the required data. It uses the Collectors.teeing() method of Java 12+
Given the following data:
ArrayList<QuoteProductDTO> lstQuoteProductDTO = new ArrayList<>();
ArrayList<QuoteProductDTO> nextQuoteProductDTO = new ArrayList<>();
// empty Quote for Optional handling below.
QuoteProductDTO emptyQuote = new QuoteProductDTO("EMPTY", -1, -1);
lstQuoteProductDTO.add(
new QuoteProductDTO("Product", 10, 15.5));
lstQuoteProductDTO.add(
new QuoteProductDTO("Product", 05, 2.5));
lstQuoteProductDTO.add(
new QuoteProductDTO("Product", 13, 1.0));
lstQuoteProductDTO.add(
new QuoteProductDTO("Product", 02, 2.0));
You can consolidate like you want into a new instance of QuoteProductDTO.
QuoteProductDTO prod = lstQuoteProductDTO.stream()
.collect(Collectors.teeing(
Collectors.maxBy(Comparator
.comparing(p -> p.value1)),
Collectors.summingDouble(
p -> p.value2),
(a, b) -> new QuoteProductDTO(
a.orElse(emptyQuote).name,
a.orElse(emptyQuote).value1,
b.doubleValue())));
System.out.println(prod);
Prints
Product, 13, 21.0
You can also take a list of lists of different products and put them in a list of consolidated products. Add the following to a new list and then add those to a main list.
nextQuoteProductDTO.add(
new QuoteProductDTO("Product2", 10, 15.5));
nextQuoteProductDTO.add(
new QuoteProductDTO("Product2", 25, 20.5));
nextQuoteProductDTO.add(
new QuoteProductDTO("Product2", 13, 1.0));
nextQuoteProductDTO.add(
new QuoteProductDTO("Product2", 02, 2.0));
List<List<QuoteProductDTO>> list = List.of(
lstQuoteProductDTO, nextQuoteProductDTO);
Now consolidate those into a list of objects.
List<QuoteProductDTO> prods = list.stream().map(lst -> lst.stream()
.collect(Collectors.teeing(
Collectors.maxBy(Comparator
.comparing(p -> p.value1)),
Collectors.summingDouble(
p -> p.value2),
(a, b) -> new QuoteProductDTO(
a.orElse(emptyQuote).name,
a.orElse(emptyQuote).value1,
b.doubleValue()))))
.collect(Collectors.toList());
prods.forEach(System.out::println);
Prints
Product, 13, 21.0
Product2, 25, 39.0
I created a class to help demonstrate this.
class QuoteProductDTO {
public String name;
public int value1;
public double value2;
public QuoteProductDTO(String name, int value1,
double value2) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
}
public String toString() {
return name + ", " + value1 + ", " + value2;
}
}

Aggregating a Map object within another Object

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

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

Check a Java list and create a new one according to some criteria

I have a list with "Booking" Objects. "Booking" has these attributes:
- Long roomId. Date fecha. Long order. Double price.
On a certain moment, this list could be:
1 -- 22/07/2016 -- 15
1 -- 23/07/2016 -- 15
1 -- 24/07/2016 -- 15
4 -- 01/08/2016 -- 25
4 -- 02/08/2016 -- 25
4 -- 03/08/2016 -- 25
4 -- 04/08/2016 -- 25
It means, there is a Booking between July 22 and 24 for room 1, total value 45.
There is a Booking between August 1 and 4 for room 4, value 100.
I want to make a new list of "OrderDetail" objects. "OrderDetail" object would have these attributes:
- roomId, InitialDate, FinalDate, price
So, with my list, It would create two OrderDetail objects and it would add to an OrderDetail list.
These objects would be:
roomId = 1, InitialDate = 22/07/2016, FinalDate = 24/07/206, price = 45
roomId = 4, InitialDate = 01/08/2016, FinalDate = 04/08/2016, price = 100.
Could someone help me? I think it´s not a difficult code but I do not usually program so I´m having some problems to make it work.
This is my shitty code:
L1 = (this is a database query)
L2 = (this is the same query) (so I have two identical lists)
List<OrderDetail> L3 = new ArrayList<OrderDetail>();
Long roomId = null;
Date InititalDate;
Date FinalDate;
double price = 0;
for (int i = 0; i < L1.size(); i++) {
InititalDate = null;
Booking current = L1.get(i);
roomId = current.getRoomId();
InititalDate = current.getFecha();
Iterator<Booking> it = L2.iterator();
while (it.hasNext()) {
Booking current2 = it.next();
if (current2.getRoomId.equals(roomId)) {
precio = precio + current2.getPrecio();
FinalDate = current2.getFecha();
i++;
}
}
OrderDetail = new OrderDetail(roomId, InitialDate, FinalDate, precio);
L3.add(OrderDetail);
}
return L3;
}
I am learning Java8, just tried to implement what you have asked, with streams
Booking b1 = new Booking(1L, LocalDate.of(2016, 7, 22), 15d);
Booking b2 = new Booking(1L, LocalDate.of(2016, 7, 23), 15d);
Booking b3 = new Booking(1L, LocalDate.of(2016, 7, 24), 15d);
Booking b4 = new Booking(4L, LocalDate.of(2016, 8, 1), 25d);
Booking b5 = new Booking(4L, LocalDate.of(2016, 8, 2), 25d);
Booking b6 = new Booking(4L, LocalDate.of(2016, 8, 3), 25d);
Booking b7 = new Booking(4L, LocalDate.of(2016, 8, 4), 25d);
List<Booking> bookings = Arrays.asList(b1, b2, b3, b4, b5, b6, b7);
List<OrderDetail> orderDetails = bookings
.stream().collect(Collectors.groupingBy(Booking::getRoomId)).values().stream().map(i -> new OrderDetail(i.get(0).getRoomId(),
i.get(0).getBookedDate(), i.get(i.size() - 1).getBookedDate(), i.stream().collect(Collectors.summingDouble(Booking::getPrice))))
.collect(Collectors.toList());
System.out.println(orderDetails);
output
[OrderDetail [roomId=1, startDate=2016-07-22, endDate=2016-07-24, totalPrice=45.0], OrderDetail [roomId=4, startDate=2016-08-01, endDate=2016-08-04, totalPrice=100.0]]
Please note : I believe there might be a better way of implementing this, please add your answer happy to learn from that

Categories

Resources