General way to group objects by several properties in Java - java

I have a collection of items:
class Item {
String type;
boolean flag;
int size;
...
}
There are a few possible types (say "a", "b" and "c") and therefore several possible combinations of type-flag values (["a" ; false], ["a" ; true"], ["b" ; false], ... ). I need to collapse items that have same combination of values, so I have collapse method with this signature
Item collapse(Collection<Item> items)
What I need is to divide input items list into groups that have same type and flag values
List<Collection<Item>> getGroups(Collection<Item> items) // method I need
so I could collapse each group
List<Item> r = getGroups(items).stream().map(Item::collapse).collect(toList());
So I could create a Map of Maps or make some composite keys, but it requires some boilerplate code which I'd like to avoid. In future I can have more attributes for grouping, so the solution should not be hardcoded on these two properties, but be easily extendable for new ones.
How can this be done nicely? Is there a well-known solution for the problem?

You can use Collectors.groupingBy:
public static void main(String[] args) {
List<Item> list = new ArrayList<>();
list.add(new Item(1, true, 1));
list.add(new Item(1, true, 2));
list.add(new Item(1, false, 3));
list.add(new Item(1, false, 4));
list.add(new Item(2, true, 5));
list.add(new Item(2, false, 6));
Collection<List<Item>> result = list.stream()
.collect(Collectors.groupingBy(x -> Arrays.<Object>asList(x.keyA, x.keyB)))
.values();
for (List<Item> items : result) {
System.out.println(items);
}
}
static class Item {
Integer keyA;
Boolean keyB;
Integer value;
public Item(Integer keyA, Boolean keyB, Integer value) {
this.keyA = keyA;
this.keyB = keyB;
this.value = value;
}
#Override
public String toString() {
return "Item{" +
"keyA=" + keyA +
", keyB=" + keyB +
", value=" + value +
'}';
}
}

In order to know which Item logically equals some other Item for collapsing, I'd put the responsibility of that on the Item class itself. You could override the equals method, but if you're gonna put them in a Set somewhere this might lead to undesirable results, so a separate method used for checking might be best.
Another option is to take those fields that would be used for this check and turn them into an inner class of Item. equals and hashCode could then be overridden for the inner class only and its instances used as a key for a Map.
None of this is going to automagically include any new fields you add later, however. So it'll be up to whoever maintains the class to make sure anything that needs to be included in the check (or equals/hashCode) is added to the method(s).
The only way I can really think of to get close to this is to use reflection. If anything that must be taken into account is only put in an inner class, that would work. If you must keep it on the Item class directly, perhaps defining an annotation (with runtime retention) could be useful. The code doing the checking (or equals/hashCode if used) could reflect upon the class and use every annotated field.
The annotation could look something like this:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CollapseField {
}
And then used like this:
class Item {
#CollapseField
String type;
#CollapseField
boolean flag;
int size;
...
}
The code using it would then need to check which fields are annotated and get their values (both of these actions using reflection) and check equality with other objects to establish which belong together. Since this might impact performance quite a bit, using caching for something like a hash code would be a good idea.
In the end, I'm not sure if it's worth it over hard-coding the used values unless you're gonna use this in a large amount of classes or the number of fields could become quite large.
Finally, it may seem odd for Java, but perhaps using the properties pattern instead of fields may make sense. Although you'll lose some type safety. Steve Yegge made a long but interesting post about it: http://steve-yegge.blogspot.be/2008/10/universal-design-pattern.html
That's pretty much all I can come up with off the top of my head. As far as I know, there is no standard approach. Maybe someone knows of some convenient library offering a solution.
EDIT: here's an example where the fields to be used for the key are made into an inner class, which implements equals and hashCode so it can be used as the key for a Map:
import java.util.Objects;
public class Item {
int size;
final Key key;
public class Key {
String type;
boolean flag;
public Key(String type, boolean flag) {
this.type = type;
this.flag = flag;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
#Override
public int hashCode() {
int hash = 5;
hash = 89 * hash + Objects.hashCode(this.type);
hash = 89 * hash + (this.flag ? 1 : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Key other = (Key) obj;
if (!Objects.equals(this.type, other.type)) {
return false;
}
if (this.flag != other.flag) {
return false;
}
return true;
}
}
public Item(String type, boolean flag, int size) {
key = new Key(type, flag);
this.size = size;
}
public String getType() {
return key.type;
}
public void setType(String type) {
this.key.type = type;
}
public boolean isFlag() {
return key.flag;
}
public void setFlag(boolean flag) {
this.key.flag = flag;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public Key getKey() {
return key;
}
}
The getters and setters on the Item level delegate some of the fields to the Key. Note that the getters and setters in Key might not be necessary if you're only going through Item, since the fields are directly accessible to the containing class. If you need to add a field that must be part of the key, add it to Key. If it must not be used for identification, add it directly to Item. equals and hashCode can be easily auto-generated by any decent IDE if you must update them.
Do note that this solution might break if you use the class in some framework that does reflection or introspection. Depending on how it's approached, the fields in Key might end up being seen as properties of Item (due to the getters/setters) or not. Something like JPA or an EJB container approaching fields directly via reflection could fail to work with this.

Putting here my current approach with composite key, but still waiting for fresh ideas
Collection<Collection<Item>> getGroups(Collection<Item> items) {
Map<String, Collection<Item>> itemsByKey = new HashMap<>();
for (Item item : items) {
String key = item.key();
Collection<Item> byKey = itemsByKey.get(key);
if (byKey == null) {
itemsByKey.put(key, (byKey = new ArrayList<>()));
}
byKey.add(item);
}
return itemsByKey.values();
}
class Item {
public String key() { type + "-" + flag }
New fields can be added easily, but string keys do not look great.

Related

Is it bad practice to return Enums in Java?

Lets say I have a class to model an item in a game like so:
public class Item {
private final EnumItem type;
public Item(EnumItem type) {
this.type = type;
}
public Item(String name) {
this.type = EnumItem.fromName(name);
}
}
public enum EnumItem {
MACHINE_GUN("machine_gun"),
SWORD("sword"),
BAT("bat"),
DEFAULT("default");
private final String name;
public EnumItem(name) {
this.name = name;
}
public String getName() { return name; }
public static EnumItem fromName(String name) {
for(EnumItem i: EnumItem.values()) {
if(i.name.equals(name)) {
return i;
} else {
return EnumItem.DEFAULT;
}
}
}
}
Assume that .equals() and .hashCode() of Item are overridden correctly to compare the internal Enum.
Now I want a way to distinguish these items with a getter in Item: should I return an Enum or the String name? Is it good practice to return an Enum in general? Or is there a better way to distinguish these Items? Because returning the enum kind of looks like exposing the rep to me and I don't want my colleagues to use EnumItem directly to compare Items.
The approaches I thought of are the following:
string getName() to do something like item1.getName().equals("machine_gun");
EnumItem getEnum() to do item1.getEnum().equals(EnumItem.MACHINE_GUN);
item1.equals(new Item("machine_gun"));
static name(String name) { new Item(name) } to do item1.equals(Item.name("machine_gun"));
I don't know what should I do, I'd appreciate some insight from experienced programmers.
I know they look like they would from context, but in my use case these items have no special functionality that would justify extending from the base Item class.
Is this good practice? Sure, you're using aggregation since Item doesn't depend on EnumItem, which is fine. That being said, could it be done better? Sure. Is the alternative I provide the only solution? No.
Alternative
If you want this to be extensible, consider using an interface to represent an item. Then allow the interface to extend this interface to provide some standard types. Alternatively you could use composition or aggregation to define a type inside EnumItem that implements the Item interface to ensure that equals/hashcode for the Item are always override and adhere to some contract.
interface Item {
String key();
}
enum EnumItem implement Item {
private final String key;
EnumItem(String key) {
this.key = key;
}
#Override
public String key() {
return key;
}
}
class AbstractItem implements Item {
// constructor, override name()
}
Item item = EnumItem.FOO_BAR;
Item item2 = new AbstractItem("FooBar");
Item item3 = () -> "FooBar";

Java compare the elements of a class

I have a class name "Users" and have 2 elements (int)userId and (String)userName.
Let's said
Users obj1 = new Users(10, "User1");
Users obj2 = new Users(11, "User2");
So I want to compare obj1 to obj2
element by element
10 compare to 11,
"User1" compare to "User2".
From the research i do from web. It looks like impossible to do it whether convert it to 2d array to compare or whatever method. Is there any method to do this kind of things?
I actually want to do an audit trail so i have the object before changes and after changes, so whatever element that have changed will insert a new record in the audit_trail table with the before value and after value.
I'm a newbie to programming i tried my best to think a way but it just doesn't work. Is there any other way of doing this by SQL? i using ng-admin as (front-end) and API java http to do a update (back-end).
You need to implement the Comparable<Users> interface. If you want equality check too, then you have to override
boolean equals(Object)
and
int hashCode()
Read:
https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html
and
Why do I need to override the equals and hashCode methods in Java?
From your question, We can compare two different objects.
Please implement the equals method to do your operations available in Comparable<Users>.
Let's say as a example below,
Class obj1 = new Class(1, "raja");
Class obj2 = new Class(2, "thiru");
The id and name are a public variable of the class. Then
override the function as,
public boolean equals(Object obj)
{
return (this.id == obj.id && this.name.equals(obj.name.equals));
}
Thanks.
You should override the .equals() method, making your Users class as follows:
public class Users {
private int mId;
private String mName;
public Users(int pId, String pName) {
mId = pId;
mName = pName;
}
public int getId() {
return mId;
}
#Override
public boolean equals(Object pObject) {
return (pObject instanceof Users && ((Users) pObject).getId() == mId);
}
}
I'd probably create a BeanDelta object
public class PropertyDelta {
private String propertyName;
private Object value1;
private Object value2;
// constructor & getters
}
public class BeanDelta<T> {
private Class<T> type;
private List<PropertyDelta> propertyDeltas = new ArrayList<>();
public BeanDelta(Class<T> type) {
this.type = type;
}
// getters
}
Then you could write a reflection based method
public <T> BeanDelta<T> getDelta(T o1, T, o2) {
Class<T> type = o1.getClass();
Method[] methods = type.getMethods();
BeanDelta<T> delta = new BeanDelta<>(type);
for (Method meth : methods) {
boolean isGetter = method.getParameterTypes().length == 0 && !method.getReturnType().equals(void.class) && meth.getName().startsWith("get");
if (isGetter) {
Object v1 = meth.invoke(o1);
Object v2 = meth.invoke(o2);
if (!Objects.equal(v1, v2)) {
String propertyName = meth.getName().substring(3);
delta.propertyDeltas.add(new PropertyDelta(propertyName, v1, v2));
}
}
}
return delta;
}
Check it out the solution proposed for do that.
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example

Get command of a hashmap with a parameter different than the key in the hashmap?

I have a custom class as my key in my hashmap like so
// In the main function
HashMap<Drink, boolean> drinkMap = new HashMap<>();
// What I would like to be able to do:
drinkMap.get("beer");
// My drink Class which is used as the key
public class Drink implements Comparable<String> {
private String name;
private String info;
public String getName() {
return Name;
}
public Drink(String name, String info) {
this.name = name;
this.info = info;
}
}
What I want to do is have the get method for the hashmap compare the string that is passed in to Drink.name and if they are the same then return that hashmap entry, but I cannot figure out how to get this to work.
I tried implementing the equals and hashcode methods in my Drink class like so:
#Override
public int hashCode() {
return Name.hashCode();
}
#Override
public boolean equals(Object o) {
return o instanceof String && o.equals(Name);
}
But when I would do hashMap.get("beer") it kept returning null even though I know there exists a Drink object with the name "beer" in the map.
This is a terrible idea. You should always query a map with the same type (or a subtype thereof) as the intended key. Not doing that only opens you up to problems (as I'm sure you've started to notice).
You should consider either making the key of your map a String type, or querying your map by Drink.
(As to why your specific case isn't working: "beer".equals(drink) != drink.equals("beer").)

How to sort a Java list of objects using a RuleBasedCollator on a field?

I want to sort an ArrayList of objects on a specific field using a given RuleBasedCollator.
For example, we have a list of Thing objects:
public Thing {
public String name;
public String type;
}
List<Thing> things = new ArrayList<Thing>();
RuleBasedCollator ruleBasedCollator = new RuleBasedCollator("< Table < Plate < Fork < Knife");
Now, after having created Thing objects and added them to the things list, I want to sort this list, getting first things of type "table" and last things of type "knife".
Does anyone know how to do it?
You can try something like this, instead of using compareTo in compare method of Comparator you can call RuleBasedCollator's compare.
mQueue.sort((o1, o2) -> {
if (o1.getDescription().getTitle() != null && o2.getDescription().getTitle() != null) {
return mRuleBasedCollator.compare(o1.getDescription().getTitle().toString(),
o2.getDescription().getTitle().toString());
} else {
return 0;
}
});
As far as I understand a RuleBaseCollator is intended for sorting Strings, at least i says so in the Collator class which is the super class. I would instead use a Comparator, something like this:
public class ThingSorter {
public enum ThingType{
//wanted sort order, sort on ordinal :
//Table < Plate < Fork < Knife
TABLE, PLATE, FORK, KNIFE
}
public static class Thing {
private String name;
private ThingType type;
public Thing(String name, ThingType tt) {
this.name = name;
type = tt;
}
public String toString() {
return name + " [" + type + "]";
}
}
public static class MyThingComparator implements Comparator<Thing> {
#Override
public int compare(Thing t1, Thing t2) {
return t1.type.ordinal() - t2.type.ordinal();
}
}
public static class MyReverseThingComparator implements Comparator<Thing> {
#Override
public int compare(Thing t1, Thing t2) {
return t2.type.ordinal() - t1.type.ordinal();
}
}
public static void main(String[] args) throws ParseException {
List<Thing> things = new ArrayList<Thing>();
things.add(new Thing("One", ThingType.KNIFE));
things.add(new Thing("Two", ThingType.FORK));
things.add(new Thing("Three", ThingType.PLATE));
things.add(new Thing("Four", ThingType.TABLE));
System.out.println("unsorted:\n" + things);
Collections.sort(things, new MyThingComparable());
System.out.println("sorted:\n" + things);
Collections.sort(things, new MyReverseThingComparable());
System.out.println("sorted:\n" + things);
}
}
The names are are not involved in the sorting in this case just the type (and the ordinal in the type)
You could certainly use the TreeMap or enum as the previous answers suggest; a rather simpler alternative is to use just a custom compatator, without the enum. If you're using Java 8 you can get it down to a single line:
Collections.sort(things,
(Thing t1, Thing t2)->ruleBasedCollator.compare(t1.type, t2.type) );
The pre-8 version would do the same thing with an anonymous Comparator
I finally found a solution using a TreeMap. I use the "type" property for the key and a list of Thing for the value. Instead of using a RuleBasedCollator, I created a ListBasedCollator extending Collator, because RuleBasedCollator rules work on characters but not on words.
public class ListBasedCollator extends Collator {
private List<String> list;
public ListBasedCollator(String[] array) {
list = Arrays.asList(array);
}
#Override
public int compare(String source, String target) {
if(!list.contains(target)) {
return 1;
}
if(!list.contains(source)) {
return -1;
}
return Integer.valueOf(list.indexOf(source)).compareTo(Integer.valueOf(list.indexOf(target)));
}
#Override
public CollationKey getCollationKey(String source) {
return null;
}
#Override
public int hashCode() {
return 0;
}
}
Here is how I construct the TreeMap:
String[] sortingList = {"TABLE", "PLATE", "FORK", "KNIFE"};
ListBasedCollator listBasedCollator = new ListBasedCollator(sortingList);
Map<String, List<Thing>> thingMap = new TreeMap<String, List<Thing>>(listBasedCollator);
So, the thingMap will always be sorted by type using the listBasedCollator.
And I can also sort alphabetically the list of things for each different type.

Remove duplicate objects from a ArrayList in Android

I know this has be discussed over and over again here, but none of the examples I've tried worked for me.
What I've got
I access the Call log from Android and I get a list of all calls made. Of course, here I get a lot of duplicates.
First I make a List
List<ContactObject> lstContacts = new ArrayList<ContactObject>();
Then I add objects into it
While (get some record in call log)
{
ContactObject contact = new ContactObject();
contact.SetAllProperties(......)
lstContacts.add(contact);
}
Set<ContactObject> unique = new LinkedHashSet<ContactObject>(lstContacts);
lstContacts = new ArrayList<ContactObject>(unique);
The Contact Object class is simple
public class ContactObject {
public ContactObject() {
super();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof ContactObject))
return false;
return this.lstPhones == ((ContactObject) obj).getLstPhones();
}
#Override
public int hashCode() {
return lstPhones.hashCode();
}
private long Id;
private String name;
private List<String> lstPhones;
private String details;
//... getters and settres
}
What I need
I need to have a Contact only once in the list. As I've read around here there are a couple of things that can be done like Set, HashSet, TreeSet. TreeSet seems the best as it keeps the order just as I receive it from the Call log. I've tried to make my code work with it but no success. Could anyone be so kind to give me a sample code based on my example. Thank you for your time.
The Working Solution. Thank you all for your support, you've made my day.
In ContactObject override the two methods
#Override
public boolean equals(Object obj) {
if (!(obj instanceof ContactObject))
return false;
return lstPhones.equals(((ContactObject) obj).getLstPhones());
}
#Override
public int hashCode() {
return (lstPhones == null) ? 0 : lstPhones.hashCode();
}
//Getters and Setters and COnstructor....
Simply use it as
Set<ContactObject> unique = new LinkedHashSet<ContactObject>(lstContacts);
lstContacts = new ArrayList<ContactObject>(unique);
LinkedHashSet which keeps insertion-order can be used in your case.
HashSet: no order.
TreeSet: sorted set, but not keep insertion order.
EDIT: As Software Monkey commented, hashCode() and equals() should be overwritten in ContactObject to fit the hash-based Set.
Remove duplication of Custom Object
Example of Removing duplicate using Comparator
Lets suppose you have a class "Contact"
public class Contact implements Comparable<Contact> {
public String getName() {
return this.Name;
}
public void setName(String name) {
this.Name = name;
}
public String getNumber() {
return this.Number;
}
public void setNumber(String number) {
this.Number = number;
}
///// this method is very important you must have to implement it.
#Override
public String toString() {
return "\n" +"Name=" + name + " Number=" + Number;
}
Here is how you can remove duplicate entries using Set , just pass your list in the function and it will work for you. New list will be returned which will have no duplicated contacts.
public ArrayList<Contact> removeDuplicates(ArrayList<Contact> list){
Set<Contact> set = new TreeSet(new Comparator<Contact>() {
#Override
public int compare(Contact o1, Contact o2) {
if(o1.getNumber().equalsIgnoreCase(o2.getNumber())){
return 0;
}
return 1;
}
});
set.addAll(list);
final ArrayList newList = new ArrayList(set);
return newList;
}
It worked for me so please try and give me your feedback. Thanks
P.S: Credit goes to Nilanchala at this article
For sure you can use TreeSet to store only once but a common mistake is do not override hashCode() and equal() methods:
This can fit for you:
public boolean equals(Object obj) {
if (!(obj instanceof ContactObject))
return false;
return this.id == ((ContactObject) obj).getId(); // you need to refine this
}
public int hashCode() {
return name.hashCode();
}
List<ContactObject> listContacts = new ArrayList<ContactObject>();
//populate...
//LinkedHashSet preserves the order of the original list
Set<ContactObject> unique = new LinkedHasgSet<ContactObject>(listContacts);
listContacts = new ArrayList<ContactOjbect>(unique);
Use Set's instead.
Set's works as an Mathematical collection, so it doesn't allow duplicated elements.
So it checks the equality and the .equals() methods for each element each time you add an new element to it.

Categories

Resources