Sort an ArrayList base on multiple attributes - java

I have an ArrayList of object. The object contain attributes date and value. So I want to sort the objects on the date, and for all objects in the same date I want to sort them on value. How can I do that?

Implement a custom Comparator, then use Collections.sort(List, Comparator). It will probably look something like this:
public class FooComparator implements Comparator<Foo> {
public int compare(Foo a, Foo b) {
int dateComparison = a.date.compareTo(b.date);
return dateComparison == 0 ? a.value.compareTo(b.value) : dateComparison;
}
}
Collections.sort(foos, new FooComparator());

public static <T> void sort(List<T> list, final List<Comparator<T>> comparatorList) {
if (comparatorList.isEmpty()) {//Always equals, if no Comparator.
throw new IllegalArgumentException("comparatorList is empty.");
}
Comparator<T> comparator = new Comparator<T>() {
public int compare(T o1, T o2) {
for (Comparator<T> c:comparatorList) {
if (c.compare(o1, o2) > 0) {
return 1;
} else if (c.compare(o1, o2) < 0) {
return -1;
}
}
return 0;
}
};
Collections.sort(list, comparator);
}

Java-8 solution using Stream API:
List<Foo> sorted = list.stream()
.sorted(Comparator.comparing(Foo::getDate)
.thenComparing(Foo::getValue))
.collect(Collectors.toList());
If you want to sort the original list itself:
list.sort(Comparator.comparing(Foo::getDate)
.thenComparing(Foo::getValue));

If you want sample code looks like, you can use following:
Collections.sort(foos, new Comparator<Foo>{
public int compare(Foo a, Foo b) {
int dateComparison = a.date.compareTo(b.date);
return dateComparison == 0 ? a.value.compareTo(b.value) : dateComparison;
}
});

If the class of the object implements Comparable, then all you need to do is properly code the compareTo method to first compare dates, and then if dates are equal, compare values, and then return the appropriate int result based on the findings.

Related

Custom Comparator sort with multiple fields

Because this question is related to my last one, I will link it here.
Suppose I have a class TestB with two integers. I would be able to sort List<TestB> list on a and then on b like this:
list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB));
Now I want to know how to do that with the custom comparator in the last answer.
The custom Comparator version of list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB)); is:
list.sort(new Comparator<>() {
#Override
public int compare(TestB b1, TestB b2) {
int cmp = b1.getA().compareTo(b2.getA());
if (cmp == 0)
cmp = b1.getB().compareTo(b2.getB());
return cmp;
}
});
One option is to use what I call a custom generic multi-comparator:
list2.sort(getComparator( p -> p.getTestB().getA(),
p -> p.getTestB().getB() ));
private <T> Comparator<T> getComparator( Function<T, ? extends Comparable<?>>... functions ) {
return new Comparator<T>() {
#Override
public int compare(T obj1, T obj2) {
for (Function<T, ? extends Comparable<?>> function : functions) {
Comparable<T> res1 = (Comparable<T>) function.apply(obj1);
Comparable<T> res2 = (Comparable<T>) function.apply(obj2);
int result = res1.compareTo((T) res2);
if ( result != 0 ) {
return result;
}
}
return 0;
}
};
}
It will sort from left to right regarding the order which function parameters are placed. Warnings will be raised although. Because it's very generic.
Keep in mind that the types of the final values to be compared must implement Comparator (which primitive types like Integer already do) and you should deal with null problems (I didn't do it here to keep it short).

How to sort list of string based on custom logic in java

I have an arraylist with values ... ST1000, ST 5000, ST 30000, ST400, ST500, SP1000, SP600
I want it o be sorted like ST1000, ST2000, ST3000, ST4000, SP600, SP1000 .. How can I do that?
Thanks
you can use the compareTo which will compare it for you lexicographically
here are 2 methods to sort your list using java 8
list.stream()
.sorted((s1, s2) -> s1.compareTo(s2)).collect(Collectors.toList());
list.sort((s1, s2)->s1.compareTo(s2));
using java 8, you don't have to use Collections.sort method
You can sort any collection in a desired manner with a Collections.sort(List<T> list, Comparator<? super T> c) method.
All you need to do is to realize Comparator's compare method accordingly.
In your particular case it would go something like this:
List<String> list = Arrays.asList("ST1000", "ST5000", "ST30000", "ST400", "ST500", "SP1000", "SP600");
Collections.sort(list, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
String o1Prefix = o1.substring(0, 2);
String o2Prefix = o2.substring(0, 2);
if (o1Prefix.equals(o2Prefix)) {
return Integer.parseInt(o1.substring(2, o1.length())) > Integer.parseInt(o2.substring(2, o2.length())) ? 1 : -1;
} else if (o1Prefix.equals("ST")) {
return -1;
} else {
return 1;
}
}
});
YOu can create a custom Comparator object and override compare method with the desired logic.
Comparator<Model> customComparator = new Comparator<Model>() {
public int compare(Model arg0, Model arg1) {
//TODO custom sort logic
}
};
if compare returns -1 then the arg0 is less than arg1 , 0 equals and 1 greater
then sort the list using the custom comparator
Collections.sort(list, customComparator);

