Building a Sort object based on Map<Enum, Enum> - java

I would like to build a Sort object based on Map<Column, Direction>. I have a problem with the fact that the Sort class only has a private constructor, it just has to be created by the static method by() or and(), therefore I have a problem with initialising the sort object with the first element from the map.
private Sort buildSort(Map<WorklistColumn, Direction> columnsDirectionsmap){
Sort sort = by("wartość inicjalna której nie chcemy", Direction.Ascending);
for (Map.Entry<WorklistColumn, Direction> columnWithDirection : columnsDirectionsmap.entrySet()) {
sort.and(columnWithDirection.getKey().toString(), columnWithDirection.getValue());
}
return sort;
}
public class Sort {
private List<Column> columns = new ArrayList();
private Sort() {
}
public static Sort by(String column) {
return (new Sort()).and(column);
}
public static Sort by(String column, Direction direction) {
return (new Sort()).and(column, direction);
}
public Sort and(String name) {
this.columns.add(new Column(name));
return this;
}
public Sort and(String name, Direction direction) {
this.columns.add(new Column(name, direction));
return this;
}
Build a Sort object from a map

I think the question is about the fact that to fully configure a Sort object from your map, you need to use the first map entry in conjunction with Sort.by(), and then use all the other entries in conjunction with Sort.and(). That is, the first entry requires different handling than the rest.
There are lots of ways of dealing with that, but the one I'm going to suggest is to work directly with the iterator of the map's entry set. Something like this:
private Sort buildSort(Map<WorklistColumn, Direction> columnsDirectionsMap) {
if (columnsDirectionsMap.isEmpty()) {
throw new NoCriteriaException(); // or whatever
}
Iterator<Map.Entry<WorklistColumn, Direction>> criterionIterator =
columnsDirectionsMap.entrySet().iterator();
Map.Entry<WorklistColumn, Direction> criterion = criterionIterator.next();
Sort sort = Sort.by(criterion.key().toString(), criterion.value());
while (criterionIterator.hasNext()) {
criterion = criterionIterator.next();
sort.and(criterion.key().toString(), criterion.value());
}
return sort;
}
Do note that depending on the Map implementation involved, the order of the entries may not be easily predictable. I assume that you need control of that order for this approach to work as desired, so it's on you to choose a Map implementation that provides that. A LinkedHashMap might be suitable, for example, but probably not a HashMap.

Related

How to use Comparator with conditions in java

I am trying to sort a list based on sort key and sort order I receive from an API.
For example,
I have a list with sortkey and sortorder and based on that I need to sort.
List<SortList> sortlist;
I have a list of an object :
List<Employee> employee;
I am able to sort using
Collections.sort(sourceList, Comparator
.comparing(Employee::getAge).reversed()
.thenComparing(Employee::getCount));
But i need to check the sortfeild on a condition and based on that only the field is considered for sorting.
ex:
if(sortkey = "name")
sortbythatkey from sortlist by the sort order
if (sortkey = "place")
sortbythat key from sortlist by the sort order
So here if sortlist has both name and place then it should sort by both key and order
Any idea how could i achieve this?
Sort List contains:
{
"sortKey":"name",
"sortOrder":"ASC"
},
{
"sortKey":"place",
"sortOrder":"DESC"
}
Requirement is to chain them together like ORDER BY in SQL
Assuming that sortlist is a list of SortCriteria, which is a class like this:
class SortCritera {
private String key;
private String order;
public String getKey() {
return key;
}
public String getOrder() {
return order;
}
// constructors, setters...
}
You first need a HashMap<String, Comparator<Employee>> to store all the corresponding comparators for each possible key:
HashMap<String, Comparator<Employee>> comparators = new HashMap<>();
comparators.put("name", Comparator.comparing(Employee::getName));
comparators.put("age", Comparator.comparing(Employee::getAge));
// ...
Then you can loop through the sortlist and keep calling thenComparing:
Comparator<Employee> comparator = comparators.get(sortlist.get(0).getKey());
if (sortlist.get(0).getOrder().equals("DESC")) {
comparator = comparator.reversed();
}
for(int i = 1 ; i < sortlist.size() ; i++) {
if (sortlist.get(i).getOrder().equals("DESC")) {
comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()).reversed());
} else {
comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()));
}
}
// now you can sort with "comparator".
As Holger has suggested, you can use the Stream API to do this as well:
sortlist.stream().map(sc -> {
Comparator<Employee> c = comparators.get(sc.getKey());
return sc.getOrder().equals("DESC")? c.reversed(): c;
}).reduce(Comparator::thenComparing)
.ifPresent(x -> Collections.sort(originalList, x));
You can create a method which when passed the sort key, you provide the proper Comparator:
public Comparator<Employee> getComparator(String sortKey) {
if("name".equals(sortKey)) {
return Comparator.comparing(Employee::getName);
} else if ("place".equals(sortKey) {
return Comparator.comparing(Employee::getPlace);
} else {
throw new IllegalArgumentException();
}
}
To call it it would simply be:
Collections.sort(sourceList, getComparator(sortKey).reversed()
.thenComparing(Employee::getCount));
While you could also write your own, I find it is better to delegate the "standard" parts and simply write the part that differs from this.
If you find yourself having many such sort keys, then a more suitable means to do this would be to use a map:
private static final Map<String, Comparator<Employee>> COMPARE_MAP = new HashMap<>() {{
put.("name", Comparator.comparing(Employee::getName));
put.("place", Comparator.comparing(Employee::getPlace));
}});
public Comparator<Employee> getComparator(String sortKey) {
if(COMPARE_MAP.containsKey(sortKey)) {
return COMPARE_MAP.get(sortKey);
} else {
throw new IllegalArgumentException();
}
}
Reflection is also an option, but I would be cautious to use reflection unless it becomes impractical to do otherwise. In that case, you could create your own annotation to determine which fields of class Employee can be used for sorting.

