I have a List of Student and each Student may register for a couple of subjects.
Therefore each Student will have a List of Subject. I would like to do a groupingBy on Subject using java 8 features.
I am not able to figure out a way. Any help will be appreciated.
This is just an example where groupBy is used. You can find many other examples on web. [This example is taken from here]
Group items by price: Collectors.groupingBy and Collectors.mapping example
public static void main(String[] args) {
//3 apple, 2 banana, others 1
List<Item> items = Arrays.asList(
new Item("apple", 10, new BigDecimal("9.99")),
new Item("banana", 20, new BigDecimal("19.99")),
new Item("orang", 10, new BigDecimal("29.99")),
new Item("watermelon", 10, new BigDecimal("29.99")),
new Item("papaya", 20, new BigDecimal("9.99")),
new Item("apple", 10, new BigDecimal("9.99")),
new Item("banana", 10, new BigDecimal("19.99")),
new Item("apple", 20, new BigDecimal("9.99"))
);
//group by price
Map<BigDecimal, List<Item>> groupByPriceMap =
items.stream().collect(Collectors.groupingBy(Item::getPrice));
System.out.println(groupByPriceMap);
// group by price, uses 'mapping' to convert List<Item> to Set<String>
Map<BigDecimal, Set<String>> result =
items.stream().collect(
Collectors.groupingBy(Item::getPrice,
Collectors.mapping(Item::getName, Collectors.toSet())
)
);
System.out.println(result);
}
Output
{
19.99=[
Item{name='banana', qty=20, price=19.99},
Item{name='banana', qty=10, price=19.99}
],
29.99=[
Item{name='orang', qty=10, price=29.99},
Item{name='watermelon', qty=10, price=29.99}
],
9.99=[
Item{name='apple', qty=10, price=9.99},
Item{name='papaya', qty=20, price=9.99},
Item{name='apple', qty=10, price=9.99},
Item{name='apple', qty=20, price=9.99}
]
}
//group by + mapping to Set
{
19.99=[banana],
29.99=[orang, watermelon],
9.99=[papaya, apple]
}
One of my friend suggested this solution and it worked fine
package grpBy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Pair {
Subject sub1;
Student student;
public Pair(Student student, Subject sub1 ) {
this.sub1 = sub1;
this.student = student;
}
public String getSub1() {
return sub1.name;
}
public String getStudent() {
return student.name;
}
static Pair of(Student stu, Subject sub) {
return new Pair( stu, sub);
}
}
public class Test2 {
public static void main(String[] args) {
Subject maths = new Subject("maths", 1);
Subject chemi = new Subject("chemi", 1);
Subject phy = new Subject("phy", 1);
Subject bio = new Subject("bio", 1);
List<Subject> s1 = new ArrayList<>();
s1.add(maths);
s1.add(chemi);
List<Subject> s2 = new ArrayList<>();
s2.add(maths);
s2.add(phy);
List<Subject> s3 = new ArrayList<>();
s3.add(bio);
s3.add(phy);
Student jack = new Student(1, "jack", s1);
Student jil = new Student(2, "jil", s2);
Student john = new Student(3, "john", s3);
List<Student> students = new ArrayList();
students.add(jack);
students.add(jil);
students.add(john);
Map<String, List<String>> m = students.stream().
flatMap(student -> student.subjects.stream().map(subject -> Pair.of(student, subject))).
collect(Collectors.groupingBy(e -> e.getSub1(),
Collectors.mapping(e -> e.getStudent(),
Collectors.toList())));
System.out.println(m);
}
}
Related
I am looking forward to get a linq query for populating list of teachers and their respective divisons.
Here I have 2 classes Teacher and Division which are related by DivisionGroupID - GroupID
public class Teacher
{
public int ID { get; set; }
public string Name { get; set; }
public List<Division> lstDivison {get;set;}
public int DivisionGroupID { get; set; }
}
public class Division
{
public int GroupID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
In main method List of both Teacher and Division will be populated
static void Main(string[] args)
{
Teacher obj = new Teacher { ID = 1, DivisionGroupID = 11, Name = "abcd" };
Teacher obj1 = new Teacher { ID = 2, DivisionGroupID = 12, Name = "efgh" };
List<Teacher> objList = new List<Teacher>();
objList.Add(obj);
objList.Add(obj1);
Division dv = new Division { GroupID = 11 ,Name="Division1",Description="first" };
Division dv1 = new Division { GroupID = 11, Name = "Division2", Description = "second" };
Division dv2 = new Division { GroupID = 11, Name = "Division3", Description = "third" };
Division dv3 = new Division { GroupID = 12, Name = "Division4", Description = "fourth" };
Division dv4 = new Division { GroupID = 12, Name = "Division5", Description = "fifth" };
Division dv5 = new Division { GroupID = 12, Name = "Division6", Description = "sixth" };
List<Division> lstDiv = new List<Division>();
lstDiv.Add(dv);
lstDiv.Add(dv1);
lstDiv.Add(dv2);
lstDiv.Add(dv3);
lstDiv.Add(dv4);
lstDiv.Add(dv5);
}
The requirement here is to get the list of teachers and populate the sublist of divisions each teachers holding. I got the solution based on 2 approaches.
Using sub query approach :
var upd = from teacher in objList
select new Teacher
{
ID = teacher.ID,
Name = teacher.Name,
lstDivison = (from div in lstDiv
where div.GroupID == teacher.DivisionGroupID
select new Division
{
Name = div.Name,
Description = div.Description
}).ToList()
};
Using Foeach loop through Teacher collection(objList) and updating the lstDivision
objList.ForEach(x => x.lstDivison = lstDiv
.Where(y => y.GroupID == x.DivisionGroupID)
.Select(p => new Division { Name = p.Name, Description = p.Description })
.ToList());
Both of these approaches will give me the result. But i am looking forward a better approach in as part of my project requirement which has to improve the query performance. Could you please suggest which is the best approach to handle this situation?
use yours teacher object to populate list of divisions under it. as my understanding that how it was designed class structure.
//populate property in object
objList.ForEach(x => {
x.lstDivison = lstDiv.Where(w=> w.GroupID == x.DivisionGroupID).ToList();
});
objList.Dump();
double finalPrice = 0;
for(Map.Entry<Integer, Integer> eachBasketItemEntry:itemsInBasket.entrySet()){
Integer itemId = eachBasketItemEntry.getKey();
if(itemPricingRuleMap.containsKey(itemId)){
//calculate pricing based on pricing rule for that item code
finalPrice+=calculateItemPrice(itemId,eachBasketItemEntry.getValue(),itemPricingRuleMap);
}
else{
throw new IllegalArgumentException("No pricing rule regsitered for the item with item id"+itemId);
}
}
return finalPrice;
private double calculateItemPrice(Integer itemId, Integer itemsCount,Map<Integer, PricingRule> itemPricingRuleMap) {
PricingRule pricingRule = itemPricingRuleMap.get(itemId);
return pricingRule.calculatePrice(itemsCount);
}
How to convert this into java8 streams?
double a1 = itemsInBasket.entrySet().stream().forEach(eachBasketItemEntry->{
double finalPrice = 0;
Integer itemId = eachBasketItemEntry.getKey();
finalPrice+= calculateItemPrice(itemId,eachBasketItemEntry.getValue(),itemPricingRuleMap);
})
Tried doing this but Im unsure how to proceed from here..
Can any one help.
Im new to java 8 streams
Thank you.
You can refer the below sample code whenever some one wants to iterate over the collection and wants to get the reduced single value output.
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
Map<String,Integer> employeeSalaryMap = new HashMap<>();
employeeSalaryMap.put("emp1",10000);
employeeSalaryMap.put("emp2",20000);
employeeSalaryMap.put("emp3",30000);
employeeSalaryMap.put("emp4",40000);
employeeSalaryMap.put("emp5",50000);
int totalSalaryOfAllEmployees1 = employeeSalaryMap.values().stream().mapToInt(Integer::intValue).sum();
int totalSalaryOfAllEmployees2 = employeeSalaryMap.entrySet().stream().map(Map.Entry::getValue)
.reduce(Integer::sum).orElseThrow(Exception::new);
System.out.println("totalSalaryOfAllEmployees1:: " + totalSalaryOfAllEmployees1);
System.out.println("totalSalaryOfAllEmployees2:: " + totalSalaryOfAllEmployees2);
}
}
**Output**
totalSalaryOfAllEmployees1:: 150000
totalSalaryOfAllEmployees2:: 150000
Both the operations will give you the same result.
Recommended to use: mapToInt/mapToDouble based on the requirements.
Thanks
You can use .map() to map your result calculated from calculateItemPrice method and then use reduce() operation to get required result.
Wrote a sample example for explanation
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
class Main {
public static void main(String[] args) {
Map<Integer, Integer> itemsInBasket = new HashMap<Integer, Integer>();//Creating HashMap
itemsInBasket.put(1, 5); //Put elements in Map
itemsInBasket.put(2, 6);
itemsInBasket.put(3, 7);
itemsInBasket.put(4, 8);
int a1 = itemsInBasket
.entrySet()
.stream()
.map(e -> (e.getKey() + e.getValue()))
.reduce(Integer::sum).orElseThrow(NoSuchElementException::new);
System.out.println(a1);
}
}
For your case : below code can help
double a1 = itemsInBasket
.entrySet()
.stream()
.map(e->calculateItemPrice(e.getKey(), e.getValue(), itemPricingRuleMap))
.reduce(Double::sum).orElseThrow(NoSuchElementException::new);
EDIT : As #Holger Suggested it can be rewritten as
double a1 = itemsInBasket
.entrySet()
.stream()
.mapToDouble(e->calculateItemPrice(e.getKey(), e.getValue(), itemPricingRuleMap))
.sum().orElseThrow(NoSuchElementException::new);
Let me know in comments further if you face further issues.
Thanks.
I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}
I am trying to create a map from a Java stream. I am able to do this easily with javascript and am trying to create the same thing in Java. Here is my data structure:
var slips = [
{
original: 'Y',
lines: [
{
detailLines: {
detailLineId: 111
}
},
{
detailLines: {
detailLineId: 222
}
}
]
},
{
original: 'N',
lines: [
{
detailLines: {
detailLineId: 333
}
},
{
detailLines: {
detailLineId: 444
}
}
]
}
]
Here is how I did it in javascript
var test = slips.reduce((acc, slip) => {
slip.lines.map(line => line.detailLines.detailLineId)
.map(arr => acc[arr] = slip.original);
return acc;
}, {});
to get my result of
{
'111': 'Y',
'222': 'Y',
'333': 'N',
'444': 'N'
}
How do I do this using the Java 8 Stream api? The slips above really is just a POJO. I converted it to a JSON object to do figure it out in js. The real structure of the Objects are
class Slip {
private Boolean original;
private List<Line> lines;
}
class Line {
private List<DetailLine> detailLines;
}
class DetailLine {
private Long detailLine;
}
So what I have started with the Java is
Map<Long, Boolean> results = slips.stream().reduce(new Map<Long, Boolean>, ...)
For me, it looks like a line object contains a single detail line and not a list.
class Slip {
private Boolean original;
private List<Line> lines;
}
class Line {
private DetailLine detailLine;
}
class DetailLine {
private Long detailLineId;
}
Assuming each detail line id is unique, you might use flatMap to create the necessary mappings id -> Boolean, and simply collects them into a map.
import java.util.AbstractMap.SimpleEntry;
import static java.util.stream.Collectors.toMap;
...
Map<Long, Boolean> results =
slips.stream()
.flatMap(s -> s.getLines().stream().map(l -> new SimpleEntry<>(l.getDetailLine().getDetailLineId(), s.getOriginal())))
.collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue))
If you indeed have the structure you claimed, you should flatMap twice:
.flatMap(s -> s.getLines().stream().flatMap(l -> l.getDetailLine().stream().map(dl -> new SimpleEntry<>(dl.getDetailLineId(), s.getOriginal()))))
Here is a working example
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<DetailLine> detailedLines1 = new ArrayList<DetailLine>();
detailedLines1.add(new DetailLine(111l));
detailedLines1.add(new DetailLine(222l));
List<DetailLine> detailedLines2 = new ArrayList<DetailLine>();
detailedLines2.add(new DetailLine(333l));
detailedLines2.add(new DetailLine(444l));
Line line1 = new Line(detailedLines1);
Line line2 = new Line(detailedLines2);
List<Line> lines1 = new ArrayList<Line>();
lines1.add(line1);
List<Line> lines2 = new ArrayList<Line>();
lines2.add(line2);
List<Slip> slips = new ArrayList<Slip>();
slips.add(new Slip(true, lines1));
slips.add(new Slip(false, lines2));
Map<Long, Boolean> myResult = new HashMap<>();
slips.stream().map(
slip ->
slip.getLines().stream().map(
line -> line.getDetailLines().stream().map(deadLine -> deadLine.getDetailLine()).collect(Collectors.toList())
).flatMap(Collection::stream)
.map(l -> new AbstractMap.SimpleEntry<>(l, slip.getOriginal()))
).flatMap(l -> l).forEach(System.out::println);
}
}
Output
111=true
222=true
333=false
444=false
I have a little issue with an editable TableView. I want to display data from the database and also be able to edit then which saves it back to the DB.
Now, I can edit it. I have an if statement which checks whether the value is blank (empty or white space) and it works properly, the item in DB doesn't get updated if the value is blank.
My issue is that the blank value still gets displayed. If I click to edit it again, it displays the proper value. Here is a picture of the issue.
Here is the method which creats the table in my view class.
private TableView<Teacher> createTable(){
TableView table = new TableView();
table.setEditable(true);
table.setPrefWidth(500);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
nameColumn = new TableColumn<>("Jméno");
surnameColumn = new TableColumn<>("Příjmení");
nickColumn = new TableColumn<>("Nick");
table.getColumns().addAll(nameColumn, surnameColumn, nickColumn);
int columnCount = table.getColumns().size();
double columnSize = Math.floor(table.getPrefWidth() / columnCount);
nameColumn.setPrefWidth(columnSize);
surnameColumn.setPrefWidth(columnSize);
nickColumn.setPrefWidth(columnSize);
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
surnameColumn.setCellValueFactory(new PropertyValueFactory<>("surname"));
nickColumn.setCellValueFactory(new PropertyValueFactory<>("nick"));
List<Teacher> list = new TeacherDao().getAllTeachers();
ObservableList<Teacher> observableList = FXCollections.observableArrayList(list);
table.setItems(observableList);
return table;
}
Here is the part of the controller class to handle the edits.
private void onEditAction(){
view.getNameColumn().setCellFactory(TextFieldTableCell.forTableColumn());
view.getNameColumn().setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<Teacher, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Teacher, String> col) {
String newValue = col.getNewValue();
if(!(CheckString.isBlank(newValue))) {
(col.getTableView().getItems().get(
col.getTablePosition().getRow())
).setName(col.getNewValue());
Teacher teacher = view.getTeacherTableView().getSelectionModel().getSelectedItem();
int id = teacher.getUser_id();
new TeacherDao().updateTeacherNick(id, newValue);
}
}
}
);
view.getSurnameColumn().setCellFactory(TextFieldTableCell.forTableColumn());
view.getSurnameColumn().setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<Teacher, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Teacher, String> col) {
String newValue = col.getNewValue();
if(!(CheckString.isBlank(newValue))) {
(col.getTableView().getItems().get(
col.getTablePosition().getRow())
).setName(col.getNewValue());
Teacher teacher = view.getTeacherTableView().getSelectionModel().getSelectedItem();
int id = teacher.getUser_id();
new TeacherDao().updateTeacherNick(id, newValue);
}
}
}
);
view.getNickColumn().setCellFactory(TextFieldTableCell.forTableColumn());
view.getNickColumn().setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<Teacher, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Teacher, String> col) {
String newValue = col.getNewValue();
if(!(CheckString.isBlank(newValue))) {
(col.getTableView().getItems().get(
col.getTablePosition().getRow())
).setName(col.getNewValue());
Teacher teacher = view.getTeacherTableView().getSelectionModel().getSelectedItem();
int id = teacher.getUser_id();
new TeacherDao().updateTeacherNick(id, newValue);
}
}
}
);
}
I also tried adding, it didn't help though.
else
(col.getTableView().getItems().get(
col.getTablePosition().getRow())
).setName(col.getOldValue());
Well, I managed to solve it, here is how if anyone is curious
public class TeacherTableView extends TableView {
private TableColumn<Teacher, String> nameColumn, surnameColumn, nickColumn;
TeacherTableView() {
createTable();
onEditAction();
}
private void createTable(){
setEditable(true);
setPrefWidth(500);
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
nameColumn = new TableColumn<>("Jméno");
surnameColumn = new TableColumn<>("Příjmení");
nickColumn = new TableColumn<>("Nick");
getColumns().addAll(nameColumn, surnameColumn, nickColumn);
int columnCount = getColumns().size();
double columnSize = Math.floor(getPrefWidth() / columnCount);
nameColumn.setPrefWidth(columnSize);
nameColumn.setCellValueFactory(cdf -> cdf.getValue().nameProperty());
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
surnameColumn.setPrefWidth(columnSize);
surnameColumn.setCellValueFactory(cdf -> cdf.getValue().surnameProperty());
surnameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
surnameColumn.setEditable(true);
nickColumn.setPrefWidth(columnSize);
nickColumn.setCellValueFactory(cdf -> cdf.getValue().nickProperty());
nickColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nickColumn.setEditable(true);
List<Teacher> list = new TeacherDao().getAllTeachers();
ObservableList<Teacher> observableList = FXCollections.observableArrayList(list);
setItems(observableList);
}
private void onEditAction(){
nameColumn.setOnEditCommit(this::updateCol);
surnameColumn.setOnEditCommit(this::updateCol);
nickColumn.setOnEditCommit(this::updateCol);
}
private void updateCol(TableColumn.CellEditEvent<Teacher, String> col) {
String newValue = col.getNewValue();
if (CheckString.isNotBlank(newValue)) {
(col.getTableView().getItems().get(
col.getTablePosition().getRow())
).setName(col.getNewValue());
Teacher teacher = (Teacher) getSelectionModel().getSelectedItem();
int id = teacher.getUser_id();
new TeacherDao().updateTeacherNick(id, newValue);
} else {
col.getTableView().refresh();
}
}
}