How to combine two Collections.sort functions

I have a program where i am have a list of Names, and how many people have that name. I want to put the names in alphabetical order while also putting the counts from greatest to least. If the name has the same count it puts the name in alphabetical order. I figured out how to put the names in abc order and figured out how to put the counts in greatest to least but i cant figure out how to combine the two to get list of names greatest to least and if they have the same count in alphabetical order.
Collections.sort(oneName, new OneNameCompare());
for(OneName a: oneName)
{
System.out.println(a.toString());
}
Collections.sort(oneName, new OneNameCountCompare());
for(OneName a: oneName)
{
System.out.println(a.toString());
}
You can make another Comparator that combines the effects of the two other Comparators. If one comparator compares equal, then you can call the second comparator and use its value.
public class CountNameComparator implements Comparator<Name>
{
private OneNameCompare c1 = new OneNameCompare();
private OneNameCountCompare c2 = new OneNameCountCompare();
#Override
public int compare(Name n1, Name n2)
{
int comp = c1.compare(n1, n2);
if (comp != 0) return comp;
return c2.compare(n1, n2);
}
}
Then you can call Collections.sort just once.
Collections.sort(oneName, new CountNameComparator());
This can be generalized for any number of comparators.
You can combine comparators like this
public static <T> Comparator<T> combine(final Comparator<T> c1, final Comparator<T> c2) {
return new Comparator<T>() {
public int compare(T t1, T t2) {
int cmp = c1.compare(t1, t2);
if (cmp == 0)
cmp = c2.compare(t1, t2);
return cmp;
}
};
}
BTW Comparators are a good example of when to use a stateless singleton. All comparators or a type are the same so you only ever need one of them.
public enum OneNameCompare implements Comparator<OneName> {
INSTANCE;
public int compare(OneName o1, OneName o2) {
int cmp = // compare the two objects
return cmp;
}
}
This avoid creating new instances or cache copies. You only ever need one of each type.
Assuming you're using the Apache Commons Collections API, you might want to check out ComparatorUtils.chainedComparator:
Collections.sort(oneName, ComparatorUtils.chainedComparator(new OneNameCompare(), new OneNameCountCompare());
Using lambdas from Java 8:
Collections.sort(Arrays.asList(""),
(e1, e2) -> e1.getName().compareTo(e2.getName()) != 0 ?
e1.getName().compareTo(e2.getName()) :
e1.getCount().compareTo(e2.getCount()));

How to use Collections.sort() in Java?

I got an object Recipe that implements Comparable<Recipe> :
public int compareTo(Recipe otherRecipe) {
return this.inputRecipeName.compareTo(otherRecipe.inputRecipeName);
}
I've done that so I'm able to sort the List alphabetically in the following method:
public static Collection<Recipe> getRecipes(){
List<Recipe> recipes = new ArrayList<Recipe>(RECIPE_MAP.values());
Collections.sort(recipes);
return recipes;
}
But now, in a different method, lets call it getRecipesSort(), I want to sort the same list but numerically, comparing a variable that contains their ID. To make things worse, the ID field is of the type String.
How do I use Collections.sort() to perform the sorts in Java?
Use this method Collections.sort(List,Comparator) . Implement a Comparator and pass it to Collections.sort().
class RecipeCompare implements Comparator<Recipe> {
#Override
public int compare(Recipe o1, Recipe o2) {
// write comparison logic here like below , it's just a sample
return o1.getID().compareTo(o2.getID());
}
}
Then use the Comparator as
Collections.sort(recipes,new RecipeCompare());
The answer given by NINCOMPOOP can be made simpler using Lambda Expressions:
Collections.sort(recipes, (Recipe r1, Recipe r2) ->
r1.getID().compareTo(r2.getID()));
Also introduced after Java 8 is the comparator construction methods in the Comparator interface. Using these, one can further reduce this to 1:
recipes.sort(comparingInt(Recipe::getId));
1 Bloch, J. Effective Java (3rd Edition). 2018. Item 42, p. 194.
Create a comparator which accepts the compare mode in its constructor and pass different modes for different scenarios based on your requirement
public class RecipeComparator implements Comparator<Recipe> {
public static final int COMPARE_BY_ID = 0;
public static final int COMPARE_BY_NAME = 1;
private int compare_mode = COMPARE_BY_NAME;
public RecipeComparator() {
}
public RecipeComparator(int compare_mode) {
this.compare_mode = compare_mode;
}
#Override
public int compare(Recipe o1, Recipe o2) {
switch (compare_mode) {
case COMPARE_BY_ID:
return o1.getId().compareTo(o2.getId());
default:
return o1.getInputRecipeName().compareTo(o2.getInputRecipeName());
}
}
}
Actually for numbers you need to handle them separately check below
public static void main(String[] args) {
String string1 = "1";
String string2 = "2";
String string11 = "11";
System.out.println(string1.compareTo(string2));
System.out.println(string2.compareTo(string11));// expected -1 returns 1
// to compare numbers you actually need to do something like this
int number2 = Integer.valueOf(string1);
int number11 = Integer.valueOf(string11);
int compareTo = number2 > number11 ? 1 : (number2 < number11 ? -1 : 0) ;
System.out.println(compareTo);// prints -1
}
Use the method that accepts a Comparator when you want to sort in something other than natural order.
Collections.sort(List, Comparator)
Sort the unsorted hashmap in ascending order.
// Sorting the list based on values
Collections.sort(list, new Comparator<Entry<String, Integer>>() {
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2)
{
return o2.getValue().compareTo(o1.getValue());
}
});
// Maintaining insertion order with the help of LinkedList
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Entry<String, Integer> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}