java sort list of objects by specifiable attribute

I want to sort a List of objects by a specified attribute of those objects and I want to choose which attribute should be used for sorting. Example:
class Car{
private String name;
private String colour;
public enum sortBy {NAME, COLOUR};
public String name(){
return name;
}
public String colour(){
return colour;
}
public static Car[] getSortedArray(Car[] carArray, sortBy sortType){
HashMap<Object, Car> carMap = new HashMap<Object, Car>();
Object[] sortArray = new Object[carArray.length];
Object value = null;
for(int i = 0; i < carArray.length; i++){
if(sortType == sortBy.NAME){
value = carArray[i].name();
}else if(sortType == sortBy.COLOUR){
value = carArray[i].colour();
}
carMap.put(value, carArray[i]);
sortArray[i] = value;
}
Arrays.sort(sortArray);
Car[] sortedArray = new Car[sortArray.length];
for(int i = 0; i < sortArray.length; i++){
sortedArray[i] = carMap.get(sortArray[i]);
}
return sortedArray;
}
}
//external:
Car[] cars = getSomeCars();
Car[] nameSortedCars = Car.getSortedArray(cars, Car.sortBy.NAME);
Car[] colourSortedCars = Car.getSortedArray(cars, Car.sortBy.COLOUR);
The idea is simple:
I put all values that i want to sort by into an array, and i create a map that maps these values back to their objects. After I sorted this array I take the objects mapped to these values and put them in the same order into a new array which is then sorted by these values. The values are just created with type Object so I can sort by multiple types (not just Strings as in the example).
This works fine unless you have two objects with the same attribute value, then only one object will be in the returned array, but two times.
Is there a better way to achieve this sorting?
It would be much simpler to use custom comparators:
To sort by name:
Arrays.sort(carArray, Comparator.comparing(Car::name));
To sort by colour:
Arrays.sort(carArray, Comparator.comparing(Car::colour));
So you could modify getSortedArray():
public static Car[] getSortedArray(Car[] carArray, Comparator<Car> comparator) {
Car[] sorted = carArray.clone()
Arrays.sort(sorted, comparator);
return sorted;
}
And call it like this:
Car[] sorted = getSortedArray(carArray, Comparator.comparing(Car::name));
Edit:
If you use a language version that does not support these features, you can create the comparators by explicitly creating a nested class that implements the Comparator interface.
This, for example, is a singleton Comparator that compares Car instances by name:
static enum ByName implements Comparator<Car> {
INSTANCE;
#Override
public int compare(Car c1, Car c2) {
return c1.name().compareTo(c2.name());
}
}
Then call:
Car[] sorted = getSortedArray(carArray, ByName.INSTANCE);
TL;DR: There's already a wheel for that.
I would say the easiest way to do this is to create a comparator:
final Comparator<Car> byName = Comparator.comparing(Car::name);
final Comparator<Car> byColour = Comparator.comparing(Car::colour);
Then just use the appropriate method on Arrays to sort by a comparator:
Arrays.sort(carArray, byName);
Now you want to do it with an enum? Just have the enum implements Comparator<Car>:
enum SortBy implements Comparator<Car> {
NAME(Comparator.comparing(Car::name)),
COLOUR(Comparator.comparing(Car::colour));
private final Comparator<Car> delegate;
private SortBy(Comparator<Car> delegate) {
this.delegate = delegate;
}
#Override
public int compare(final Car o1, final Car o2) {
return delegate.compare(o1, o2);
}
}
Want to sort by name then by colour? Easy:
final Comparator<Car> byName = SortBy.NAME.thenComparing(SortBy.COLOUR);
Want to sort by name in reverse order? Easy:
final Comparator<Car> byName = SortBy.NAME.reversed();
You're reinventing the wheel! Life will be much easier for you if you use the templated Collections API. To do this, you would work with List instead of arrays, define a Comparator to do your sorting, and then let the API do the work for you.
Comparator<Car> carComparator = new Comparator<Car>(){
public int sort(Car car1, Car car2){
//Sorting logic goes here.
}
}
List<Car> cars = getCars();
cars = Collections.sort(cars, carComparator); //the cars collection is now sorted.
If you wanted to sometimes sort by one attribute or another, you could make my variable carComparator into its own class and define which attributes to sort by in the constructor.
Hope that helps :)
Edit: As others have pointed out, this approach also works with arrays. But unless you have a good reason to be working with Arrays, working with Collections will generally be easier.
I think the solution would be more efficient if you passed a Comparator implementation to the Arrays.sort. Right now, you are looping n*2 from the looks of it, the hash map (O(1)) plus the Arrays.sort (which is another 0(n log n) or such). If you do the below, you could skip the 2 loops, and the map, you are using currently.
You can simply create a Comparator like (rough code):
class CarComparator implements Comparator<Car> {
enum compareType; //plus setter
public int compareTo(Car a, Car b) {
if(compareType == COLOUR) return a.colour.compareTo(b.colour);
if(compareType == NAME.....
}
}
, and then simply send the array of Cars to
Arrays.sort(cars, new CarComparator(COLOUR))
, or use more specialised comparator classes, one for each attribute, and a factory to render them, and of course don't create a new Comparator() for each sort if this is happening often. :-)
Overall, this approach should make your code more efficient.
}

