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.
Related
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.
I am writing a code, that will calculate the area of country, it must get all values (Province areas and states areas in arraylist separate) and then add up everything.
This is the code for my Country Class:
import java.util.*;
public class Country implements Stats {
public String name;
ArrayList<Integer> provincesAreas = new ArrayList<>();
ArrayList<Integer> statesAreas;
public Country(String name, ArrayList<Integer> provincesAreas, ArrayList<Integer> statesAreas) {
super();
this.name = name;
this.provincesAreas = provincesAreas;
this.statesAreas = new ArrayList<Integer>();
}
public double computeArea() {
double parea=0;
for (Integer pA: provincesAreas) {
parea = parea + pA;
}
double sarea=0;
for (Integer sA: statesAreas) {
sarea+=sA;
}
return sarea+parea;
}
}
This is the Test Class Constructor
Country country = new Country ("Pakistan", new ArrayList<>(List.of(21, 62, 46, 98, 67, 34, 25, 9)), new ArrayList<>(List.of(1,2,3,4,5,6,7,8)));
System.out.println(country.computeArea());
The output its giving me is 362.0 but it should give an output of 398.0
How can I solve this error?
On the fourth line of your Country constructor, you do not initialize this.stateAreas to the stateAreas parameter.
Your Country constructor should instead be:
public Country(String name, ArrayList<Integer> provincesAreas, ArrayList<Integer> statesAreas) {
super();
this.name = name;
this.provincesAreas = provincesAreas;
this.statesAreas = statesAreas;
}
you'r not really populating the array in the constructor
this.statesAreas = new ArrayList<Integer>();
You are not storing the statesArea parameter, but are initializing it as an empty ArrayList.
There might be other issues with your Test Class, are you sure you're using the "List.of" method correctly?
I got an error on Eclipse.
I tried fixing your code, try this in the body of your constructor:
import java.util.*;
public class Country {
public String name;
ArrayList<Integer> provincesAreas = new ArrayList<>();
ArrayList<Integer> statesAreas;
public Country(String name, ArrayList<Integer> provincesAreas, ArrayList<Integer> statesAreas) {
this.name = name;
this.provincesAreas = provincesAreas;
this.statesAreas = new ArrayList<Integer>(statesAreas);
}
public double computeArea() {
double parea=0;
for (Integer pA: provincesAreas){
parea = parea + pA;
}
double sarea = 0;
for (Integer sA: statesAreas){
sarea+=sA;
}
return sarea+parea;
}
}
I have a comparator that compares nested fields of an object. Here the price of a product.
Problem: if the field is null, I'm getting a NullPointerException.
Question: how can I tell the comparator to ignore objects where the comparing field is null, without(!) having to filter the list beforehand? With ignore I mean having them at the end of the list.
public class SorterTest {
private static final Comparator<Product> PRODUCT_COMPARATOR =
Comparator.comparing(p -> p.details.price);
static class Product {
String name;
Details details;
static class Details {
BigDecimal price;
}
}
#Test
public void test() {
List<Product> products = Arrays.asList(
createProduct("A", new BigDecimal(30.00)),
createProduct("B", new BigDecimal(55.00)),
createProduct("C", new BigDecimal(20.00)),
createProduct("D", null),
createProduct("E", null),
createProduct("F", null)
);
Collections.sort(products, PRODUCT_COMPARATOR);
assertEquals("C", products.get(0).name);
assertEquals("A", products.get(1).name);
assertEquals("B", products.get(2).name);
assertEquals("D", products.get(3).name);
assertEquals("E", products.get(4).name);
assertEquals("F", products.get(5).name);
}
private Product createProduct(String name, BigDecimal price) {
Product p = new Product();
p.details = new Product.Details();
p.name = name;
p.details.price = price;
return p;
}
}
Result:
java.lang.NullPointerException
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.util.TimSort.binarySort(TimSort.java:296)
at java.util.TimSort.sort(TimSort.java:221)
at java.util.Arrays.sort(Arrays.java:1438)
at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
at java.util.Collections.sort(Collections.java:175)
You can use the method nullsLast of the Comparator:
private static final Comparator PRODUCT_COMPARATOR =
Comparator
.nullsLast(Comparator.comparing(p -> p.details.price, Comparator.nullsLast(Comparator.naturalOrder())));
The first nullsLast for Product being null, the second for price being null.
You get the NullPointerException because of the price is null. So use an if statement to check the price value, and if is null then use other value like 0 to replace it. The code may like:
public class SorterTest {
private static final Comparator<Product> PRODUCT_COMPARATOR =
Comparator.comparing(p->
{
if (p.details != null && p.details.price != null) {
return p.details.price;
} else {
return new BigDecimal(0);
}
}
);
static class Product {
String name;
Details details;
static class Details {
BigDecimal price;
}
}
#Test
public void test() {
List<Product> products = Arrays.asList(
createProduct("A", new BigDecimal(30.00)),
createProduct("B", new BigDecimal(55.00)),
createProduct("C", new BigDecimal(20.00)),
createProduct("D", null),
createProduct("E", null),
createProduct("F", null)
);
Collections.sort(products, PRODUCT_COMPARATOR);
assertEquals("D", products.get(0).name);
assertEquals("E", products.get(1).name);
assertEquals("F", products.get(2).name);
assertEquals("C", products.get(3).name);
assertEquals("A", products.get(4).name);
assertEquals("B", products.get(5).name);
}
private Product createProduct(String name, BigDecimal price) {
Product p = new Product();
p.details = new Product.Details();
p.name = name;
p.details.price = price;
return p;
}
}
You can use Optional<> to see if the result from getter is a null or a valid value. For null values, you can put them at the tail or head by always treating their comparator() result is -1 or 1.
private static final Comparator<Product> PRODUCT_COMPARATOR =
Comparator.comparing(p -> {
Optional<BigDecimal> res = Optional.ofNullable(p.getPrice);
if(res.isPresent())
return p.getPrice();
else
return -1;
});
...
static class Product {
String name;
BigDecimal price;
BigDecimal getPrice(){
return this.price;
}
...
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I want to create an app that counts my expenses. I created array list for trips and now I want to create array list for categories and another array list for each category. for example, in trip1 I have 3 categories: a, b, c. and in every category, I have my expenses. so how I make an array list for each value in the trip array list?
You need to have some classes
class Trip {
private ArrayList[Category] categories = new ArrayList();
public void addCategory(Category c) {
categories.add(c);
}
public void getCategory(int index) {
categories.get(index);
}
}
class Category {
private ArrayList[Expense] expenses = new ArrayList();
public void addExpense(Expense e) {
expenses.add(e);
}
}
class Expense {
private String name;
private int amount;
public Expense(name, amount) {
this.name = name;
this.amount = amount;
}
}
And then in the other parts of your code you can do things like
Trip africa = new Trip();
africa.addCategory(new Category());
africa.getCategory(0).addExpense(new Expense("apple", 10));
You might want your ArrayList of categories inside Trip to be a HashList, then your categories can have names.
You are creating a database-like structure. Here are somes tips :
1) You don't want to have duplicate data, because it's so easy to introduce inconsistencies in your structure.
This means : don't add your expanses in both expanses list and categories[cat] list
2) Maybe, use some Maps
3) Use some classes, to aggregate data
I would have done this like this :
import java.util.*;
public class Trip {
/** An item in the expanses list */
static class Item {
final String item;
final int cost;
public Item(String item, int cost) {
this.item = item;
this.cost = cost;
}
#Override public String toString() {
return this.item + " (" + this.cost + "$)";
}
}
/** A map with category as key and the associed list of items as value */
Map<String,List<Item>> expanses;
public Trip() {
this.expanses = new HashMap<String,List<Item>>();
String[] categories = {"food","clothes","souvenir"};
for (String cat: categories) { // init the categories with empty lists
this.expanses.put(cat, new ArrayList<Item>());
}
}
/** Register a new expanse to the trip. */
public void add(String item, int cost, String category) {
List<Item> list = this.expanses.get(category);
if (list == null)
throw new IllegalArgumentException("Category '"+category+"' does not exist.");
list.add( new Item(item, cost) );
}
/** Get the expanses, given a category.
* #return a fresh ArrayList containing the category elements, or null if the category does not exists
*/
public List<Item> getItems(String category) {
List<Item> list = this.expanses.get(category);
if (list == null)
return null;
return new ArrayList<Item>(list);
}
/** Get the expanses, given a category.
* #return a fresh ArrayList containing all the elements
*/
public List<Item> getItems() {
List<Item> list = new ArrayList<Item>();
for (List<Item> l: this.expanses.values()) // fill with each category items
list.addAll(l);
return list;
}
/** Get the total cost, given a category. */
public int getCost(String category) {
List<Item> list = this.expanses.get(category);
if (list == null)
return -1;
int cost = 0;
for (Item item: list)
cost += item.cost;
return cost;
}
/** Get the total cost. */
public int getCost() {
int cost = 0;
for (List<Item> l: this.expanses.values())
for (Item item: l)
cost += item.cost;
return cost;
}
// lets test this structure
public static void main(String[] args) {
Trip trip1 = new Trip();
trip1.add("apple", 10, "food");
trip1.add("cheese", 15, "food");
trip1.add("hat", 30, "clothes");
System.out.println( trip1.getItems("food") );
System.out.println( trip1.getCost("food") );
System.out.println();
System.out.println( trip1.getItems() );
System.out.println( trip1.getCost() );
}
}
try to make a trip an object(with those values associated with it) then put that into a list instead of making so many lists
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.