Comparator based on a configurable order - java

Is there a way to write custom comparator, following this example:
There are at most 10 items coming in at a random order
i.e.
first item: item_one
second: second_one
third: third_one
I want result them to be sorted like : second_one, third_one, first_one. I'd like to pull this order from configuration file, sort of like template for sorting.
Am I using the wrong data structure, does anyone have experience with this?

Sure. Here is an "OrderedComparator" that compares elements according to a predefined order:
class OrderedComparator implements Comparator<String> {
List<String> predefinedOrder;
public OrderedComparator(String[] predefinedOrder) {
this.predefinedOrder = Arrays.asList(predefinedOrder);
}
#Override
public int compare(String o1, String o2) {
return predefinedOrder.indexOf(o1) - predefinedOrder.indexOf(o2);
}
}
And here is some test code. (I used a List instead of a Set since it 1) seem more natural when talking about the order of the elements and 2) better illustrate what happens with duplicate elements upon sorting using this comparator.)
class Test {
public static void main(String[] args) {
// Order (could be read from config file)
String[] order = { "lorem", "ipsum", "dolor", "sit" };
List<String> someList = new ArrayList<String>();
// Insert elements in random order.
someList.add("sit");
someList.add("ipsum");
someList.add("sit");
someList.add("lorem");
someList.add("dolor");
someList.add("lorem");
someList.add("ipsum");
someList.add("lorem");
System.out.println(someList);
Collections.sort(someList, new OrderedComparator(order));
System.out.println(someList);
}
}
Output:
[sit, ipsum, sit, lorem, dolor, lorem, ipsum, lorem]
[lorem, lorem, lorem, ipsum, ipsum, dolor, sit, sit]