PriorityQueue with indices for keeping counts sorted

A problem I often encounter in Java (usually while writing computational linguistics code) is the need to count the number of occurrences of some items in a dataset, then sort the items by their counts. The simplest concrete example is word counting: I need to count the number of occurrences of each word in a text file, then sort the words by their counts to find the most frequently used words.
Unfortunately, Java doesn't seem to have a good data structure for this task. I need to use the words as indices of a collection while I'm counting, so that I can efficiently look up the right counter to increment every time I read a word, but the values I want to sort on are the counts, not the words.
Map<String, Integer> provides the interface I need for looking up the count associated with a word, but Maps can only be sorted by their keys (i.e. TreeMap). PriorityQueue is a nice heap implementation that will sort on whatever comparator you give it, but it provides no way to access the elements by some kind of index and no way to update and re-heapify an element (other than by removing and adding it). Its single type parameter also means I need to stick the words and their counts together into one object in order to use it.
My current "solution" is to store the counts in a Map while counting them, then copy them all into a PriorityQueue to sort them:
Map<String, Integer> wordCounts = countStuff();
PriorityQueue<NamedCount> sortedCounts = new PriorityQueue<>(wordCounts.size(),
Collections.reverseOrder());
for(Entry<String, Integer> count : wordCounts.entrySet()) {
sortedCounts.add(new NamedCount(count.getKey(), count.getValue()));
}
(Note that NamedCount is just a simple pair<string, int> that implements Comparable to compare the integers). But this is inefficient, especially since the data set can be very large, and keeping two copies of the count set in memory is wasteful.
Is there any way I can get random access to the objects inside the PriorityQueue, so that I can just store one copy of the counts in the PriorityQueue and re-heapify as I update them? Would it make sense to use a Map<String, NamedCount> that keeps "pointers" to the objects in the PriorityQueue<NamedCount>?
First, for the base data structure, typically Guava's Multiset<String> is preferable to Map<String, Integer> in the same way that Set<String> is preferable to Map<String, Boolean>. It's a cleaner API and encapsulates the incrementing.
Now, if this were me, I would implement a custom Multiset which adds some additional logic to index the counts, and return them. Something like this:
class IndexedMultiset<T extends Comparable<T>> extends ForwardingMultiset<T> {
private final Multiset<T> delegate = HashMultiset.create();
private final TreeMultimap<Integer, T> countIndex = TreeMultimap.create();
#Override
protected Multiset<T> delegate() {
return delegate;
}
#Override
public int add(T element, int occurrences) {
int prev = super.add(element, occurrences);
countIndex.remove(prev, element);
countIndex.put(count(element), element);
return prev;
}
#Override
public boolean add(T element) {
return super.standardAdd(element);
}
//similar for remove, setCount, etc
}
Then I'd add whatever query capabilities you need based on counts. For example, retrieving an iterable of word/count pairs in descending order could look something like this:
public Iterable<CountEntry<T>> descendingCounts() {
return countIndex.keySet().descendingSet().stream()
.flatMap((count) -> countIndex.get(count).stream())
.map((element) -> new CountEntry<>(element, count(element)))
.collect(Collectors.toList());
}
public static class CountEntry<T> {
private final T element;
private final int count;
public CountEntry(T element, int count) {
this.element = element;
this.count = count;
}
public T element() {
return element;
}
public int count() {
return count;
}
#Override
public String toString() {
return element + ": " + count;
}
}
And it would all be used like this:
public static void main(String... args) {
IndexedMultiset<String> wordCounts = new IndexedMultiset<>();
wordCounts.add("foo");
wordCounts.add("bar");
wordCounts.add("baz");
wordCounts.add("baz");
System.out.println(wordCounts.descendingCounts()); //[baz: 2, bar: 1, foo: 1]
wordCounts.add("foo");
wordCounts.add("foo");
wordCounts.add("foo");
System.out.println(wordCounts.descendingCounts()); //[foo: 4, baz: 2, bar: 1]
}
If you can use third-party libraries like Guava, Multiset is designed pretty specifically as a solution to this problem:
Multiset<String> multiset = HashMultiset.create();
for (String word : words) {
multiset.add(word);
}
System.out.println(Multisets.copyHighestCountFirst(multiset));

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.

Categories