Java order by priority list - java

Given a list of objects (List<MyClass> objects).
class MyClass {
int id;
String name;
}
And a list with names:
name1
name2
name3
Whats a nice way to write a comparator to use the list of names as a priority list and if a priority doesnt exist for a name use alphabetic ordering?

I would suggest, that you use the java.util.Collections.sort method, and provide a custom comparator.
// Define a new static comparator attribute for your class
public static Comparator<MyClass> MY_COMPARATOR = new Comparator<>() {
#Override
public int compare(MyClass o1, MyClass o2) {
return o1.name.compareTo(o2.name); // or whatever logic
}
};
//Then just call this to sort when you need it
List<MyClass> myList; // initialised somewhere
Collections.sort(myList, MY_COMPARATOR);
If you're using java 8+, then the code to create the comparator is even shorter:
public static Comparator<MyClass> MY_COMPARATOR = (o1, o2) -> o1.name.compareTo(o2.name);

Put the strings into an array and loop through it to see which one you encounter first.
public class NameComparator implements Comparator {
static private [] String strNames = {"Ken", "Alisia", "Ben"};
public int compare(MyClass objX, MyClass objY) {
String x = objX.Name;
String y = objY.Name;
String strCurrentName;
if(x.equals(y)) {
return 0;
}
for(strCurrentName: strNames) {
if(strCurrentName.equals(x)) {
return 1;
}
if(strCurrentName.equals(y)) {
return -1;
}
}
return x.compareTo(y);
}
}
Sorting with this comparator would give you, for instance, "Ken", "Alicia", "Michelle" and "Nancy".
If speed is an issue you could put the names in a HashMap instead of an array. The code would then be quite different, I can give you an example if you are interested.

Related

Can Collections.sort() take treeSet as first argument? If not why?

I have my 'Person' class as follows -
public class Person implements Comparable<Person> {
private int marks;
private String name;
Person(int marks, String name) {
this.marks = marks;
this.name = name;
}
public int getMarks() {
return marks;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "Person: Marks = " + marks + ", Name = " + name;
}
#Override
public int compareTo(Person person) {
return marks > person.marks ? 1 : -1;
}
}
Now in main method I have created another comparator with opposite sorting -
Comparator<Person> comparator = new Comparator<Person>() {
#Override
public int compare(Person p1, Person p2) {
int marks1 = p1.getMarks();
int marks2 = p2.getMarks();
return marks1 > marks2 ? -1 : 1;
}
};
Now, I create a TreeSet -
TreeSet<Person> treeSet1 = new TreeSet<>(List.of(
new Person(67, "Himani"),
new Person(73, "Hasani"),
new Person(21, "Rohini")
));
Now, I try passing treeSet as an argument to Collections.sort() -
Collections.sort(treeSet1, comparator);
Here, I get the following error -
Required type Provided
list:List<T> TreeSet<Person>
c:Comparator<? super T> Comparator<Person>
As far as I can deduce from the error - Collections.sort() with comparator can only be used on List and not Set.
Am I correct? If not where am I going wrong? If yes why am I correct?
The first argument to Collections.sort is of type List<T>. So you can only pass List objects to it, not Set objects, or anything else.
This is sensible. The reason is that Set objects are of two types.
There are those that are, by their nature, always unsorted, such as HashSet. It doesn't make sense to sort these.
There are those that are, by their nature, always sorted the same way, such as TreeSet. It doesn't make sense to sort these, because they're already sorted.
I think maybe what you want is to take your TreeSet and have it sorted using a different Comparator. To do that, you'll have to make a new TreeSet with your new Comparator, and copy the entries across from the old set using addAll.

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.
}

Sort List based on index value

