I have a shopping site, in which items can be added to a basket, each item is an instance of a class Product, and all the items are stored in a Collection<Product> items,
I am currently then iterating through that list and displaying each item in a table.
However I want to display a quantity value for each item instead.
I created a Map, and am trying to put each of my products into it.
However each Product is still listed as only existing once because each class instance is different?
How would I adjust this?
My Product class has a product ID value. Here's the code I have currently.
Map<Product, Integer> map = new HashMap<>();
for (Product p : items) {
Integer i = map.get(p);
if (i == null) {
map.put(p, 1);
}
else {
map.put(p, i+1);
}
}
Having implemented hashcode and equals methods.
Trying to add the items to the map.
Collection<Product> items = basket.getItems();
Map<Product, Integer> map = new HashMap<>();
for (Product p : items) {
for (Product key : map.keySet()) {
if (p.equals(key)) {
map.put(key, map.get(key));
}
else {
map.put(p, 1);
}
}
}
However each Product is still listed as only existing once because each class instance is different?
Yes.
HashMap identifies keys by using their implementation of hashcode() and equals().
You you either use a property, which already has a proper implementation of both (as #zsmb13 suggested) or you create implementations of hashcode() and equals() in your Product class (ATTENTION! do not inherit them! They must be implemented in a decent child which will not be extended itself...).
You need to override the equals and the hashCode of Product Class for your hashing based operations to function properly. You need your Product class something like this
class Product{
private int price;
private String name;
public Product(String itm, int pr){
this.name = itm;
this.price = pr;
}
public int hashCode(){
int hashcode = 0;
hashcode = price*20;
hashcode += name.hashCode();
return hashcode;
}
public boolean equals(Object obj){
if (obj instanceof Product) {
Product pp = (Product) obj;
return (pp.name.equals(this.name) && pp.price == this.price);
} else {
return false;
}
}
}
Related
I need to have a Sorted Map where all elements are sorted by some key, but I need to be able to get elements by another key.
I assumed that I can solve this task by creating a custom key:
public class MyKey implements Comparable<MyKey>{
private long id;
private double price;
public MyKey(long orderId, double price) {
this.id = id;
this.price = price;
}
#Override
public int hashCode(){
return Objects.hash(id);
}
#Override
public boolean equals(Object o){
if(!(o instanceof MyKey)) return false;
return id == ((MyKey) o).id;
}
#Override
public int compareTo(MyKey o) {
if(price > o.price) return 1;
if(price < o.price) return -1;
return 0;
}
}
Here is I need to be able to get elements by key, but I need to force Map be sorted by price.
I tried to use:
Map<MyKey, Integer> myTestMap = new ConcurrentSkipListMap<>();
myTestMap.put(new MyKey(1, 200.0), 1);
myTestMap.put(new MyKey(2, 100.0), 2);
myTestMap.put(new MyKey(3, 300.0), 3);
myTestMap.put(new MyKey(6, 500.0), 6);
myTestMap.put(new MyKey(5, 400.0), 5);
myTestMap.put(new MyKey(4, 600.0), 4);
In this case Map is successfully sorted by price, but I can't get the element by using:
System.out.println(myTestMap.get(new MyKey(2, 0)));
I have to set price also to be able to get the element:
System.out.println(myTestMap.get(new MyKey(2, 100.0)));
Are there any workaround in this case?
No real solution to your answer, but an explanation why it is not working: If your dig into ConcurrentSkipListMap you'll see that getting an object checks an internal Index-structure and uses the compareTo method of your Comparable. It is not just using the hashCode method that takes only id into account (for which it would seem plausible to give just your id into the adhoc created MyKey instance).
I'd too suggest using a different approach.
I really don't understand from the below code how the get() methods, set() methods and toString() are called. Could someone explain me ?
The whole point here is after seeing the output I don't understand how the tostring method has been called. I don't see anything explicitly being called.
public class MyDuplicateKeyEx {
public static void main(String a[]){
HashMap<Price, String> hm = new HashMap<Price, String>();
hm.put(new Price("Banana", 20), "Banana");
hm.put(new Price("Orange", 30), "Orange");
printMap(hm);
Price key = new Price("Banana", 20);
System.out.println("Adding duplicate key...");
hm.put(key, "Grape");
System.out.println("After adding dulicate key:");
printMap(hm);
}
public static void printMap(HashMap<Price, String> map){
Set<Price> keys = map.keySet();
for(Price p:keys){
System.out.println(p+"==>"+map.get(p));
}
}
}
class Price{
private String item;
private int price;
public Price(String itm, int pr){
this.item = itm;
this.price = pr;
}
public int hashCode(){
int hashcode = 0;
hashcode = price*20;
hashcode += item.hashCode();
return hashcode;
}
public boolean equals(Object obj){
if (obj instanceof Price) {
Price pp = (Price) obj;
return (pp.item.equals(this.item) && pp.price == this.price);
} else {
return false;
}
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String toString(){
return "item: "+item+" price: "+price;
}
}
Output:
item: Apple price: 40==>Apple
item: Orange price: 30==>Orange
item: Banana price: 20==>Banana
Adding duplicate key...
After adding dulicate key:
item: Apple price: 40==>Apple
item: Orange price: 30==>Orange
item: Banana price: 20==>Grape
Thanks !!
You are right, there is no explicit call to toString. But under the hood, that is what Java is doing. When seeing p+"==>"+map.get(p), Java is doing p.toString()+"==>"+map.get(p).toString(). That is why you can concatenate strings and objects without problems.
Additionally, a better way of iterating through the key/values of a Map is:
public static void printMap(HashMap<Price, String> map) {
for (Map.Entry<Price, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "==>" + entry.getValue())
}
}
When using a HashMap with user-defined objects as keys, you must be very careful that you do never modify the fields used to compute the hashCode if they are present in the map. This is why you'll often see that final fields should be used to compute it. With a large program, this avoids lots of unnecessary mistakes.
Of course there is no explicit toString! Java does this implicitly for you! You should thank him, ya know.
The magic lies behind this code:
p+"==>"+map.get(p)
Here, p is a Price and map.get(p) is a String. So the above is basically adding a price to a string, then add another string to the result.
When Java wants to add any object to a string, it calls that object's toString to convert that object to String first. Otherwise, how can a price be added to a string, right?
Tip:
Never use objects which hash code can change as keys of a hash map. I have actually seen a person who was very extreme and he implemented the hashCode method with Math.random()! Here's the post: Could not understand the output And asked why can a hash map store things with the same hash code.
So to avoid that confusion, please don't use mutable objects as keys. Just remove those setters and you'll be fine.
How would I go about only inserting an item if the item does not have the same name as the other items in the arraylist within my insertUniqueItem() method?
public void insertUniqueItem()
{
}
public static void main(String[] args)
{
ShoppingCart sc = new ShoppingCart();
Item a = new Item("Kraft Cheese", 4, "\"Best Cheese in Town!\"");
Item b = new Item("Bottle of Water", 2.50, "\"Refreshing Water!\"");
sc.insertItem(a);
sc.insertItem(b);
sc.printInvoice();
sc.insertUniqueItem();
sc.print();
DecimalFormat df = new DecimalFormat("#.00");
System.out.println("Total Price: $" + df.format(sc.getTotal()));
}
}
I would probably use a LinkedHashMap<String, Item> instead, where the key is the name of the item. That way:
You still get to preserve ordering
You can easily and efficiently check whether there's already an entry for a given item name.
You'd have something like:
if (!map.containsKey(item.getName()) {
map.put(item.getName(), item);
}
This only works if you want all items to be unique in terms of name though. If you sometimes want to allow a duplicate name and sometimes not, you probably do want a list, and then you could have something like:
boolean containsItemName(ArrayList<Item> items, String name) {
for (Item item : items) {
if (item.getName().equals(name)) {
return true;
}
}
return false;
}
If you're using Java 8, you could use a stream-based solution:
boolean containsItemName(ArrayList<Item> items, String name) {
return items.stream().anyMatch(item -> item.getName().equals(name));
}
You need to check if an item with the same name already exists in your list:
public void insertUniqueItem(Item item) {
if(!contains(item)) {
cart.add(item);
}
}
private boolean contains(Item item) {
for(Item i : cart) {
if(i.getName().equals(item.getName())) {
return true;
}
}
return false;
}
You can modify your Item class to have the equals and hashcode methods as follows:
public class Item{
private String name;
public boolean equals(Object o){
if (o instanceOf Item and ((Item)o).getName().compareTo(name) == 0){
return true;
}
return false;
}
public int hashCode(){
return name.hashCode();
}
}
You can now use the contains method of the arrayList to determine if another item exists with the same name in the arrayList or not.
I recommend you to use a Set in your ShoppingCart instead of a List. Set will manage the unicity of your items. Make sure to have equals method override in your Item class
Why you do not using Set ?
Adds the specified element to this set if it is not already present
(optional operation). More formally, adds the specified element e to
this set if the set contains no element e2 such that (e==null ?
e2==null : e.equals(e2)). If this set already contains the element,
the call leaves the set unchanged and returns false. In combination
with the restriction on constructors, this ensures that sets never
contain duplicate elements.
too simple :
override equal and hashCode on Item class :
class Item {
private String title ;
private String description ;
// getter & setter
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false
Item item = (Item) o;
return Objects.equals(title,item.title) ; // Or any statement you want
}
#Override
public int hashCode() {
return Objects.hash(title); //same statement like equals method
}
}
Now :
List<Item> listCollection = new ArrayList<>();
Item a = new Item("A", "Best Cheese in Town!");
Item b = new Item("B", "Refreshing Water!");
// add items ...
listCollection.add(a);
listCollection.add(b);
Set<Item> setCollection = new HashSet<>(listCollection);
Item duplicate = new Item("B","New Description");
setCollection.add(duplicate); // Not added to setCollection
I have a List of Employee object.
class Employee{
private int empId;
private String name;
}
Now I have
List<Employee> empList = new ArrayList<Employee>();
How can I find, if my list contains an employee named "ABC"??
empList.contains("ABC"); wont work...
Should I put it in Map?? Which one is more efficient??
Just wanted to mention that I get my Employee object from database....
You can use
Map<String, Employee> map = new HashMap<>();
map.put("ABC", new Employee("ABC"));
map.put("John", new Employee("John"));
and then check
map.containsKey("ABC")
Should I put it in Map?? Which one is more efficient??
Because contains() method of list, calls indexOf, which needs to iterate over all elements
like this
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
Where as map no need to iterate over all elements
Since you are storing the Employee objects and not String in your list , i think it is impossible to search without looping through all list objects
for (Employee employee : empList) {
if (employee.getName().equals(searchString))
System.out.println("Found");
}
Note: Your Employee class should give access to name field either through getter method or change it to public
There are other alternatives, but it depends on your requirements and tradeoff's between speed, space, readability, resources etc
One thing i can think of is HashMap, which has constant time lookup in average case
HashMap<Integer, String> hm = new HashMap<Integer, String>();
hm.put(1, "Tom");
System.out.println(hm.containsValue("Tom"));
Now,
Should I put it in Map?? Which one is more efficient??
Instead of coding and analyzing, Know Thy Complexities beforehand !
In Java 8, if you wanted to determine whether the employee list contains an employee named "ABC", you could do this:
boolean containsABC = empList.stream().anyMatch(emp -> emp.getName().equals("ABC"));
Override equals. You can then use List.contains
class Employee {
private empId;
private name;
public boolean equals(Object o) {
return (o instanceof Employee && ((Employee)o).empId == empId && ((Employee)o).name = name);
}
}
List l = ...;
Employee e = new Employee(...);
l.add(e);
l.contains(e);
Here is the code that you can use.
I am considering that you want list to return true when empId and name of the Employee matches.
I also prefer to use Constructor in your code(Just recommendation).
The below code will run as you are wanting it to be.
class Employee {
private int empId;
private String name;
// below overriden function will return true if it found Employee with
// same ID and name
#Override
public boolean equals(Object obj) {
return (obj instanceof Employee //Checking instace of obj
&& ((Employee)obj).empId == empId //Checking empId
&& ((Employee)obj).name.equals(name)); //Checking name
}
// Used constructor to create Employee
Employee(int id, String nm) {
empId = id;
name = nm;
}
}
Here is an example run :
List l = new ArrayList();
l.add(new Employee(1, "ME");
System.out.println(l.contains(new Employee(1, "ME"))); //print true
I would also like to acknowledge you that you should also override hashCode() when you decides to override equals(...) method according to Design Pattern.
i having an object with below information
TransHdr: id, order_num
TransItem: hdr_id, product_code, refnum, qty (child record)
transHdr.id=transItem.hdr_id
if let say 3 record can be found in TransItem,
parkA,112,10
parkA,112,6
parkB,113,10
i would like group it base on refnum, means that my result will be
parkA,112,16
parkB,113,10
i need a method that will loop the object (item level) and need to return transHdr object to other function. anyway to do this?
for (java.util.Iterator<ITransItem> groupTransItems = TransHdr.getTransItems().iterator();
groupTransItems.hasNext();) {
ITransItem _TransItem = groupTransItems.next();
if (null!=_TransItem.getRefNum()){
<question here..how do i group and sum up my item and become only 1 record?>
}
}
return newGroupingTransHdr;
}
Create a new Map with refnum as key and qty as value.
Map<String,Integer> qtyMap=new HashMap<String,Integer>();
while iterating, try
String refNum=transItem.getRefNum();
// Mark for removal ? if this is not the first item in the list with the refnum
boolean remove=true;
Integer currentQty=qtyMap.get(refNum);
if(currentQty==null){
currentQty=0;
// this doesnt exist already in the map, this is the first item with this reference
// number in the list, so you should keep this without removing
remove=false;
}
currentQty=currentQty+transItem.getQty();
qtyMap.put(refNum,currentQty);
// if the remove is true then remove this item from the list.
if(remove){
groupTransItems.remove();
}
This will sum up the qty for refnum's in the map and once your iteration is over, the map will have the sums of quantities for each refnum. You will have to iterate the list once more to set the current qty to each item from the map [EDIT] :- Added the iterating time removal.
Similar to the solution suggested in this post. You can have a Map with ref_num as key and TransItem as value.
TransHdr transHdr; // Externally given
Map<String, ITransItem> collapsedItems = new HashMap<String, ITransItem>();
List<ITransItem> items = transHdr.getItems();
transHdr.setItems(new ArrayList<ItransItem>());
for (ITransItem item : items) {
String ref_num = item.getRefNum();
ITransItem collapsedItem = collapsedItems.get(ref_num);
if (collapsedItem == null) {
collapsedItems.put(ref_num, item);
} else {
int qnt = item.getQnt();
collapsedItem.setQnt(collapsedItem.getQunt() + qnt);
}
}
transHdr.setItems(new ArrayList<ITransItem>(collapsedItems.values()));
Another way to accomplish what you want to do is to embed the logic in an add method on your TransHdr class.
pulic class TransHdr {
private String id;
private int orderNumber;
private Map<String, ITransItem> items;
public TransHdr(String id, int orderNumber) {
this.id = id;
this.orderNumber = orderNumber;
this.items = new HashMap<String, ITransItem>();
}
public void addItem(ITransItem item) {
String ref = item.getRefNum();
ITransItem currentItem = items.get(ref);
if (currentItem == null) {
items.put(ref, item);
} else {
int qnt = item.getQnt();
currentItem.setQnt(currentItem.getQnt() + qnt);
}
}
public Set<ITransItem> getItems() {
return items.values();
}
}
As you can see, there's multiple ways of doing this. The appropriate solution depends on what your requirements and use cases are.