Take a look at TreeSet (http://download.oracle.com/javase/6/docs/api/java/util/TreeSet.html). You can provide a custom Comparator in a constructor. This Comparator will take into account your config. file . The logic of the comparator will not be pretty though since you want arbitrary order. You will most probably end up enumerating all possible comparisons.

A set stores unordered elements. If you want to compare and sort, you should probably go with a list. Here's a quick snippet for you:
List<X> sorted = new ArrayList<X>(myset);
Collections.sort(sorted, new Comparator<X>() {
public int compare(X o1, X o2) {
if (/* o1 < o2 */) {
return -1;
} else if (/* o1 > o2 */) {
return 1;
} else {
return 0;
}
}
});
Now you've got sorted, which has all the same elements of myset, which was unordered by virtue of being a set.
You can also look at TreeSet, which orders its elements, but it's generally not a good idea to rely on a set being ordered.

Use Guava's com.google.common.collect.Ordering:
Ordering.explicit(second_one, third_one, first_one);

Related

Sorting a list of string to show first strings with a particular substring and then the remaining strings in their sorted order

I have a list of string (com.abc.c345, com.abc.a123, com.abc.d456, com.abc.b234, com.qwr.a1, com.wer.b2, com.ert.c3)
I want to sort this so that the strings containing 'com.abc' will be displayed first in their sorted style and after that the remaining strings in their sorted style.
So the output should look something like this
(com.abc.a123, com.abc.b234, com.abc.c345, com.abc.d456, com.ert.c3, com.qwr.a1, com.wer.b2)
Edit 1:
I tried using comparators as:
Collections.sort(list, new Comparator<LoggerDetails>() {
#Override
public int compare(LoggerDetails o1, LoggerDetails o2) {
if (o1.logger.startsWith("com.abc") && o2.logger.startsWith("com.abc")) {
return o1.logger.compareTo(o2.logger);
}
if (o1.logger.startsWith("com.abc") && !o2.logger.startsWith("com.abc")) {
return -1;
}
if (o1.logger.startsWith("com.abc") && !o2.logger.startsWith("com.abc")) {
return 1;
}
return 0;
}
});
You're quite close: use your comparator, which performs ordering according to first criterion, then compound it with natural ordering comparator:
Collections.sort(list, new Comparator<LoggerDetails>() {
#Override
public int compare(LoggerDetails o1, LoggerDetails o2) {
if(o1.logger.startsWith("com.abc") && !o1.logger.startsWith("com.abc")) {
return -1;
}
if (o1.logger.startsWith("com.abc") && !o1.logger.startsWith("com.abc")) {
return -1;
} -- I just copypasted your original code, this should definitely be vice versa
return 0;
}
}.thenComparing(o -> o.logger, Comparator.naturalOrder()));
You can make your comparator more concise utilizing fact than booleans are comparable:
comparing(o -> o.logger.startsWith("com.abc"), Comparator.reverseOrder())
Or if you have more such "exceptional" strings (as I noticed you are keeping editing original post), map them onto numbers.
create a new list by removing every entry starting with your substring and add it to a new list.
after that you can just add the elements of the new list using list.addAll(0, newList)
You can simply sort the list using Collections.sort provided in util package in java. Your desired output seems to be in alphabetical and numerically sorted format, so that would work. Here is the code sample that worked for me:
List<String> list = new ArrayList<>();
list.add("com.abc.c345");
list.add("com.abc.a123");
list.add("com.abc.d456");
list.add("com.abc.b234");
list.add("com.qwr.a1");
list.add("com.wer.b2");
list.add("com.ert.c3");
Collections.sort(list);
list.forEach(value -> System.out.println(value));
Hope that helps!

A TreeSet or TreeMap that allow duplicates

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.

Changing the order of TreeSet elements to customized output

I know that TreeSet stores objects in a sorted manner. But is there a way that i can customize the order?
For example if i have a treeSet:
TreeSet t1 = new TreeSet();
t1.add("c");
t1.add("d");
t1.add("a");
And now if i iterate over it>
Iterator it1 =t1.iterator();
while(it1.hasNext()){
Object o1 = it1.next();
System.out.println(o1);
}
i will always get the order as: a>c>d, however i want it to return the order the same as i added the elements in it i.e c>d>a?
Use LinkedHashSet for it,
TreeSet sorts the element, and for string it sorts based on natural order( this is how its comparator is implemented), If you want to manage the insertion order then you need to user LinkedHashSet
and if you don't need the uniquness (feature of set) then go for List
Since you mention about being bound to use TreeSet, something like this comes to my mind:
Set<String> result = new TreeSet<String>(new Comparator<String>(){
#Override
public int compare(String arg0, String arg1) {
return returnCode(arg0).compareTo(returnCode(arg1));
}
});
where:
private Integer returnCode(String p){
int code = 0;
String id = p.toLowerCase();
if ("a".equalsIgnoreCase(id)) code = 3;
else if ("b".equalsIgnoreCase(id)) code = 2;
else if ("c".equalsIgnoreCase(id)) code = 1;
//etc
return new Integer(code);
}
So basically you are implementing your own comparator which is nothing but assigning certain integer values to the inserted String (which i assume you know already).
NOTE: this solution will not work, incase you do not catch the option in your returnCode() method. I assume you already know the data that is being fed to the TreeSet.
It is much more easy:
TreeSet treeSetObj = new TreeSet(Collections.reverseOrder());
I have got a good answer. It's my first one.
import java.util.*;
class M
{
public static void main(String args[])
{
TreeSet<String> t=new TreeSet<>(new MyComparator());
t.add("c");
t.add("d");
t.add("a");
System.out.println(t);//[c,d,a] not [a,c,d] ....your answer
}
}
class MyComparator implements Comparator
{
public int compare(Object o1,Object o2)
{
String i1=(String)o1;
String i2=(String)o2;
return +1; //Store elements as they come
}
}
What basically +1 does store the elements in the order in which they come.
Always remember
if I write return +1 it means to store the elements in the order in which they come.
if I write return -1 it means to store the elements in reverse order in which they come.

Sorting java arrayList with predefined order

I have an unsorted list but I want to sort in a custom way i.e.
item_one_primary.pls
item_one_secondary.pls
item_one_last.pls
item_two_last.pls
item_two_primary.pls
item_two_secondary.pls
item_three_secondary.pls
item_three_last.pls
item_three_primary.pls
Here is my predefined order : primary, secondary, last
Above unordered list once the ordering is applied should look like this :
item_one_primary.pls
item_one_secondary.pls
item_one_last.pls
item_two_primary.pls
item_two_secondary.pls
item_two_last.pls
item_three_primary.pls
item_three_secondary.pls
item_three_last.pls
I tried something with comparator but I end up something like this :
item_one_primary.pls
item_two_primary.pls
item_three_primary.pls
...
Does anyone have an idea how to get this sorted?
Here is some code I've used :
List<String> predefinedOrder;
public MyComparator(String[] predefinedOrder) {
this.predefinedOrder = Arrays.asList(predefinedOrder);
}
#Override
public int compare(String item1, String item2) {
return predefinedOrder.indexOf(item1) - predefinedOrder.indexOf(item2);
}
I didn't include the splits(first split by dot(.) second split by underscore(_) to get the item in pre-ordered list).
You have to use a Comparator that checks first the item number and only if they are equal, check your predefined order.
Try something like this:
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
String[] a1 = s1.split("_");
String[] a2 = s2.split("_");
/* If the primary elements of order are equal the result is
the order of the second elements of order */
if (a1[1].compareTo(a2[1]) == 0) {
return a1[2].compareTo(a2[2]);
/* If they are not equal, we just order by the primary elements */
} else {
return a1[1].compareTo(a2[1]);
}
}
This is just a basic example, some extra error checking would be nice.
A solution using the Google Guava API yields a simple and readable result:
// some values
List<String> list = Lists.newArrayList("item_one_primary", "item_one_secondary", "item_one_last");
// define an explicit ordering that uses the result of a function over the supplied list
Ordering o = Ordering.explicit("primary", "secondary", "last").onResultOf(new Function<String, String>() {
// the function splits a values by '_' and uses the last element (primary, secondary etc.)
public String apply(String input) {
return Lists.newLinkedList(Splitter.on("_").split(input)).getLast();
}
});
// the ordered result
System.out.println("o.sortedCopy(list); = " + o.sortedCopy(list));