This is what i have so far, i'm trying to sort a bunch of List<String>'s based on the value of an index.
LinkedHashSet<List<String>> sorted = new LinkedHashSet<List<String>>();
How do i sort the LinkedHashSet in order from Highest to Lowest index 2 value of the List's?
Example input:
List<String> data1 = Database.getData(uuid);
double price = Double.valueOf(data1.get(2))
data1.add("testval");
data1.add("testval");
data1.add("100.00");
sorted.add(data1);
and on another seperate List:
List<String> data2 = Database.getData(uuid);
double price = Double.valueOf(data2.get(2))
data2.add("anotherval");
data2.add("anotherval");
data2.add("50.00");
sorted.add(data2);
Output of the sorted LinkedHashSet in descending order.
testval testval 100.00
anotherval anotherval 50.00
Sorry if this is confusing, im not sure where to go about sorting like this.
Create a new class to represent you complex objects. There is no need to store multiple values in a list when you can do it in objects.
public class ComplexObject {
private String description1;
private String description2;
private Double value;
public ComplexObject(String description1, String description2, Double value) {
this.description1 = description1;
this.description2 = description2;
this.value = value;
}
public void setDescription1(String description1) {
this.description1 = description1;
}
public String getDescription1() {
return description1;
}
public void setDescription2(String description2) {
this.description2 = description2;
}
public String getDescription2() {
return description2;
}
public void setValue(Double value) {
this.value = value;
}
public Double getValue() {
return value;
}
}
Then add elements to the list and sort it using a new, custom, comparator:
public static void main(String[] args) {
List<ComplexObject> complexObjectList = new ArrayList<ComplexObject>();
//add elements to the list
complexObjectList.add(new ComplexObject("testval","testval",100.00d));
complexObjectList.add(new ComplexObject("anotherval","anotherval",50.00d));
//sort the list in descending order based on the value attribute of complexObject
Collections.sort(complexObjectList, new Comparator<ComplexObject>() {
public int compare(ComplexObject obj1, ComplexObject obj2) {
return obj2.getValue().compareTo(obj1.getValue()); //compares 2 Double values, -1 if less , 0 if equal, 1 if greater
}
});
//print objects from sorted list
for(ComplexObject co : complexObjectList){
System.out.println(co.getDescription1()+" "+co.getDescription2()+" "+co.getValue());
}
}
Output:
testval testval 100.0
anotherval anotherval 50.0
Firstly, you shouldn't use a LinkedHashSet but a TreeSet. LinkedHashSet will retain the insertion order without sorting.
Secondly, you need to initialize your TreeSet with a Comparator that compares based on whichever value of your List is required, that is, if you know the index of the String that will represent a double value in advance. Otherwise I would recommend using custom objects instead of List.
If you decide to use custom objects, you don't necessarily need to initialize your TreeSet with a Comparator as second argument.
Instead, you could have your custom objects implement Comparable, and implement a one-time comparation logic there.
It all depends on whether you only need to sort in a particular order.
Finally, custom objects will require you to override equals and hashCode.
First, and extracted from Oracle's Java reference:
This linked list defines the iteration ordering, which is the order in which elements were inserted into the set
So you can't sort your data just inserting it into the LinkedHashSet.
You may be confusing that set implementation with SortedSet. SortedSet allows you to pass a comparator which will determine the elements order in the data structure.
On the other hand, I don't know whether you chose you List<String> arbitrarily but it seems to me a wiser option to aggregate your the 3 strings as a class attributes. The point is that, if your elements are always going to be 3 elements, being the last one a double value: Why do you need a dynamic structure as a List?
EDIT
Here you have a possible better implementation of what you want:
public class Element
{
public Element(String a, String b, double val) {
this.a = a;
this.b = b;
this.val = val;
}
#Override
public String toString() {
return a + "\t" + b + "\t" + val;
}
public String a;
public String b;
public double val;
}
And you can use this class to store your elements. An example of use:
SortedSet<Element> sorted = new TreeSet<>(new Comparator<Element>() {
#Override
public int compare(Element o1, Element o2) {
return (new Double(o1.val)).compareTo(o2.val);
}
});
sorted.add(new Element("testval", "testval", 100.0));
sorted.add(new Element("anotherval", "anotherval", 50.0));
for(Element el: sorted)
{
System.out.println(el);
}
Note that the comparator is given as an instance of an anonympous inner class implementing Java's Comparator interface.

How to use Comparator and Comparable in ArrayList?

