I have a complex entity structure. Which contains the ID of the previous item ("previodElementId")
interface IPreviousElementEntity<PK> {
public void setId(PK id);
public PK getId();
public void setPreviousElementId(PK previousElementId);
public PK getPreviousElementId();
}
After receiving all entities from DB, I need to convert the resulting list into a linked list, and the linked list should be organized by the previous id.
I wrote the following code for conversion:
static <T extends IPreviousElementEntity> LinkedList<T> getLinkedListByPreviousId(Collection<T> collection) {
LinkedList<T> linkedList = new LinkedList<>();
if (collection == null || collection.isEmpty())
return linkedList;
// first find root element
collection.stream()
.filter(element -> element.getPreviousElementId() == null)
.forEach(linkedList::add);
if (linkedList.isEmpty()) return linkedList;
// TODO: convert to use stream. Please help!
Boolean isRun = true;
while (isRun) {
for (T element : collection) {
isRun = false;
if (linkedList.getLast().getId().equals(element.getPreviousElementId())) {
linkedList.add(element);
isRun = true;
break;
}
}
}
return linkedList;
}
But this code is terrible! Is it possible to write all these transformations on a stream? I especially want to get rid of the thundering while loop.
My full code:
import java.util.*;
public class App {
public static void main(String[] args) {
Entity entity1 = new Entity(3L, 2L, "third");
Entity entity2 = new Entity(2L, 1L, "second");
Entity entity3 = new Entity(4L, 3L, "forth");
Entity entity4 = new Entity(1L, null, "first");
List<Entity> entities = new ArrayList<>();
entities.add(entity1);
entities.add(entity2);
entities.add(entity3);
entities.add(entity4);
LinkedList<Entity> linkedListByPreviousId = getLinkedListByPreviousId(entities);
System.out.println(linkedListByPreviousId);
}
private static <T extends IPreviousElementEntity> LinkedList<T> getLinkedListByPreviousId(Collection<T> collection) {
LinkedList<T> linkedList = new LinkedList<>();
if (collection == null || collection.isEmpty())
return linkedList;
// first find root element
collection.stream()
.filter(element -> element.getPreviousElementId() == null)
.forEach(linkedList::add);
if (linkedList.isEmpty()) return linkedList;
//TODO: convert to use stream. Please help!
Boolean isRun = true;
while (isRun) {
for (T element : collection) {
isRun = false;
if (linkedList.getLast().getId().equals(element.getPreviousElementId())) {
linkedList.add(element);
isRun = true;
break;
}
}
}
return linkedList;
}
}
interface IPreviousElementEntity<PK> {
public void setId(PK id);
public PK getId();
public void setPreviousElementId(PK previousElementId);
public PK getPreviousElementId();
}
class Entity implements IPreviousElementEntity<Long> {
private Long id;
private Long previousElementId;
private String name;
public Entity(Long id, Long previousElementId, String name) {
this.id = id;
this.previousElementId = previousElementId;
this.name = name;
}
#Override
public Long getId() {
return id;
}
#Override
public void setId(Long id) {
this.id = id;
}
#Override
public Long getPreviousElementId() {
return previousElementId;
}
#Override
public void setPreviousElementId(Long previousElementId) {
this.previousElementId = previousElementId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entity entity = (Entity) o;
return Objects.equals(id, entity.id) &&
Objects.equals(previousElementId, entity.previousElementId) &&
Objects.equals(name, entity.name);
}
#Override
public int hashCode() {
return Objects.hash(id, previousElementId, name);
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Entity{");
sb.append("id=").append(id);
sb.append(", previousElementId=").append(previousElementId);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
The while loop is nasty because it attempts to do an O(n^2) operation using a list, and continually repeating until there are no more options.
An O(n) operation is more suitable, through the use of a Map using previousElementId as a key.
if (linkedList.isEmpty()) return linkedList;
//create a map with previousElementId as Key, T as Object
Map<Integer, T> map = collection.stream().collect(
Collectors.toMap(T::getPreviousElementId, Function.identity()));
//we fetch nodes using the current ID as the key
T node = map.get(linkedList.getLast().getId());
while(node != null) {
linkedList.add(node);
node = map.get(node.getId());
}
You have a common use case and I think you should be able to come up with cleaner solution using streams.
Here is approach with stream only:
private static < T extends IPreviousElementEntity<?> > LinkedList<T> getLinkedListByPreviousId(
Collection<T> collection) {
//first create map with previous id mapped to element, this assumes
//whatever id you use has proper implementation of equals and hashCode
Map<?, T> map = collection.stream()
.collect(
Collectors.toMap(
IPreviousElementEntity::getPreviousElementId, Function.identity(),
(i1, i2) -> i1 ) );
//then create infinite stream which starts with element that has null previous id
//and moves on to the next element that points to it via previous id
//since this is an infinite stream we need to limit it by the number of elements in the map
return Stream
.iterate( map.get(null), i -> map.get( i.getId() ) )
.limit( map.size() )
.collect( Collectors.toCollection(LinkedList::new) );
}
Related
I have a Collection<Event>. Event looks something like this
public class Event {
private Integer id;
private LocalDateTime localDateTime;
// getters, setters omitted
}
Each Event in that Collection needs to have a unique id and localDateTime. How can I do that check using stream API and return true if the condition is satisfied?
This is the Event class you would need;
class Event {
private Integer id;
private LocalDateTime localDateTime;
public Integer getId() {
return id;
}
public LocalDateTime getLocalDateTime() {
return localDateTime;
}
public void setId(Integer id) {
this.id = id;
}
public void setLocalDateTime(LocalDateTime localDateTime) {
this.localDateTime = localDateTime;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Event event = (Event) o;
return id.equals(event.id) &&
localDateTime.equals(event.localDateTime);
}
#Override
public int hashCode() {
return Objects.hash(id, localDateTime);
}
public static boolean hasDuplicates(List<Event> events) {
/*
return events
.stream()
.noneMatch(e -> events
.stream()
.filter(ev -> ev.equals(e)).count() > 1);
*/
return events.stream()
.distinct()
.count() != events.size(); // Kudos to #Holger for this approach.
}
}
This hasDuplicates is a static function, so it won't make any affect for your object creation. You can use it as a utility method to check duplicates though. You would only need single line of check.
First thing first, override the method equals() and hashcode() and write it like so:
#Override
public boolean equals(Object o){
if (o instanceof Event){
Event e = (Event) o;
return e.id.equals(this.id) && e.localDateTime.equals(this.localDateTime);
}
else return false;
}
#Override
public int hashCode(){
return Objects.hash(id,localDateTime); //this is the default implementation, up to you to implement it in a better way
}
Then, you can use the stream like this to check if there are any duplicates:
public boolean checkAllUnique(Collection<Event> col){
return col.stream().allMatch(new HashSet<>()::add);
}
import java.util.concurrent.atomic.AtomicInteger;
public class Event {
private static final AtomicInteger idGenerator = new AtomicInteger(1000);
private Integer id;
private LocalDateTime localDateTime;
public Event(){
id = idGenerator.getAndIncrement();
}
// getters, setters omitted
}
Code Test
public class Test {
public static void main(String[] args) {
for(int i = 0; i < 10; ++ i){
System.out.println(new Event().getId());
}
}
}
Output
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
You can use two HashSet as backing collections to store unique elements and iterate over the list of events as:
public boolean duplicateExists(List<Event> eventList) {
Set<Integer> ids = new HashSet<>();
Set<LocalDateTime> localDateTimes = new HashSet<>();
return eventList.stream()
.anyMatch(event -> !ids.add(event.getId()) ||
!localDateTimes.add(event.getLocalDateTime()));
}
This is my VO
public class SomeVO {
private String name;
private String usageCount;
private String numberofReturns;
private String trendNumber;
private String nonTrendNumber;
private String trendType;
private String auditType;
public SomeVO(String name,String usageCount,String numberofReturns,String trendNumber,String nonTrendNumber,String trendType,String auditType){
this.name = name;
this.usageCount = usageCount;
this.numberofReturns = numberofReturns;
this.trendNumber = trendNumber;
this.nonTrendNumber = nonTrendNumber;
this.trendType = trendType;
this.auditType = auditType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsageCount() {
return usageCount;
}
public void setUsageCount(String usageCount) {
this.usageCount = usageCount;
}
public String getNumberofReturns() {
return numberofReturns;
}
public void setNumberofReturns(String numberofReturns) {
this.numberofReturns = numberofReturns;
}
public String getTrendNumber() {
return trendNumber;
}
public void setTrendNumber(String trendNumber) {
this.trendNumber = trendNumber;
}
public String getNonTrendNumber() {
return nonTrendNumber;
}
public void setNonTrendNumber(String nonTrendNumber) {
this.nonTrendNumber = nonTrendNumber;
}
public String getTrendType() {
return trendType;
}
public void setTrendType(String trendType) {
this.trendType = trendType;
}
public String getAuditType() {
return auditType;
}
public void setAuditType(String auditType) {
this.auditType = auditType;
}
}
Here is my values
List<SomeVO> myList = new ArrayList<SomeVO>();
SomeVO some = new SomeVO("A","0","0","123","123","Trend","AuditX");
myList.add(some);
some = new SomeVO("B","1","1","234","234","Non trend","AuditX");
myList.add(some);
some = new SomeVO("C","0","2","345","345","Trend","AuditX");
myList.add(some);
some = new SomeVO("D","2","3","546","546","Trend","AuditX");
myList.add(some);
some = new SomeVO("E","2","4","678","678","Non trend","AuditX");
myList.add(some);
some = new SomeVO("F","0","0","123","123","Non trend","AuditA");
myList.add(some);
some = new SomeVO("G","0","0","123","123","Trend","AuditB");
myList.add(some);
Here is my comparator
public String currentAudit = "AuditX";
public class AuditComparator implements Comparator<SomeVO> {
#Override
public int compare(SomeVO o1, SomeVO o2) {
if(currentAudit.equalsIgnoreCase(o1.getAuditType()) && currentAudit.equalsIgnoreCase(o2.getAuditType())) {
int value1 = o2.getUsageCount().compareTo(o1.getUsageCount());
if (value1 == 0) {
int value2 = o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
if(o1.getTrendType().equalsIgnoreCase("Trend") && o2.getTrendType().equalsIgnoreCase("Trend")) {
if (value2 == 0) {
return o1.getTrendNumber().compareTo(o2.getTrendNumber());
} else {
return value2;
}
} else {
if (value2 == 0) {
return o1.getNonTrendNumber().compareTo(o2.getNonTrendNumber());
} else {
return value2;
}
}
}
return value1;
} else {
return 1;
}
}
}
I am trying to sort the VO based on below conditions
First only set of values of currentAudit should be taken in to
consideration i.e., AuditX
a) then it should be sorted with
Usage count in descending order
b) if same usage count found then it
should be sorted with Return count in ascending order
c) if same
return count then it should check for trendType, if trendType
="Trend" then it should sort with Trend number otherwise nonTrend number.
then it should consider rest all auditType's and sorted with
a),b),c) condition as like currentAudit. I tried achieving it and i
ended up with only above comparator. Expected result: D, A, C, E,
F, G. But i get G,F,D,E,B,A,C. Please help me to update the
comparator above.
Your comparator does not meet a simple condition: it is not stateless. A following should always be true: A>B => B<A. In your case, in some scenarios A>B and B>A.
I resolved it by splitting the actual list in to 2 list based on AuditX and rest in another list. Then used below comparator one by one, and then merged in to a result list. Works good.
for(SomeVO some:myList) {
if(some.getAuditType().equalsIgnoreCase("AuditX")) {
auditX.add(some);
} else {
auditY.add(some);
}
}
Collections.sort(auditX, new AuditComparator());
Collections.sort(auditY, new AuditComparator());
public class AuditComparator implements Comparator<SomeVO> {
#Override
public int compare(SomeVO o1, SomeVO o2) {
int value1 = o2.getUsageCount().compareTo(o1.getUsageCount());
if (value1 == 0) {
int value2 = o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
if (value2 == 0) {
return (o1.getTrendType().equalsIgnoreCase("Trend") && o2.getTrendType().equalsIgnoreCase("Trend")) ?
o1.getTrendNumber().compareTo(o2.getTrendNumber()):o1.getNonTrendNumber().compareTo(o2.getNonTrendNumber());
} else {
return value2;
}
}
return value1;
}
The return 1 at the bottom of the comparator makes a bug.
The comparator shall only return 1 if the second element is bigger than the first one, but if they're different, you always return 1, so the very first sorting criteria will be messy.
// a helper for case insensitive comparison
private int compareIgnoreCase(String o1,String o2) {
return o1.toLowercase.compareTo(o2.toLowercase());
}
#Override
public int compare(SomeVO o1, SomeVO o2) {
int result=compareIgnoreCase(o1.getAuditType(),o2.getAuditType());
if (result==0) {
// we need to go to the 2nd criteria
result=o2.getUsageCount().compareTo(o1.getUsageCount());
}
if (result==0) {
// ok, 1st and 2nd criteria was the same, go to the 3rd
result=o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
}
if (result==0) {
// check trends
...
}
return result;
}
I found that this representation of multiple comparison criteria makes the code much easier to follow. We first do the highest priority of comparison, and go on with further comparions if the previous comparisons returned that the two elements are the same (i.e. result is still zero).
In case you need to make a descending sorting at some level, simply put a -, e.g.:
result=-o1.something.compareTo(o2.something)
It is a good idea to have only one exit point in a method (this also makes easier to follow what is happening).
#Entity
#NamedQueries({
#NamedQuery(
name = "FolderNode.findByName",
query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode = :parentNode"),
#NamedQuery(
name = "FolderNode.findRootNodeByName",
query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode is null")
})
public class FolderNode extends InstructorTreeNode {
public FolderNode() {
super();
}
public FolderNode(String name) {
this();
setName(name);
}
public FolderNode(int sortOrder, String name) {
this(name);
this.sortOrder = sortOrder;
}
public FolderNode(int sortOrder, String name, EmployeeState status) {
this(sortOrder, name);
this.status = status;
}
public static FolderNode addWaitingListNode(String name) {
EntityManager em = getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListNode = getWaitingListFolder();
FolderNode folderNode = new FolderNode(0, name);
waitingListNode.addChild(folderNode);
em.merge(waitingListNode);
em.getTransaction().commit();
em.close();
return folderNode;
}
public static void addWaitingListStudent(String waitingList, Student s) {
EntityManager em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListsNode = getWaitingListFolder();
FolderNode waitingListNode = getDao().findFolderNodeByName(waitingListsNode, waitingList);
waitingListNode.addChild(new EmployeeLeaf(s.getInmate()));
em.merge(waitingListNode);
em.getTransaction().commit();
em.close();
}
public static FolderNode getAMClassFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "AM Class");
}
public static FolderNode getAttendanceFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Employee Attendance");
}
public static FolderNode getFormerParaprosFolder() {
return getDao().findFolderNodeByName(getParaprosFolder(), "Former");
}
public static FolderNode getFormerStudentsFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "Former");
}
public static FolderNode getPMClassFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "PM Class");
}
public static FolderNode getParaprosFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Parapros");
}
public static FolderNode getPendingStudentsFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Pending Students");
}
public static FolderNode getRootFolder() {
return getDao().findFolderNodeByName(null, EducationPreferences.getInstructor().getInstructorName());
}
public static FolderNode getStudentsFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Students");
}
public static FolderNode getWaitingListFolder(String name) {
FolderNode waitingListsNode = getWaitingListFolder();
return getDao().findFolderNodeByName(waitingListsNode, name);
}
public static FolderNode getWaitingListFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Waiting List");
}
public static void setClassFolder(Student aStudent, EntityManager entityManager) {
EntityManager em = entityManager;
if (entityManager == null) {
em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
}
EmployeeLeaf leaf = EmployeeLeaf.findActiveStudentLeaf(aStudent);
FolderNode node = aStudent.getShift() == Shift.AM ? getAMClassFolder() : getPMClassFolder();
leaf.setParentNode(node);
em.merge(leaf);
GlobalEntityMethods.updateHistory(leaf);
if (entityManager == null) {
em.getTransaction().commit();
em.close();
}
}
public static void transferWaitingListStudent(String currentFolder, String toFolder, Student student) {
EntityManager em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListsNode = getWaitingListFolder();
FolderNode currentWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, currentFolder);
EmployeeLeaf employeeLeaf = EmployeeLeaf.getDao().findWaitingListLeafByInmate(student.getInmate());
currentWaitingListNode.removeChild(employeeLeaf);
FolderNode toWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, toFolder);
toWaitingListNode.addChild(employeeLeaf);
em.merge(currentWaitingListNode);
em.merge(toWaitingListNode);
em.getTransaction().commit();
em.close();
}
public void addChild(InstructorTreeNode node) {
childNodes.add(node);
node.setParentNode(this);
}
public List<InstructorTreeNode> getChildNodes() {
Collections.sort(childNodes);
return childNodes;
}
#Override
public Set<Inmate> getInmates() {
Set<Inmate> inmateSet = new HashSet<> (50);
for (InstructorTreeNode node: getChildNodes()) {
inmateSet.addAll(node.getInmates());
}
return inmateSet;
}
public int getSortOrder() {
return sortOrder;
}
public EmployeeState getStatus() {
return status;
}
#Override
public List<InstructorTreeNode> getTree() {
List <InstructorTreeNode> result = new ArrayList<> (25);
for (InstructorTreeNode childNode: getChildNodes()) {
if (childNode instanceof FolderNode) {
result.add(childNode);
}
result.addAll(childNode.getTree());
}
return result;
}
#Override
public JPanel getView(EmployeeViewController controller) {
if ("Employee Attendance".equals(getName())) {
return new AttendanceView();
} else if ("Waiting List".equals(getName())) {
return new AllWaitingListsPanel(controller);
} else if (getParentNode().getName().equals("Waiting List")) {
return new WaitingListPanel(controller);
} else if ("Pending Students".equals(getName())) {
return new PendingStudentsPanel(controller);
} else if ("Students".equals(getName())) {
return new AllStudentsPanel(controller);
} else if ("AM Class".equals(getName())) {
return new AllStudentsPanel(controller, Shift.AM);
} else if ("PM Class".equals(getName())) {
return new AllStudentsPanel(controller, Shift.PM);
} else if (getParentNode().getName().equals("Students") && "Former".equals(getName())) {
return new FormerStudentsPanel(controller);
} else if ("Parapros".equals(getName())) {
return new AllParaprosPanel(controller);
} else if (getParentNode().getName().equals("Parapros") && "Former".equals(getName())) {
return new FormerParaprosPanel(controller);
}
throw new UnsupportedOperationException("unknown folder");
}
public void removeChild(InstructorTreeNode node) {
childNodes.remove(node);
node.setParentNode(null);
}
public void removeEmployeeLeaf(Inmate inmate) {
for (InstructorTreeNode node: childNodes) {
if (node instanceof EmployeeLeaf) {
EmployeeLeaf employeeLeaf = (EmployeeLeaf) node;
if (employeeLeaf.getInmate().equals(inmate)) {
childNodes.remove(employeeLeaf);
break;
}
}
}
}
public void setChildNodes(List<InstructorTreeNode> childNodes) {
this.childNodes = childNodes;
}
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
public void setStatus(EmployeeState status) {
this.status = status;
}
#OneToMany(mappedBy = "parentNode", cascade = CascadeType.ALL, orphanRemoval = true)
private List<InstructorTreeNode> childNodes;
private int sortOrder;
#Enumerated(EnumType.STRING)
private EmployeeState status;
}
#Entity
#Table(catalog = "education", name = "instructortreenode", uniqueConstraints = #UniqueConstraint(columnNames = {
"PARENTNODE_ID", "NAME"
}))
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class InstructorTreeNode implements Comparable<InstructorTreeNode> {
public InstructorTreeNode() {
super();
}
public static InstructorTreeNodeDAO getDao() {
return dao;
}
#Override
public int compareTo(InstructorTreeNode o) {
if (o instanceof FolderNode && this instanceof FolderNode) {
FolderNode thisFolder = (FolderNode) this;
FolderNode otherFolder = (FolderNode) o;
if (thisFolder.getSortOrder() != otherFolder.getSortOrder()) {
return thisFolder.getSortOrder() - otherFolder.getSortOrder();
} else {
return thisFolder.getName().compareToIgnoreCase(otherFolder.getName());
}
} else if (o instanceof EmployeeLeaf && this instanceof EmployeeLeaf) {
return getName().compareToIgnoreCase(((InstructorTreeNode) o).getName());
}
return (o instanceof FolderNode) ? -1 : +1;
}
public int getCount() {
return getTree().size();
}
public abstract Set<Inmate> getInmates();
public String getName() {
return name;
}
public FolderNode getParentNode() {
return parentNode;
}
public abstract List<InstructorTreeNode> getTree();
public abstract JPanel getView(EmployeeViewController theController);
public void setName(String name) {
this.name = name;
}
public void setParentNode(FolderNode parentNode) {
this.parentNode = parentNode;
}
#Override
public String toString() {
return name;
}
private static final InstructorTreeNodeDAO dao = new InstructorTreeNodeDAO();
private String name;
#ManyToOne
private FolderNode parentNode;
}
Here is my problem:
The Collections.sort line works just fine in Java 8u5 and before, but
in Java 8u20 they seem to have changed the code for Collections.sort
and it no longer uses anything but the natural order, even if you specify
a Comparator.
Should I be using another method to sort my list, or is there an error in
Collections.sort.
Any help would be much appreciated, as this is driving me crazy.
I forgot to say that this code does not use a specified comparator, but according to the documentation it is supposed to use the CompareTo, if your class implements Comparable, which is what I am using.
I tried also specifying a comparator, but it did not work either.
Since Collections.sort now delegates to List.sort, the actual List implementation has an impact. Implementations like ArrayList and Vector take the opportunity to implement List.sort in a more efficient manner than the default implementation as they pass their internal array directly to Arrays.sort omitting the copy steps of the default implementation.
This works seamlessly unless programmers use the anti-pattern of subclassing an implementation (rather than using delegation) overriding methods to implement a contradicting behavior. Lazily populated lists like these from EclipseLink/JPA are known to have problems with this as they try to intercept every reading method to populate the list before proceeding but miss the new sort method. If the list hasn’t populated yet when sort is called, sort will see an empty list state.
In your code, there is no indication where the list does come from and which actual implementation class it has, but since I see a lot of familiar looking annotations, I guess, you are using such a framework…
If you use the method Collections#sort(List<T> list), it defers to the method List#sort(Comparator comparator) with comparator given as null. The source code from java.util.Collections is as follows:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
If you want to specify your own Comparator, you need to use the method Collections#sort(List<T> list, Comparator<T> comparator), which passes on your comparator to the list sorting method. The source code from java.util.Collections is as follows:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
So far so good. Now, as you have correctly pointed out, if you do not specify a comparator, the natural ordering of the class, that is, the compareTo method you have defined, is used.
However, the Comparable class documentation also states the following:
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
Since the class InstructorTreeNode does not override Object#equals, your compareTo method may return 0 even if == returns false. I reckon this is leading to what the documentation calls "strangely".
You might not like this answer because it doesn't give you a quick-fix for your situation, but it will help you more in the long run.
This is the kind of bug that you can figure out yourself with a little debugging. I don't know what IDE you are using, but with Eclipse, you can even step into code that is in the JDK!
So, what I would do, is set a breakpoint at the line where you call sort() on the childNodes. Then I would step into the JDK code and just walk through it myself. It will become very clear what is going on and why it isn't calling your compare function.
You could try to build a custom comparator. Here is an example how that should look. This is for comparing BigDecimals.
class YourComparator implements Comparator<InstructorTreeNode> {
#Override
public int compare(final InstructorTreeNode 01, final InstructorTreeNode o2) {
return o2.getYourCompVal().compareTo(o1.getYourCompVal());
}
}
public List<InstructorTreeNode> getChildNodes() {
Collections.sort(childNodes, new YourComparator());
return childNodes;}
I am trying to remove duplicate Set of custom objects from ArrayList. Below is the code I have written which uses toString representation of custom EmployeeObj to compare. Can you please suggest what other approaches can be taken?
package com.collections;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DupSetInsideList {
public static void main(String[] args) {
List<Set<EmployeeObj>> list = new ArrayList<Set<EmployeeObj>>();
Set<EmployeeObj> set1 = new HashSet<EmployeeObj>();
Set<EmployeeObj> set2 = new HashSet<EmployeeObj>();
Set<EmployeeObj> set3 = new HashSet<EmployeeObj>();
list.add(set1);
list.add(set2);
list.add(set3);
EmployeeObj empObj1 = new EmployeeObj(1, "Nikhil");
EmployeeObj empObj2 = new EmployeeObj(2, "Rakesh");
EmployeeObj empObj3 = new EmployeeObj(3, "Kunal");
set1.add(empObj1);
set1.add(empObj2);
set2.add(empObj1);
set2.add(empObj2);
set3.add(empObj1);
set3.add(empObj2);
set3.add(empObj3);
System.out.println("List with duplicaes: " + list);
//Output: List with duplicaes: [[1=Nikhil, 2=Rakesh], [1=Nikhil, 2=Rakesh], [3=Kunal, 1=Nikhil, 2=Rakesh]]
//Remove duplicates
List<Set<EmployeeObj>> nonDupList = new ArrayList<Set<EmployeeObj>>();
for(Set<EmployeeObj> obj1:list) {
if(!nonDupList.contains(obj1)) {
nonDupList.add(obj1);
}
}
System.out.println("List without duplicates: " + nonDupList);
//List without duplicates: [[1=Nikhil, 2=Rakesh], [3=Kunal, 1=Nikhil, 2=Rakesh]]
}
}
class EmployeeObj {
private int id;
private String name;
public int getId() {
return id;
}
public String getName() {
return name;
}
public EmployeeObj(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EmployeeObj other = (EmployeeObj) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
#Override
public String toString() {
return id + "=" + name;
}
}
If you want a list behaviour guaranteeing a uniqueness of elements use a LinkedHashSet instead of ArrayList.
If you are bound to using ArrayList (as in a student exercise), extend it, overwrite add and addAll methods by checking the uniqueness first and then calling super.add resp. super.addAll and then use the new class in place of ArrayList.
The Set interface specifies the equals method as follows:
Compares the specified object with this set for equality. Returns true if the specified object is also a set, the two sets have the same size, and every member of the specified set is contained in this set (or equivalently, every member of this set is contained in the specified set).
So in order to create a list that contains no duplicates (even if the elements in the list are sets), one can simply write
//Remove duplicates
List<Set<EmployeeObj>> nonDupList =
new ArrayList<Set<EmployeeObj>>(
new LinkedHashSet<Set<EmployeeObj>>(list));
Try this solution:
private boolean equals(Set elements, Set elements2) {
return elements != null && elements.equals(elements2);
}
private List<Set> removeDuplicates(List<Set> from) {
List<Set> noDuplicates = new ArrayList<Set>();
for (Set possibleDuplicate : from) {
boolean alreadyInNoDuplicatesList = false;
for (Set elementFromNoDuplicateList : noDuplicates) {
if (equals(elementFromNoDuplicateList, possibleDuplicate)) {
alreadyInNoDuplicatesList = true;
break;
}
}
if (!alreadyInNoDuplicatesList) {
noDuplicates.add(possibleDuplicate);
}
}
return noDuplicates;
}
Put the Sets in a Set and they'll be removed as if by magic!
In an attempt to create a N-ary tree with multiple node with different type of node objects[Country | State etc], I tried modifying the below generic class from -
https://github.com/vivin/GenericTree/blob/master/src/main/java/net/vivin/GenericTreeNode.java
I tried the following -
package com.mycompany.ds;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GenericTreeNode<T>{
private T data;
private List<GenericTreeNode<? super T>> children;
private GenericTreeNode<? super T> parent;
public GenericTreeNode() {
super();
children = new ArrayList<GenericTreeNode<? super T>>();
}
public GenericTreeNode(T data) {
this();
setData(data);
}
public GenericTreeNode<? super T> getParent() {
return this.parent;
}
public List<GenericTreeNode<? super T>> getChildren() {
return this.children;
}
public int getNumberOfChildren() {
return getChildren().size();
}
public boolean hasChildren() {
return (getNumberOfChildren() > 0);
}
public void setChildren(List<GenericTreeNode<? super T>> children) {
for(GenericTreeNode<? super T> child : children) {
child.parent = this;
}
this.children = children;
}
public void addChild(GenericTreeNode<? super T> child) {
child.parent = this;
children.add(child);
}
public void addChildAt(int index, GenericTreeNode<T> child) throws IndexOutOfBoundsException {
child.parent = this;
children.add(index, child);
}
public void removeChildren() {
this.children = new ArrayList<GenericTreeNode<? super T>>();
}
public void removeChildAt(int index) throws IndexOutOfBoundsException {
children.remove(index);
}
public GenericTreeNode<? super T> getChildAt(int index) throws IndexOutOfBoundsException {
return children.get(index);
}
public T getData() {
return this.data;
}
public void setData(T data) {
this.data = data;
}
public String toString() {
return getData().toString();
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GenericTreeNode<?> other = (GenericTreeNode<?>) obj;
if (data == null) {
if (other.data != null) {
return false;
}
} else if (!data.equals(other.data)) {
return false;
}
return true;
}
/* (non-Javadoc)
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
public String toStringVerbose() {
String stringRepresentation = getData().toString() + ":[";
for (GenericTreeNode<? super T> node : getChildren()) {
stringRepresentation += node.getData().toString() + ", ";
}
//Pattern.DOTALL causes ^ and $ to match. Otherwise it won't. It's retarded.
Pattern pattern = Pattern.compile(", $", Pattern.DOTALL);
Matcher matcher = pattern.matcher(stringRepresentation);
stringRepresentation = matcher.replaceFirst("");
stringRepresentation += "]";
return stringRepresentation;
}
}
But errors in the following methods -
public void setChildren(List<GenericTreeNode<? super T>> children) {
for(GenericTreeNode<? super T> child : children) {
child.parent = this;
}
this.children = children;
}
public void addChild(GenericTreeNode<? super T> child) {
child.parent = this;
children.add(child);
}
Errors -
1 - Type mismatch: cannot convert from GenericTreeNode<T> to GenericTreeNode<? super capture#2-of ? super
T>
2 - Type mismatch: cannot convert from GenericTreeNode<T> to GenericTreeNode<? super capture#4-of ? super
T>
How can I fix these?
You could create a class / interface that represents a GISEntity and create the generic tree node whose generic type T extends GISEntity. This would allow you to have nodes of different kinds of GISEntity subclasses-- Country / State etc.
To build up on the answer of ditkin:
after having made all your classes implement or extend GISEntity, you would write your tree this way:
public class GenericTreeNode<T extends GISEntity>{
private T data;
private List<GenericTreeNode<? extends GISEntity>> children;
private GenericTreeNode<? extends GISEntity> parent;
public GenericTreeNode() {
super();
children = new ArrayList<GenericTreeNode<? extends GISEntity>>();
}
////////
......
////////
public void addChild(GenericTreeNode<? extends GISEntity> child) {
child.parent = this;
children.add(child);
}
public void addChildAt(int index, GenericTreeNode<? extends GISEntity> child) throws IndexOutOfBoundsException {
child.parent = this;
children.add(index, child);
}
////////
......
////////
}
Note that it will not really help you to avoid class casting. The thing is that as soon as you have added children to your node, when you retrieve them you just know that they are GISEntity, because of type erasure. So this technique only give you a bit of type safety.
It's not a good idea to use Generic in order to store different types of objects in the same collection. What you should do is to create an hierarchy and use it to store your objects. With a good design, the base class will have all that's necessary to access the different objects without casting; otherwise you will have to write some cast here and there. Here is an example of code (please note that the design here is far from beeing optimal and is simply to show the use of virtual function and polymorphism) :
static class GISEntity {
final String name;
public GISEntity (String name) { this.name = name; }
public String getName() { return name; }
public String getTypeName() { return "GISEntity"; }
public String toString() { return name; }
}
//
static class Country extends GISEntity {
final String typeName = "country";
public Country (String name) { super(name); }
public String getTypeName() { return typeName; }
public String toString() { return name; }
}
//
static class State extends GISEntity {
public State (String name) { super(name); }
public String getTypeName() { return "state"; }
public String toString() { return name; }
}
//
static class Territory extends GISEntity {
public Territory (String name) { super(name); }
public String getTypeName() { return "territory"; }
public String toString() { return name; }
}
//
// Here's an example of subclassing GenericTreeNode<GISEntity>:
//
static class IsATerritory extends GenericTreeNode<GISEntity> {
IsATerritory (String name) { super (new Territory (name)); }
public GISEntity getData() {
State s = new State (super.getData().getName().toUpperCase());
return s; }
};
//
// Here we put some data. Note that the order of insertion is important
// for the tree and that it's not alphabetical in this example.
//
GenericTree<GISEntity> earth = new GenericTree<GISEntity>() ;
//
GenericTreeNode<GISEntity> ListOfCountries = new GenericTreeNode<GISEntity>(new GISEntity("List of countries"));
//
GenericTreeNode<GISEntity> US = new GenericTreeNode<GISEntity>(new Country("United States"));
GenericTreeNode<GISEntity> Washington = new GenericTreeNode<GISEntity>(new State("Washington"));
GenericTreeNode<GISEntity> Florida = new GenericTreeNode<GISEntity>(new State("Florida"));
//
GenericTreeNode<GISEntity> Canada = new GenericTreeNode<GISEntity>(new Country("Canada"));
//
// We are now using some different ways for creating the nodes:
//
#SuppressWarnings("unchecked")
List<GenericTreeNode<GISEntity>> CanadaProvinces = new ArrayList<GenericTreeNode<GISEntity>>(
Arrays.asList(new GenericTreeNode<GISEntity>(new State("Quebec")),
new GenericTreeNode<GISEntity>(new State("Ontario")))
);
//
US.addChild(Washington);
US.addChild(Florida);
//
// Here's are two examples of subclassing; this time with anonymous classes.
// Don't forget that these two anonymous classes will hold an hidden reference
// to the outer classe as they are not static!
//
GenericTreeNode<GISEntity> alberta = new GenericTreeNode<GISEntity>() {
{ setData(new State ("Alberta")); }
public GISEntity getData() {
State s = new State (super.getData().getName().toUpperCase());
return s;
}
};
//
GenericTreeNode<GISEntity> saskatchewan = new GenericTreeNode<GISEntity>(new State ("saskatchewan")) {
public GISEntity getData() {
State s = new State (super.getData().getName().toUpperCase());
return s; }
};
//
CanadaProvinces.add(alberta);
CanadaProvinces.add(saskatchewan);
//
// Other ways for creating the nodes:
CanadaProvinces.add(new GenericTreeNode<GISEntity>(new State("Manitoba")));
//
// Note the use of the IsATerritory subclass:
CanadaProvinces.add(new IsATerritory("Northwest Territories"));
//
Canada.setChildren(CanadaProvinces);
//
ListOfCountries.addChild(Canada);
ListOfCountries.addChild(US);
//
earth.setRoot(ListOfCountries);
//
System.out.println(earth.toString());
System.out.println();
System.out.println(earth.toStringWithDepth());
System.out.println();
System.out.println(ListOfCountries.toStringVerbose());
//
List<GenericTreeNode<GISEntity>> loc = earth.build(GenericTreeTraversalOrderEnum.PRE_ORDER);
System.out.println(loc);
//
Map<GenericTreeNode<GISEntity>, Integer> locd = earth.buildWithDepth(GenericTreeTraversalOrderEnum.PRE_ORDER);
System.out.println(locd);
//
Map<GenericTreeNode<GISEntity>, Integer> locd2 = earth.buildWithDepth(GenericTreeTraversalOrderEnum.POST_ORDER);
System.out.println(locd2);
//
// Two examples of iteration; showing both the use of the instanceof operator
// and of virtual (or override) functions:
//
for (GenericTreeNode<GISEntity> gen: loc) {
GISEntity data = gen.getData();
if (data instanceof State) {
System.out.println("Is State: " + data.getName());
} else if (data instanceof Country) {
System.out.println("Is Country: " + data.getName());
} else {
System.out.println(data.getTypeName() + data.getName());
}
}
//
for (Entry<GenericTreeNode<GISEntity>, Integer> entry: locd.entrySet()) {
GISEntity data = entry.getKey().getData();
Integer depth = entry.getValue();
if (data instanceof State) {
System.out.println(depth.toString() + ": Is State: " + data.getName());
} else if (data instanceof Country) {
System.out.println(depth.toString() + ": Is Country: " + data.getName());
} else {
System.out.println(depth.toString() + ": " + data.getTypeName() + data.getName());
}
}
In this example, I have subclassed the class GenericTreeNode in three different ways (two anonymous classes, one a named class) in order to change the getData so that it will return a new GISEntity where the name has been replaced with its UpperCase copy.
Note that will all these three subclasses, I'm using GenericTreeNode<GISEntity> and not something like GenericTreeNode<Territory>. This is because that even if Territory is a subclass of GISEntry, the class GenericTreeNode<Territory> is not a subclass of GenericTreeNode<GISEntry>.
For using something like a mix of GenericTreeNode<Territory> with GenericTreeNode<GISEntry>, we have to use the ? extends GISEntry and ? super GISEntry and this will multiply by one thousand the complexity of the generic code. Unless that you want to make some heavy subclassing of the generic classes GenericTree<> and GenericTreeNode<>, it's totally useless to use the ? type; even for a collecting different types of objects. Unless that you have years of experience in generic code, don't use the ? notation. Most projects will do totally fine with the simpler generic code.
I've also added some examples of iterations over the generic tree for both the build() and the buildWithDepth() functions for those interested.
Finally, as a reference, this generic tree is explained in http://vivin.net/2010/01/30/generic-n-ary-tree-in-java/ (3 pages).