I have two classes. Child and Product with association many to many. Id like to find child who has the most products (product as a method parameter) using java stream :)
Thanks
You need something like:
private Optional<Child> childWithMostProduct(String productName) {
return childList.stream()
.max(Comparator.comparingLong(ch -> ch.getProducts()
.stream()
.filter(pr -> pr.getName().equals(productName)).count());
}
Or if you have defined equals for Product:
private Optional<Child> childWithMostProduct(Product product) {
return childList.stream()
.max(Comparator.comparingInt(ch -> Collections.frequency(ch.getProducts(), product));
}
Here is a complete working example of the second model:
public record Child(String name, List<Product> productList) {
private record Product(String name) { }
public static Optional<Child> childWithMostProduct(List<Child> childList, Product product) {
return childList.stream()
.max(Comparator.comparingInt(ch -> Collections.frequency(ch.productList, product)));
}
public static void main(String[] args) {
Product p1 = new Product("p1");
Product p2 = new Product("p2");
List<Child> childList = List.of(
new Child("ch1", List.of(p1, p1, p2)),
new Child("ch2", List.of(p1, p2, p2)));
System.out.println(childWithMostProduct(childList, p1));
System.out.println(childWithMostProduct(childList, p2));
}
}
The output of this is:
Optional[Child[name=ch1, productList=[Product[name=p1],
Product[name=p1], Product[name=p2]]]]
Optional[Child[name=ch2,
productList=[Product[name=p1], Product[name=p2], Product[name=p2]]]]
Which is correct.
Related
I have method returnSpecificOrder(Type type) (like in code below) where Type is enum class , this method should return only Orders containing item.type = "Clothes" where Item is another class witch have variable public Type type;I try to return orders from hashMap but java says that can't resolve symbol , how I should change my method ?
public class Order {
public long id;
public LocalDate dateTime;
public User user;
public List<Item> items;
//set seters and geters
public HashMap<Long, Order> createOrder() {
Order myFirstOrder = new Order();
myFirstOrder.setId(1);
myFirstOrder.setDateTime(LocalDate.now());
myFirstOrder.setUser(user);
myFirstOrder.setItems(items);
Order mySecondOrder = new Order();
mySecondOrder.setId(2);
mySecondOrder.setDateTime(LocalDate.now());
mySecondOrder.setUser(user);
mySecondOrder.setItems(item2);
//hash map of orders
HashMap<Long, Order> orders = new HashMap<>();
orders.put(myFirstOrder.getId(), myFirstOrder);
orders.put(mySecondOrder.getId(), mySecondOrder);
return orders;
}
//method that will return only Orders containing item.type = "Clothes"
public static Map<Long, Order> returnSpecificOrder(Type type) {
return orders.entrySet()
.stream()
.filter(o -> o.getValue().getItems().stream().anyMatch(item -> item.getType() == Type.clothes))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
} }
your filter should work.
The following is the working codes.
I refactored your Entity classes, and added comments where needed.
class Order {
public long id;
public List<Item> items;
//convenient constructor.
public Order(long id, Type... items) {
this.id = id;
this.items = Arrays.stream(items).map(Item::new).collect(Collectors.toList());
}
}
class Item {
Type type;
public Item(Type type) {
this.type = type;
}
}
enum Type {
Clothes, NotClothes, Car, Bicycle, Shoes
}
Then added your filter method into main
public class Main {
public static void main(String[] args) {
//pre-populate with sample data
Order o1 = new Order(1, Type.Bicycle, Type.Car, Type.Clothes);
Order o2 = new Order(2, Type.NotClothes, Type.Bicycle);
HashMap<Long, Order> orders = new HashMap<>();
orders.put(1L, o1);
orders.put(2L, o2);
//method that will return only Orders containing item.type = "Clothes"
Map<Long, Order> result = orders.entrySet().stream()
.filter(o -> {
//I Split into two lines to make it less cluttered.
List<Item> list = o.getValue().items;
return list.stream().anyMatch(item -> item.type == Type.Clothes);
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//The answer shows that only 1 item is returned.
System.out.println("result = " + result.size());
}
}
I'm trying to learn aggregate functions and lambdas in Java. I have a class:
public class Person {
public enum Privilege{
PRIV1, PRIV2, PRIV3, PRIV4, PRIV4
}
private String name;
private Set<Privilege> privileges;
...
}
and a list of objects of this class.
I want to convert it to EnumMap<Privilege, List<String>>
where List contains names of all people having certain privilege. I created a method to do this:
public static Map<Privilege,List<String>> personsByPrivilege(List<Person> personList){
Map<Privilege, List<String>> resultMap = new EnumMap(Privilege.class);
Arrays.asList(Privilege.values())
.stream()
.forEach(p->resultMap.put(p,new ArrayList<String>()));
for(Person p :personList){
Set<Privilege> personsPrivileges = p.getPrivileges();
for(Privilege pr : personsPrivileges){
resultMap.get(pr).add(p.getName());
}
}
return resultMap;
}
How do I do it using aggregate functions?
I mean e.g. personlist.stream().collect style
You could flatten the list of person -> list of privileges into pairs, then groupby by the privilege, and map with the name
public static Map<Person.Privilege, List<String>> personsByPrivilegeB(List<Person> personList) {
return personList.stream()
.flatMap(pers -> pers.getPrivileges().stream().map(priv -> new Object[]{priv, pers.getName()}))
.collect(groupingBy(o -> (Person.Privilege) o[0], mapping(e -> (String) e[0], toList())));
}
You can add a Pair class and use the below code for achieving your goal
return personList.stream().flatMap(p -> {
String name = p.getName();
return p.getPrivileges().stream()
.flatMap(priv -> Stream.of(new NamePriviledge(priv, name)));
}).collect(Collectors.groupingBy(NamePriviledge::getPrivilege, Collectors.mapping(NamePriviledge::getName, Collectors.toList())));
}
class NamePriviledge {
private final Person.Privilege privilege;
private final String name;
public NamePriviledge(Person.Privilege privilege, String name) {
this.privilege = privilege;
this.name = name;
}
public String getName() {
return name;
}
public Person.Privilege getPrivilege() {
return privilege;
}
#Override
public String toString() {
return "NamePriviledge{" +
"privilege=" + privilege +
", name='" + name + '\'' +
'}';
}
}
I have this Person class which has a list of Person (s). How do I loop through persons and check if each object inside of that has a list of Person(s) and if each object inside that has a list and so on and so forth? Everything I can think of is pretty limiting as far as how nested it gets. I can write a recursive loop but that gets me to the first level deep, but not sure how to get x levels deep with recursion. I am sure somebody has come accross this problem in the past and it shouldn't be that difficult but I just can't quite wrap my head around it. Any and all ideas are welcomed!
public class Person {
// other fields removed for simplicity
private long id;
private List<Person> persons;
public List<Person> getPersons() {
return debates;
}
}
// essentially I am looking for a way to make this unlimited level nested looping
private void loopPersons() {
Person person = new Person();
if(person.getPersons() != null && !person.getPersons().isEmpty()) {
for(Person person1 : person.getPersons()) {
if(person1.getPersons() != null && !person1.getPersons().isEmpty()) {
System.out.println(person1.getId());
for(Person person2 : person1.getPersons()) {
if(person2.getPersons() != null && !person2.getPersons().isEmpty()) {
System.out.println(person2.getId());
}
}
}
}
}
}
UPDATE:
The answer by Brian in this other post (scroll down) is essentially what does it. iterate through recursive objects
You might just be looking for some flattening on the lines of making use of recursion with a tail condition. This could be similar to the following implementation
// essentially I am looking for a way to make this unlimited level nested looping
private List<Person> loopPersons(Person person, List<Person> flattened) {
if (person.getPersons() == null || person.getPersons().isEmpty()) {
return flattened;
} else {
flattened.addAll(person.getPersons());
person.getPersons().forEach(p -> loopPersons(p, flattened));
}
return flattened;
}
Note: The code is not tested and is to depict a possible approach that you can take if you think over the same lines.
Do it as follows:
import java.util.List;
class Person {
// other fields removed for simplicity
private long id;
private List<Person> persons;
public Person(long id, List<Person> persons) {
this.id = id;
this.persons = persons;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
public List<Person> getPersons() {
return persons;
}
#Override
public String toString() {
return "Person [id=" + id + ", persons=" + persons + "]";
}
public void showAll() {
if (getPersons() == null || getPersons().isEmpty()) {
return;
}
getPersons().get(0).showAll();
System.out.println(getPersons());
}
}
Demo:
public class Main {
public static void main(String[] args) {
Person p1 = new Person(1,List.of(new Person(11, List.of(new Person(111, List.of(new Person(1111, null))),
new Person(112, List.of(new Person(1121, null))),
new Person(113, List.of(new Person(1131, null))))),
new Person(12, List.of(new Person(121, List.of(new Person(1211, null))))),
new Person(13, List.of(new Person(131, List.of(new Person(1311, null)))))));
p1.showAll();
}
}
Output:
[Person [id=1111, persons=null]]
[Person [id=111, persons=[Person [id=1111, persons=null]]], Person [id=112, persons=[Person [id=1121, persons=null]]], Person [id=113, persons=[Person [id=1131, persons=null]]]]
[Person [id=11, persons=[Person [id=111, persons=[Person [id=1111, persons=null]]], Person [id=112, persons=[Person [id=1121, persons=null]]], Person [id=113, persons=[Person [id=1131, persons=null]]]]], Person [id=12, persons=[Person [id=121, persons=[Person [id=1211, persons=null]]]]], Person [id=13, persons=[Person [id=131, persons=[Person [id=1311, persons=null]]]]]]
My problem essentially comes down to this simplified example. I have data coming back from a DB which has some duplicate information in the rows.
In this example I have a list of TeamRow objects that come back from the DB. I can easily group these using Collectors.groupingBy:
public class TeamRow {
private int id;
private String name;
private String player;
public TeamRow(int id, String name, String player) {
this.id = id;
this.name = name;
this.player = player;
}
public int getId() {return id;}
public String getName() { return name; }
public String getPlayer() {return player;}
}
public class Team {
private int id;
private String name;
private List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<String>(players);
}
}
List<TeamRow> dbTeams = new ArrayList<TeamRow>();
dbTeams.add(new TeamRow(1, "Team1", "Jonny"));
dbTeams.add(new TeamRow(1, "Team1", "Rob"));
dbTeams.add(new TeamRow(1, "Team1", "Carlos"));
dbTeams.add(new TeamRow(2, "Team2", "Shane"));
dbTeams.add(new TeamRow(2, "Team2", "Lucas"));
dbTeams.add(new TeamRow(3, "Team3", "Geraint"));
dbTeams.add(new TeamRow(3, "Team3", "Rocky"));
dbTeams.add(new TeamRow(3, "Team3", "Wayne"));
dbTeams.add(new TeamRow(3, "Team3", "Dwayne"));
dbTeams.add(new TeamRow(3, "Team3", "Lester"));
Map<Integer, List<TeamRow>> myMap = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId));
However, what I'm actually trying to achieve is to convert the TeamRows to Teams. So that the id and name are only represented once and the players are stored in a List in the Team object. I can achieve this by adding a forEach over the map as shown below.
But I've been trying to figure out if there is a way I can achieve the same result by adding some sort of mapper or downstream collector. Would this even offer any benefit over adding a subsequent forEach ?? Eg:
List<Team> teams = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId, ???), ???).???;
Conversion using forEach:
List<Team> teams = new ArrayList<>();
myMap.forEach((id, teamRows) -> {
if (teamRows.size() > 0) {
TeamRow tr = teamRows.get(0);
List<String> players = teamRows.stream().map(TeamRow::getPlayer).collect(Collectors.toList());
teams.add(new Team(id, tr.getName(), players));
}
});
Previously I said I would do it by creating an atomic transformer function like this:
Function<TeamRow, Team> getTeamRowTransformer() {
final Map<Integer, Team> map = new ConcurrentHashMap<Integer, Team>();
return (teamRow)->{
Team result = map.computeIfAbsent(teamRow.getId(), id->new Team(id, teamRow.getName(), Collections.emptyList()));
result.players.add(teamRow.getPlayer());
return result;
};
}
It handles the mapping and your stream code becomes one very legible step:
Set<Team> finalTeams = dbTeams.stream()
.map(getTeamRowTransformer())
.collect(Collectors.toSet());
HOWEVER, I realized, you can also do this:
List<Team> teams = dbTeams.stream()
.map(tr->new Team(tr.getId(), tr.getName(), Arrays.asList(tr.getPlayer())))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(t->t.id,
Collectors.reducing((Team a, Team b)->{
a.players.addAll(b.players);
return (Team)a;
})
), m->m.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
This way you never have an accessible mutable collection until List<Team> teams is assigned.
You may use toMap collector with custom merge function. It's probably a good idea to add merge method to the Team class:
public class Team {
private final int id;
private final String name;
private final List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<>(players);
}
// merges other team into this team, returning this team
public Team merge(Team other) {
assert id == other.id; // remove asserts if you don't like them
assert name.equals(other.name);
players.addAll(other.players);
return this;
}
}
Now you can solve your problem this way:
Collection<Team> teams = dbTeams.stream()
.map(tr -> new Team(tr.id, tr.name, Arrays.asList(tr.player)))
.collect(Collectors.toMap(t -> t.id, t -> t, Team::merge)).values();
You could try something like
List<Team> teamList = dbTeams.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(TeamRow::getId),
(m -> m.entrySet().stream().map(
e -> {
List<TeamRow> l = e.getValue();
return new Team(l.get(0).getId(), l.get(0).getName(), l.stream().map(TeamRow::getPlayer).collect(Collectors.toList()));
}
).collect(Collectors.toList()))));
Using collectingAndThen() you can use a function which maps the entries of the map to Teams. l.get(0) should not fail as there is always at least one entry in the list.
I am not sure if this is more concise, but at least it does not use foreach.
I looking for solution how to make List (or something) with unique Product. The reason i want to do it is total price of Product. Each sets can contains same Product.
Here's my classes.
public class Product {
public String name; // unique name
public double price;
public double qty;
}
&
public class Sets {
public Product item1;
public Product item2;
...
public Product item7;
public static listsProduct<Product> = new Arraylists<Product>();
}
I'm trying to make a Lists but i don't know how to add a unique product. To add Product i use reflection.
My method:
public void getProducts() throws NoSuchMethodException, Exception {
Sets object = this;
Class clazz = object.getClass();
Field[] fields = clazz.getFields();
Method m1 = Product.class.getMethod("getname", null);
for (Field field : fields) {
if(field.get(object)!=null) {
System.out.println(field.getName()+" = " + m1.invoke(field.get(object),null));
Product e=(Product) field.get(object);
if (listsProduct==null ) listsProduct.add((Produkt) field.get(object));
if (!(listsProduct.contains(field.get(object)))) listsProduct.add(e);
}
}
It's adding a Product correctly but how make UNIQUE lists?
Thanks in advance for any help !
EDIT :
So... What do u want achieve?
eg.
sets :
1) butter, milk, peanut
2) goodie, butter, xxx
3) milk, peanut, xxx
result:
List of unique product
butter
milk
peanut
xxx
goodie
if product exist on lists sum price
You do not need to use the Reflection API for things like this!
One way to get a list of unique products would be to use Map::merge with the Product name as key and the Product::merge method from this example as the remapping function.
Given a class Product...
public class Product {
String name;
double price;
int qty;
public Product(String name, double price, int qty) {
this.name = name; this.price = price; this.qty = qty;
}
public Product merge(Product p) {
price += p.price;
qty += p.qty;
return this;
}
#Override
public String toString() {
return String.format("%s x %d (%.2f)", name, qty, price);
}
}
And a List of Sets of Products:
List<Set<Product>> sets = asList(
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("milk", 3, 3))),
new HashSet<>(asList(new Product("peanut", 1, 1), new Product("bread", 2, 1))),
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("peanut", 1, 1)))
);
A Collection of unique Products can be created like this:
private static Collection<Product> listProducts(List<Set<Product>> sets) {
Map<String, Product> uniques = new HashMap<>();
for(Set<Product> set : sets)
for(Product p : set)
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
Full working example:
import static java.util.Arrays.*;
import java.util.*;
public class UniqProd {
public static class Product {
String name;
double price;
int qty;
public Product(String name, double price, int qty) {
this.name = name;
this.price = price;
this.qty = qty;
}
public Product merge(Product p) {
price += p.price;
qty += p.qty;
return this;
}
#Override
public String toString() {
return String.format("%s x %d (%.2f)", name, qty, price);
}
}
public static void main(String[] args) {
List<Set<Product>> sets = asList(
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("milk", 3, 3))),
new HashSet<>(asList(new Product("peanut", 1, 1), new Product("bread", 2, 1))),
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("peanut", 1, 1)))
);
listProducts(sets).forEach(System.out::println);
}
private static Collection<Product> listProducts(List<Set<Product>> sets) {
Map<String, Product> uniques = new HashMap<>();
for (Set<Product> set : sets)
for (Product p : set)
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
}
If you, for some reason, must/want to use your Sets class instead of a java.util.Set, construct a List of the products in your Sets instance before iterating it:
private static Collection<Product> listProducts(List<Sets> sets) {
Map<String, Product> uniques = new HashMap<>();
for (Sets set : sets)
for (Product p : asList(set.item1, set.item2, set.item3))
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
I still didn't get why you want to use reflection , I am giving below answer to solve your problem of how to store unique products ?
If you want to add unique products . you must define that what makes the product Unique? Lets assume that product name will help you identifying whether they are not-equal/unique.
To define it let's write equals &hashcode method in product class
public class Product {
public String name; // unique name
public double price;
public double qty;
public boolean equals(Object obj){
if (obj ==null || ! obj instanceOf Prpduct) return false;
else return ((Product) obj).name.equals(name);// also write a null check conditions for name field.
}
public interest hashCode(){
return name.hashCode();
}
}
Now change your data structure from List to Set
which will store only distinct Products , like below
`
public class Sets {
public Product item1;
public Product item2;
...
public Product item7;
public static Set<Product> productSet= new HashSet<Product>();
}
`
With this whenever you will add productSet only unique would get stored.