I need to sort the ArrayList with respect to column.That is In My ArrayList,each row contains some float values and a string.Each of them is separated using commas.
Example:
1 row:--- 4.9,5.1,2.3,1.0,classA
likewise ALL ROWS.
So I want to sort this arraylist based on each values(based on the 1st val of all rows,similarly all rows).
Here How I use Comparator and Comparable?
public class FieldComparator implements Comparator<String> {
private int column;
private int numberOfFloats;
public FieldComparator(int column, int numberOfFloats) {
this.column = column;
this.numberOfFloats = numberOfFloats;
}
#Override
public int compare(String o1, String o2) {
String[] o1Fields = o1.split(",");
String[] o2Fields = o2.split(",");
if (column < numberOfFloats) {
return new Float(o1Fields[column]).compareTo(new Float(o2Fields[column]));
} else {
return o1Fields[column].compareTo(o2Fields[column]);
}
}
}
You can use instance of this class as Comparator while sorting or anything else.
Refer to documentation of Comparator and Collections for sorting.
Good luck.
You need to use Collections#sort. It will take your Array List and a Comparator.
You can create anonymous Comparator and in its compareTo method you will obtain that first value by splitting both the string and than compare them and return -1,0,1 accordingly.
Hope this helps.

How to perform a series of sort operation on arraylist (multiple sort criteria)

I have an arrayList of objects and I want to run a series of sort operations on this list. I would want to sort them by first by name, and if two names are same than sort them by id, for example.
How can i implement it?
This is my code
Comparator<Ticket> mc;
mc = new TicketIdComparator();
Collections.sort(tickets, mc);
final class TicketIdComparator implements Comparator<Ticket>
{
#Override
public int compare(Ticket ticket1, Ticket ticket2) {
String TicketId1 = ((Ticket) ticket1).getNumber();
String TickedId2 = ((Ticket) ticket2).getNumber();
int num1=Integer.parseInt(TicketId1);
int num2 =Integer.parseInt(TickedId2);
if (num1<num2)
return 1;
if (num1>num2)
return -1;
return 0;
}
}
This code sorting list by id, but again i want to sort on name
You can write a Comparator from a collection of comparators.
public static <T extends Comparable<T>> Comparator<T> comparatorFrom(final Comparator<T>... comparators) {
return new Comparator<T>() {
#Override
public int compare(T o1, T o2) {
for (Comparator<T> comparator : comparators) {
final int cmp = comparator.compare(o1, o2);
if (cmp != 0) return cmp;
}
return 0;
}
};
}
// you can then use
Arrays.sort(list, comparatorFrom(comp1, comp2, comp3, comp4));
Collections.sort(myList, new Comparator() {
#Override
public int compare(Object o1, Object o2)
{
// write your ordering code here
return 0;
}
});
Just fill in the code for the comparison you want and Java will handle the sorting for you.
Edit for updated question:
Comparator<Ticket> mc;
mc = new TicketIdComparator();
Collections.sort(tickets, mc);
final class TicketIdComparator implements Comparator<Ticket>
{
#Override
public int compare(Ticket ticket1, Ticket ticket2) {
String TicketId1 = ((Ticket) ticket1).getNumber();
String TickedId2 = ((Ticket) ticket2).getNumber();
int num1=Integer.parseInt(TicketId1);
int num2 =Integer.parseInt(TickedId2);
if (num1<num2)
return 1;
else if (num1>num2)
return -1;
else
return ticket1.getName().compare(ticket2.getName());
}
}
As you said, and similar as the code you already wrote you would create a class that implements the Comparator interface for Ticket. First, you compare the ticket's names using a String comparator, and if that results in 0 (equal name) then you would compare by id in the same comparator.
Make sure the string comparator does trimming before (remove before and after empty spaces) and maybe ignore casing, that's up to you.
If you want some kind of generalization, then you can write a decorator comparator which calls the more specifics ones. If you want to know more about this, let me know.
Collections.sort(myList, new Comparator() {
#Override
public int compare(Object o1, Object o2)
{
// write your ordering code here for sorting list by id
return 0;
}
});
list with sorted by id
Collections.sort(myList, new Comparator() {
#Override
public int compare(Object o1, Object o2)
{
// write your ordering code here for sorting list by name
return 0;
}
});
it will give list with sorted by name
With Java 8 you can use lambdas and based on answer by Peter Lawrey you can do something pretty:
Collections.sort(users, comparatorFrom(
(final User u1, final User u2) -> u1.getDomain().getFirstName()
.compareTo(u2.getFirstName()),
(final User u1, final User u2) -> u1.getLastName().compareTo(u2.getLastName()),
...more comparators here...
));
You need to use a stable sorting algorithm, then sort by ID first and after that sort by name.
Check out this comparison of algorithms to find one that suits your needs.

Categories