Common question: How to use different Comparators of the custom class for sorting sequence its objects in PriorityQueue?
I tried to do that using this comparators in appropriate pairs of priorityqueues and lists of the objects with expected similar sorting results in the next code:
class User{
private Integer id;
private String name;
public User(Integer i, String n){
this.id=i;
this.name=n;
}
public Integer getId() {return id;}
public String getName() {return name;}
#Override
public boolean equals(Object obj) {
if (this == obj)return true;
if (obj == null)return false;
if (getClass() != obj.getClass())return false;
User other = (User) obj;
if(id == null){
if (other.id != null)return false;
}else if(!id.equals(other.id))return false;
return true;
}
#Override
public String toString() {return "[id:" + id + ", name:" + name + "]";}
}
public class MyPriorityQueue {
public static Comparator<User> cmpId = Comparator.comparingInt(x -> x.getId());
public static Comparator<User> cmpNameLength = Comparator.comparingInt(x -> x.getName().length());
public static void main(String[] args) {
List<User> users = new ArrayList<User>(10);
users.add(new User(1,"11111"));
users.add(new User(3,"333"));
users.add(new User(5,"5"));
users.add(new User(4,"44"));
users.add(new User(2,"2222"));
Queue<User> ids = new PriorityQueue<User>(10, cmpId); //use first comparator
users.forEach(x-> ids.offer(x));
Queue<User> names = new PriorityQueue<User>(10, cmpNameLength); //use second comparator
names.addAll(users);
System.out.println("Variant_1.1:");
ids.forEach(System.out::println);
System.out.println("Variant_2.1:");
names.forEach(System.out::println);
System.out.println("Variant_1.2:");
users.sort(cmpId); //use first comparator
users.forEach(System.out::println);
System.out.println("Variant_2.2:");
users.sort(cmpNameLength); //use second comparator
users.forEach(System.out::println);
}
}
Output:
Variant_1.1: //Failed sorted queue by user.id with using comporator cmpId
[id:1, name:11111]
[id:2, name:2222]
[id:5, name:5]
[id:4, name:44]
[id:3, name:333]
Variant_2.1: //Failed sorted queue by length of the user.name with cmpNameLength
[id:5, name:5]
[id:4, name:44]
[id:3, name:333]
[id:1, name:11111]
[id:2, name:2222]
Variant_1.2: // OK: correctly sorted list by user.id with cmpId comporator
[id:1, name:11111]
[id:2, name:2222]
[id:3, name:333]
[id:4, name:44]
[id:5, name:5]
Variant_2.2: //OK: for list by length of the user.name with cmpNameLength
[id:5, name:5]
[id:4, name:44]
[id:3, name:333]
[id:2, name:2222]
[id:1, name:11111]
I expected that the:
results of the variant 1.1 and 2.1;
results of the variant 1.2 and 2.2;
will be same, but they were different.
My questions: What have I done wrong for ordering priorytyqueue/comparator and How to get sorting result for the priorityqueue as for the appropriate list in my example?
You haven't done anything wrong, it's just that PriorityQueue's iterator is:
not guaranteed to traverse the elements of the priority queue in any particular order (Javadoc)
The forEach method internally uses the iterator, so the same problem exists.
This is because the underlying data structure is such that it "sorts" as you deque items. If the implementor wanted to return items in sorted order, they would have had to first collect the items, and then sort them before returning them. This incurs a performance hit, and so (I presume) it was decided to return it unordered, because PriorityQueue is primarily a queue, rather than a sorted collection, and a user could always sort the item themselves (which is as efficient as it gets).
In order to obtain the elements ordered, do something like:
while(pq.peek() != null){
System.out.println(pq.poll());
}
In your code sample, you are using Iterable#forEach to iterate through the queues.
ids.forEach(System.out::println);
names.forEach(System.out::println);
forEach ultimately delegates into Iterable#iterator. However, it's important to note that the subclass override in PriorityQueue#iterator has different JavaDocs with a special note about ordering.
Returns an iterator over the elements in this queue. The iterator does not return the elements in any particular order.
In other words, there is no guarantee that iterating over a PriorityQueue will use your Comparator. If instead you changed your code to drain the queue by repeatedly calling PriorityQueue#poll, then I expect you'd see results ordered according to your custom Comparator.
Digging into the OpenJDK source, we can see that the internal data structure inside PriorityQueue is a binary heap. This is backed by an array, and as callers add and remove elements of the queue, the code internally maintains the heap invariant.
/**
* Priority queue represented as a balanced binary heap: the two
* children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The
* priority queue is ordered by comparator, or by the elements'
* natural ordering, if comparator is null: For each node n in the
* heap and each descendant d of n, n <= d. The element with the
* lowest value is in queue[0], assuming the queue is nonempty.
*/
transient Object[] queue; // non-private to simplify nested class access
However, the internal Iterator implementation simply uses an integer cursor to scan forward through that array, with no consideration of element priorities or heap layout.
return (E) queue[lastRet = cursor++];
Related
I'm always confused about the Java Collections (set, map) remove "complex object", which I mean some self-defined class rather than just primitive type.
I'm experimenting like:
public class Main {
public static void main(String[] args) {
// set
Set<Node> set = new HashSet<>();
set.add(new Node(1,2));
set.add(new Node(3,4));
System.out.println(set);
set.remove(new Node(1,2));
System.out.println(set + "\n");
// tree set
TreeSet<Node> tset = new TreeSet<>((a, b) -> a.name - b.name);
tset.add(new Node(1,2));
tset.add(new Node(3,4));
System.out.println(tset);
tset.remove(new Node(1,2));
System.out.println(tset);
}
}
class Node {
int name;
int price;
Node(int name, int price) {
this.name = name;
this.price = price;
}
}
In the example above, the printout would be:
Set:
[Node#5ba23b66, Node#2ff4f00f]
[Node#5ba23b66, Node#2ff4f00f]
TreeSet:
[Node#48140564, Node#58ceff1]
[Node#58ceff1]
Obviously, the general Set can't remove new Node(1, 2), which is treated as a different object. But interestingly the TreeSet can remove, which I think because the hashing code is based on the lambda comparator I defined here?
And if I change to remove new Node(1, 6), interestingly it's the same printout, where obviously the remove in TreeSet is based on only the name value.
I think I still lack of deep understanding of how Set build up hashing and how comparator would affect this.
For HashMap and HashSet, you need to overwrite hashCode() and equals(Object), where if two objects are equal, they should have equal hash codes. E.g., in your case, you could implement it like this:
#Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Node node = (Node) o;
return name == node.name && price == node.price;
}
#Override
public int hashCode() {
return Objects.hash(name, price);
}
For TreeMap and TreeSet, the notion of equality is based on a comparison (whether the class implements Comparable, or you supply a custom Comparator). In the code you provided, you have a custom Comparator that only takes the name in to consideration, so it would consider any two Nodes with the same name as being equal, regardless of their price.
javadoc comes to rescue
https://docs.oracle.com/javase/7/docs/api/java/util/Set.html#remove(java.lang.Object)
Removes the specified element from this set if it is present (optional
operation). More formally, removes an element e such that (o==null ?
e==null : o.equals(e)), if this set contains such an element. Returns
true if this set contained the element (or equivalently, if this set
changed as a result of the call). (This set will not contain the
element once the call returns.)
So just change your Node class and override equals(Object o) method with your own.
So I'm trying to sort this Arraylist of "Movies" type, alphabetically based on the actor's name and containing no duplicates, which means using a treeSet.
So I have implemented the "comparable" interface and then didn't know what to do after for making the list sorted by alphabets
public int compareTo(Movie m){
for(Movie movileTitle: moviesList){
return movileTitle.getActor().compareTo(m.getActor());
}
return 0;
}
//this is where I'm trying add the arrayList of the movies and sort them using a treeSet
public TreeSet<Movie> movieListByOrder(){
TreeSet<Movie> movieTemp = new TreeSet<>();
//Note, "allMoviesList" is the the list that contains all movies(duplicates) that I'm trying
to add to the treeSet, so that it gets rid of duplicates and that is also ordered.
movieTemp.addAll(allMoviesList);
System.out.println(movieTemp);
return movieTemp;
}
Continuing in the direction in which you are already headed, you may try using an inline comparator when creating your TreeSet:
Set<Movie> treeSet = new TreeSet<Movie>(new Comparator<Movie>() {
#Override
public int compare(Movie m1, Movie m2) {
if (m1 == null) {
if (m2 != null) return -1;
else return 0;
}
else if (m2 == null) {
return 1;
}
else {
return m1.getActor().compareTo(m2.getActor());
}
}
});
A TreeSet is a sorted collection, meaning it will maintain the sort order imposed by the above comparator. So if you want to print out the contents of the set in order, you need only iterate through it.
All that looks archaic, sorry.
Try to look into this code and decide if it's more interesting.
movies.stream()
.sorted(comparing(Movie::getActor))
.distinct()
.collect(toList());
PS: play with performance tuning for extra credit
I need a Collection that sorts the element, but does not removes the duplicates.
I have gone for a TreeSet, since TreeSet actually adds the values to a backed TreeMap:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
And the TreeMap removes the duplicates using the Comparators compare logic
I have written a Comparator that returns 1 instead of 0 in case of equal elements. Hence in the case of equal elements the TreeSet with this Comparator will not overwrite the duplicate and will just sort it.
I have tested it for simple String objects, but I need a Set of Custom objects.
public static void main(String[] args)
{
List<String> strList = Arrays.asList( new String[]{"d","b","c","z","s","b","d","a"} );
Set<String> strSet = new TreeSet<String>(new StringComparator());
strSet.addAll(strList);
System.out.println(strSet);
}
class StringComparator implements Comparator<String>
{
#Override
public int compare(String s1, String s2)
{
if(s1.compareTo(s2) == 0){
return 1;
}
else{
return s1.compareTo(s2);
}
}
}
Is this approach fine or is there a better way to achieve this?
EDIT
Actually I am having a ArrayList of the following class:
class Fund
{
String fundCode;
BigDecimal fundValue;
.....
public boolean equals(Object obj) {
// uses fundCode for equality
}
}
I need all the fundCode with highest fundValue
You can use a PriorityQueue.
PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();
PriorityQueue(): Creates a PriorityQueue with the default initial capacity (11) that orders its elements according to their natural ordering.
This is a link to doc: https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html
I need all the fundCode with highest fundValue
If that's the only reason why you want to sort I would recommend not to sort at all. Sorting comes mostly with a complexity of O(n log(n)). Finding the maximum has only a complexity of O(n) and is implemented in a simple iteration over your list:
List<Fund> maxFunds = new ArrayList<Fund>();
int max = 0;
for (Fund fund : funds) {
if (fund.getFundValue() > max) {
maxFunds.clear();
max = fund.getFundValue();
}
if (fund.getFundValue() == max) {
maxFunds.add(fund);
}
}
You can avoid that code by using a third level library like Guava. See: How to get max() element from List in Guava
you can sort a List using Collections.sort.
given your Fund:
List<Fund> sortMe = new ArrayList(...);
Collections.sort(sortMe, new Comparator<Fund>() {
#Override
public int compare(Fund left, Fund right) {
return left.fundValue.compareTo(right.fundValue);
}
});
// sortMe is now sorted
In case of TreeSet either Comparator or Comparable is used to compare and store objects . Equals are not called and that is why it does not recognize the duplicate one
Instead of the TreeSet we can use List and implement the Comparable interface.
public class Fund implements Comparable<Fund> {
String fundCode;
int fundValue;
public Fund(String fundCode, int fundValue) {
super();
this.fundCode = fundCode;
this.fundValue = fundValue;
}
public String getFundCode() {
return fundCode;
}
public void setFundCode(String fundCode) {
this.fundCode = fundCode;
}
public int getFundValue() {
return fundValue;
}
public void setFundValue(int fundValue) {
this.fundValue = fundValue;
}
public int compareTo(Fund compareFund) {
int compare = ((Fund) compareFund).getFundValue();
return compare - this.fundValue;
}
public static void main(String args[]){
List<Fund> funds = new ArrayList<Fund>();
Fund fund1 = new Fund("a",100);
Fund fund2 = new Fund("b",20);
Fund fund3 = new Fund("c",70);
Fund fund4 = new Fund("a",100);
funds.add(fund1);
funds.add(fund2);
funds.add(fund3);
funds.add(fund4);
Collections.sort(funds);
for(Fund fund : funds){
System.out.println("Fund code: " + fund.getFundCode() + " Fund value : " + fund.getFundValue());
}
}
}
Add the elements to the arraylist and then sort the elements using utility Collections.sort,. then implement comparable and write your own compareTo method according to your key.
wont remove duplicates as well, can be sorted also:
List<Integer> list = new ArrayList<>();
Collections.sort(list,new Comparator<Integer>()
{
#Override
public int compare(Objectleft, Object right) {
**your logic**
return '';
}
}
)
;
I found a way to get TreeSet to store duplicate keys.
The problem originated when I wrote some code in python using SortedContainers. I have a spatial index of objects where I want to find all objects between a start/end longitude.
The longitudes could be duplicates but I still need the ability to efficiently add/remove specific objects from the index. Unfortunately I could not find the Java equivalent of the Python SortedKeyList that separates the sort key from the type being stored.
To illustrate this consider that we have a large list of retail purchases and we want to get all purchases where the cost is in a specific range.
// We are using TreeSet as a SortedList
TreeSet _index = new TreeSet<PriceBase>()
// populate the index with the purchases.
// Note that 2 of these have the same cost
_index.add(new Purchase("candy", 1.03));
Purchase _bananas = new Purchase("bananas", 1.45);
_index.add(new Purchase(_bananas);
_index.add(new Purchase("celery", 1.45));
_index.add(new Purchase("chicken", 4.99));
// Range scan. This iterator should return "candy", "bananas", "celery"
NavigableSet<PriceBase> _iterator = _index.subset(
new PriceKey(0.99), new PriceKey(3.99));
// we can also remove specific items from the list and
// it finds the specific object even through the sort
// key is the same
_index.remove(_bananas);
There are 3 classes created for the list
PriceBase: Base class that returns the sort key (the price).
Purchase: subclass that contains transaction data.
PriceKey: subclass used for the range search.
When I initially implemented this with TreeSet it worked except in the case where the prices are the same. The trick is to define the compareTo() so that it is polymorphic:
If we are comparing Purchase to PriceKey then only compare the price.
If we are comparing Purchase to Purchase then compare the price and the name if the prices are the same.
For example here are the compareTo() functions for the PriceBase and Purchase classes.
// in PriceBase
#Override
public int compareTo(PriceBase _other) {
return Double.compare(this.getPrice(), _other.getPrice());
}
// in Purchase
#Override
public int compareTo(PriceBase _other) {
// compare by price
int _compare = super.compareTo(_other);
if(_compare != 0) {
// prices are not equal
return _compare;
}
if(_other instanceof Purchase == false) {
throw new RuntimeException("Right compare must be a Purchase");
}
// compare by item name
Purchase _otherPurchase = (Purchase)_other;
return this.getName().compareTo(_otherChild.getName());
}
This trick allows the TreeSet to sort the purchases by price but still do a real comparison when one needs to be uniquely identified.
In summary I needed an object index to support a range scan where the key is a continuous value like double and add/remove is efficient.
I understand there are many other ways to solve this problem but I wanted to avoid writing my own tree class. My solution seems like a hack and I am surprised that I can't find anything else. if you know of a better way then please comment.
I'm using a priority queue to sort and use a large number of custom objects. The objects have a "weight" that is their natural ordering. However, different objects that are inserted into the priority queue may have the same "weight". In such cases, I want the priority queue to order them in the same order in which they were put into the queue.
For example, if I add in CustomObjects A,B,C,D in that order, all with the same "weight", than the priority queue should return them in that order as well - even if I poll one or more of the objects before adding in the others.
Here is the CompareTo for my custom object:
public int compareTo(CustomObject o) {
int thisWeight = this.weight;
int thatWeight = o.weight;
if(thisWeight < thatWeight){
return -1;
}
else{
return 1;
}
}
While I thought that this would maintain that initial order, it doesn't. This occurs when I input A,B,C with weight 1; poll A; and add D,E also with weight 1. Somehow, D and E are sorted after B, but before C.
I am aware that the Iterator for PriorityQueues doesn't return the correct ordering, so I am limited in my ability to look at the ordering - however I can see the order that the elements leave the queue and it clearly doesn't follow the path that I want it to.
Suggestions?
If you need to have an ordering according the insertion order you need to use an extra element for timestamp.
I.e. on insertions and equal weight use timestamp to see which element was inserted first.
So CustomObject should be something like:
class CustomObject {
int weight;
long timestamp;
}
And the comparison should be:
public int compareTo (CustomObject o) {
int thisWeight = this.weight;
int thatWeight = o.weight;
if (thisWeight != thatWeight) {
return thisWeight - thatWeight;
}
else {
return this.timestamp - o.timestamp;
}
}
The smaller timestamp means it was inserted earlier so you keep in the insertion order.
You could also use a "logical" time by maintaining a counter that you update on each add or remove.
You could use an automatically-incremented sequence number as a secondary key, and use it to break ties.
Javadoc for PriorityBlockingQueue includes an example of this technique:
Operations on this class make no guarantees about the ordering of elements with equal priority. If you need to enforce an ordering, you can define custom classes or comparators that use a secondary key to break ties in primary priority values. For example, here is a class that applies first-in-first-out tie-breaking to comparable elements. To use it, you would insert a new FIFOEntry(anEntry) instead of a plain entry object.
class FIFOEntry<E extends Comparable<? super E>>
implements Comparable<FIFOEntry<E>> {
final static AtomicLong seq = new AtomicLong();
final long seqNum;
final E entry;
public FIFOEntry(E entry) {
seqNum = seq.getAndIncrement();
this.entry = entry;
}
public E getEntry() { return entry; }
public int compareTo(FIFOEntry<E> other) {
int res = entry.compareTo(other.entry);
if (res == 0 && other.entry != this.entry)
res = (seqNum < other.seqNum ? -1 : 1);
return res;
}
}
I want to augment or lower the priority of items in a PriorityQueue: for example, I might be downloading a long list of images and suddenly want the thirtieth one to have highest priority.
As I understand it, poll() always returns the queue object with the lowest value (as determined by a comparator). If I can lower the value of an item already in a queue (e.g. if this value is determined by an int in the object and I reduce the int value in some other function), will it be returned first by poll(), or is the sorting that allows poll() to do this done at insert time (e.g. by bubbling new queue elements down a list till they reach their "natural" depth)?
If this is done on a PriorityBlockingQueue, could it cause concurrency issues?
None of the collections in Java automatically reorders elements if you change the property that determine their order. For collections that depend on the .hashCode() , .equals() or some comparator, you are not allowed to change the object while it resides in the collection so that the hashcode/equals or comparator would yield different values.
You have to remove, change, re-insert the object if you want to change its priority within a PriorityQueue.
If you look at the source code, every time you poll() on a PriorityQueue it resifts, but it always returns the item that was at the top before the sift.
public class PQ {
int priority;
public PQ(int priority) {
this.priority = priority;
}
public static void main(String[] args) {
PQ one = new PQ(1);
PQ two = new PQ(2);
PQ three = new PQ(3);
PQ four = new PQ(4);
PQ five = new PQ(5);
PriorityQueue<PQ> q = new PriorityQueue<PQ>(3, new Comparator<PQ>() {
#Override
public int compare(PQ o1, PQ o2) {
return o1.priority-o2.priority;
}
});
q.add(three);
q.add(one);
q.add(four);
q.add(two);
q.add(five);
//Prints;
//PQ-1
//PQ-2
//PQ-3
//PQ-4
//PQ-5
while (!q.isEmpty()) {
System.out.println(q.poll());
}
q.add(three);
q.add(one);
q.add(four);
q.add(two);
q.add(five);
//Change the priority after it has been queued
four.priority = 10;
//Prints;
//PQ-1
//PQ-2
//PQ-3
//PQ-5
//PQ-10
while (!q.isEmpty()) {
System.out.println(q.poll());
}
//Reset the priority
four.priority = 4;
q.add(three);
q.add(one);
q.add(four);
q.add(two);
q.add(five);
//Change the priority after it has been queued
four.priority = 0;
//Prints;
//PQ-1
//PQ-0
//PQ-2
//PQ-3
//PQ-5
while (!q.isEmpty()) {
System.out.println(q.poll());
}
}
public String toString() {
return "PQ-" + priority;
}
}
If you iterate over a priority queue, you will find it is in no particular order (except the first element) If you want to change the order, I suggest you create another priority queue.
If you wan to change the position of one entry, I suggest you remove it, change its fields as required and add it again.