java comparator on multidimensional array

Is there a better way to write this comparator? I have an equivalent of multidimensional array where columns are Object's. The actual objects are String and BigDecimal 99% of the time. I am sorting "rows" on a given column index.
I want to avoid instanceof.
protected static class MyComparator implements Comparator<DataRow> {
private int idx;
public MyComparator(int idx) {
this.idx = idx;
}
#Override
public int compare(DataRow r1, DataRow r2) {
Object o1 = r1.getColumns()[idx];
Object o2 = r2.getColumns()[idx];
if (o1 instanceof String){
return ((String)o1).compareTo((String)o2);
}else if (o1 instanceof BigDecimal){
return ((BigDecimal)o1).compareTo((BigDecimal)o2);
}else{
throw new UnsupportedOperationException("comparison cannot be performed");
}
}
Since both String and BigDecimal are Comparables:
return ((Comparable)o1).compareTo(o2);
I think since you only depend on the type Comparable you could rewrite it as:
public int compare(DataRow r1, DataRow r2) {
Comparable o1 = (Comparable) r1.getColumns()[idx];
Comparable o2 = (Comparable) r2.getColumns()[idx];
return o1.compareTo(o2);
}
If you carefully populate the table you shouldn't face the UnsupportedOperationException situation.
If the colum types are not comparable, I would implement separate comparators and make the Column class a factory for the correct comparator that should be used.

Categories

Resources