List Sorting puzzle

Assuming I have
final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
What can I do to transform this unsorted list into this:
[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]
(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)
I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...
I would keep separate lists.
One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.
knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
I suggest filling List with your values and using Collections.sort(...).
Something like
Collections.sort(myList, new FunkyComparator());
using this:
class FunkyComparator implements Comparator {
private static Map<String,Integer> orderedExceptions =
new HashMap<String,Integer>(){{
put("PREFZ", Integer.valueOf(1));
put("PREFA", Integer.valueOf(2));
}};
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
Integer i1 = orderedExceptions.get(s1);
Integer i2 = orderedExceptions.get(s2);
if (i1 != null && i2 != null) {
return i1 - i2;
}
if (i1 != null) {
return -1;
}
if (i2 != null) {
return +1;
}
return s1.compareTo(s2);
}
}
Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.
I would first use Collections.sort(list) to sort the list.
Then, I would remove the known items, and add them to the front.
String special = "PREFA";
if (list.remove(special)
list.add(0, special);
Or, if you have a list of array of these values you need in the front you could do:
String[] knownValues = {};
for (String s: knownValues) {
if (list.remove(s))
list.add(0, s);
}
Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:
final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted =
concat(
Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)
// List is not efficient here; for large problems, something like SkipList
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");
private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES
= new Function<Object, Integer>() {
public Integer apply(Object in) {
int index = KNOWN_INDEXES.indexOf(in);
return index == -1 ? null : index;
}
};
...
List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
Collections.sort(values,
Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);
So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.
Messy, perhaps, but fun.
I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.
class MyComparator implements Comparator<String> {
public int compare(String o1, String o2) {
// Now you can define the behaviour for your sorting.
// For example your special cases should always come first,
// but if it is not a special case then just use the normal string comparison.
if (o1.equals(SPECIAL_CASE)) {
// Do something special
}
// etc.
return o1.compareTo(o2);
}
}
Then sort by doing:
Collections.sort(list, new MyComparator());

Categories