LINQ has Enuemerable.Select, does Java have any equivalent?
Say I have some class Record with three fields id, price, itemsSold and a list of these records from which I want to filter out the most popular item measured by which product sold the most items.
In LINQ I could do something like this
var mostPopularItem = records.GroupBy(sr => sr.Id)
.Select(g => new
{
Id = g.Key,
TotalSold = g.Sum(r => r.ItemsSold)
})
.OrderByDescending(i => i.TotalSold).First();
by using the Select(...)
method to reconstruct into a suitable form. How would I do the same in Java? I could simply use streams to extract the actual number
records.stream().map(r -> r.getItemsSold()).sorted(Comparator.reverseOrder()).collect(Collectors.toList)).get(0)
but this would only give me an array of the items sold sorted in descending order. Optimally I would like to end up with an object that contains the id and the itemsSold.
CodePudding user response:
List<Record> recordList = new ArrayList<>();
recordList.add(new Record(1L, new BigDecimal(2), 4));
recordList.add(new Record(1L, new BigDecimal(2), 5));
recordList.add(new Record(1L, new BigDecimal(2), 7));
recordList.add(new Record(2L, new BigDecimal(2), 10));
Map.Entry<Long, Integer> mostPopularItem = recordList.stream().collect(groupingBy(Record::getId, summingInt(Record::getItemsSold))).entrySet().stream().max(Map.Entry.comparingByValue()).orElse(null);
Output:
Key = 1, Value = 16
public class Record {
public Record(Long id, BigDecimal price, Integer itemsSold) {
this.id = id;
this.price = price;
this.itemsSold = itemsSold;
}
private Long id;
private BigDecimal price;
private Integer itemsSold;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getItemsSold() {
return itemsSold;
}
public void setItemsSold(Integer itemsSold) {
this.itemsSold = itemsSold;
}
}
CodePudding user response:
This should return the most popular item:
var itemsSoldById = records.stream()
.collect(groupingBy(rec -> rec.getId(), summingInt(rec -> rec.getItemsSold())));
var mostPopularItem = itemsSoldById
.entrySet()
.stream()
.max(Comparator.comparingInt(Map.Entry::getValue))
.orElseThrow(()-> new IllegalStateException("The list of records is empty"));
Previous version before my edit that did not group records by id:
var mostPopularItem = records.stream()
.sorted(Comparator.comparing(rec -> rec.getItemsSold()))
.reduce((rec1 , rec2) -> rec1.getItemsSold() > rec2.getItemsSold() ? rec1 : rec2)
.orElseThrow(()-> new IllegalStateException("The list of records is empty"));
CodePudding user response:
Something like this?
package example.stackoverflow;
import java.util.List;
import java.util.stream.Collectors;
public class Select {
public static void main(String[] args) {
record Thing(String id, int price, int itemsSold){}
List<Thing> records = List.of(
new Thing("a", 5, 14),
new Thing("b", 4, 33),
new Thing("c", 6, 10),
new Thing("a", 5, 21),
new Thing("c", 6, 12)
);
record PopularThing(String id, int totalSold){}
records.stream()
.collect(Collectors.groupingBy(Thing::id, Collectors.summingInt(Thing::itemsSold)))
.entrySet().stream().map(e -> new PopularThing(e.getKey(), e.getValue()))
.max((a,b) -> a.totalSold - b.totalSold)
.ifPresent(System.out::println);
}
}
CodePudding user response:
The equivalent of Select
in Java is the map
-function on Stream
. But in your case, you probably want to use the second parameter of groupingBy
-collector to sum up the ItemsSold
-values.
Java doesn't have is anonymous objects, like you create in your Select
-call. So you would need to define a class or record to hold that data.
Using records, you could do this:
record TotalSoldRecord(String id, int totalSold) {}
var mostPopularItem = records.stream()
// Grouping by ID into a Map<String, int>, where the keys are
// the recordIds and the values are the sum of "itemsSold".
.collect(Collectors.groupingBy(Record::getId,
Collectors.summingInt(Record::getItemsSold)))
.entrySet()
.stream()
.map(entry -> new TotalSoldRecord(entry.getKey(), entry.getValue()))
// Finding the TotalSoldRecord with the maximum totalSold.
.max(Comparator.comparingInt(TotalSoldRecord::totalSold));
Note: You could also use Map.Entry
as the result instead of mapping it to TotalSoldRecord
.