Reversing part of the CompareTo method from custom objects - java

I have a object called project, I want to sort this project by 2 of its fields:
First: by Date(Gregorian Callander);
Second: by Name(String);
I want to sort the project by date form new to old. The only way I know to do this is to reverse the collection. However I want to sort the project with same date on name(alphabetically), where reverse also reverses this part of the sort.
Is there a way to reverse only part of the sort method, or any other way to get this sorted first by a date(reverse) and then a string(normal order a-z) ?
At the moment I am overriding the object compareTo method like so:
#Override
public int compareTo(Project project) {
int i = this.projectDate.compareTo(project.projectDate);
if(i != 0) return i;
return this.projectName.compareTo(project.projectName);
}

Date#compareTo returns a value < 0 if this Date is before the Date argument and a value > 0 otherwise.
If you want to reverse the sort from new to old, you can just return the negative compare result:
#Override
public int compareTo(Project project) {
int i = this.projectDate.compareTo(project.projectDate);
if(i != 0) return -i; // reverse sort
return this.projectName.compareTo(project.projectName);
}

In Java 8, the Comparator interface has a method thenComparing. You can use this method to create a comparator that compare by more than one field.
If you have a comparator to compare alphabetically and other to compare by dates, you can combine the comparator to sort by the field you want:
Comparator<Project> nameComparator = ...
Comparator<Project> dateComparator = ...
You can mix the comparator, using the reverse comparator if needed. These are some examples:
Comparator<Project> nameAndDateComparator = nameComparator.thenComparing(dateComparator);
Comparator<Project> nameAndReversedDateComparator = nameComparator.thenComparing(dateComparator.reversed());
Then, you can use the method sort as usual with the comparator that matches your needs.
If you are not using Java 8, you can create an utility class to combine your comparators:
public class CombinedComparator<T> implements Comparator<T> {
Comparator<T> firstComparator;
Comparator<T> secondComparator;
public CombinedComparator(Comparator<T> firstComparator, Comparator<T> secondComparator) {
this.firstComparator = firstComparator;
this.secondComparator = secondComparator;
}
#Override
public int compare(T o1, T o2) {
int result = firstComparator.compare(o1, o2);
return (result != 0) ? result : secondComparator.compare(o1, o2);
}
}
And you could create multiple fields comparators this way:
Comparator<Project> nameAndDateComparator = new CombinedComparator<Project>(nameComparator, dateComparator);
Comparator<Project> nameAndReversedDateComparator = new CombinedComparator<Project>(nameComparator, Collections.reverseOrder(dateComparator));

Related

How to sort list with two object fields?

Good Morning!
I'm trying to compare a list by two fields, but the result is not working, can you help me?
I have an example list:
Name - MesesDuracao - Buy
A - 1 - 10
A - 2 - 5
B - 1 - 8
I would like the ordering to look like this:
A-1-10
B-1-8
A-2-5
I'm trying this way:
Collections.sort (testImportados, new Comparator <BeanRelEstatisticaMateriaPrima> () {
#Override
public int compare (BeanRelEstatisticaMateriaPrima p1, BeanRelEstatisticaMateriaPrima p2)
{
int comparison = p1.getMesesduration (). compareTo (p2.getMesesduration ());
return comparison == 0? p1.getQtyBuy (). compareTo (p2.getQtyBuy ()): comparison;
}
});
However, it does only sorting by "getMesesduration ()", it is not sorting by quantity purchased.
Any suggestion?
Thank you
Collections.sort(
testImportados,
Comparator.comparing(BeanRelEstatisticaMateriaPrima::getMesesduration)
.thenComparing(BeanRelEstatisticaMateriaPrima::getQtyBuy));
Should be the simplest way to provide the correct Comparator, if you are using java-8 already.
I did not understand your methods.
getQtyComprar() makes what?
getQtyBuy() makes what?
Two methods are different i think. But you are comparing two different method call in your comparator as p1.getQtyComprar().compareTo(p2.getQtyBuy ()). So your final Comparator should be in same method usage form.
If you wants to compare getQtyComprar after first comparing , code looks like ;
Collections.sort (testImportados, new Comparator <BeanRelEstatisticaMateriaPrima> () {
#Override
public int compare (BeanRelEstatisticaMateriaPrima p1, BeanRelEstatisticaMateriaPrima p2)
{
int comparison = p1.getMesesduration (). compareTo (p2.getMesesduration ());
return comparison == 0? p1.getQtyComprar (). compareTo (p2.getQtyComprar()): dateComparison;
}
});
If you wants to compare getQtyBuy after first comparing , code looks like ;
Collections.sort (testImportados, new Comparator <BeanRelEstatisticaMateriaPrima> () {
#Override
public int compare (BeanRelEstatisticaMateriaPrima p1, BeanRelEstatisticaMateriaPrima p2)
{
int comparison = p1.getMesesduration().compareTo(p2.getMesesduration());
return comparison == 0? p1.getQtyBuy().compareTo (p2.getQtyBuy()): dateComparison;
}
});
As a result of my post , chose your comparing method carefully.
I think you want to first sort by quantity then by duration.
Collections.sort (testImportados, new Comparator <BeanRelEstatisticaMateriaPrima> () {
#Override
public int compare (BeanRelEstatisticaMateriaPrima p1, BeanRelEstatisticaMateriaPrima p2) {
// first sort by quantity
int cmp = Integer.compare(p1.getQtyBuy(), p2.getQtyBuy());
// the quantities are the same, then sort by duration
if (cmp == 0) {
cmp = p1.getMesesDuration().compareTo(p2.getMesesDuration());
}
return cmp;
}
I forgot to mention, I have negative values ​​in getQtyBuy and it looks like the code is getting lost.
I tried this other code:
public class CustomerSortingComparator implements Comparator < BeanRelEstatisticaMateriaPrima > {
#Override
public int compare(BeanRelEstatisticaMateriaPrima cust1, BeanRelEstatisticaMateriaPrima cust2) {
// all comparison
int compareMes = cust1.getMesesduration().compareTo(cust2.getMesesduration());
int compareBuy = cust1.getQtyBuy().compareTo(cust2.getQtyBuy());
int compareName = cust1.getProdname().compareTo(cust2.getProdname());
// 3-level comparison using if-else block
if (compareMes == 0) {
return ((compareBuy == 0) ? compareName : compareBuy);
} else {
return compareMes;
}
}
}
Collections.sort(testImportados, new CustomerSortingComparator());
Continue sorting only by one parameter getMesesduration()
I do not understand.

Sort arraylist of objects containing epochs in Java

I've an arraylist of objects like arrlist =[obj1, obj2,obj3], where each object contains epochs and an Integer i.e. obj_i = {"1493275005401", 2} for all i . I want to sort arraylist on the basis of epochs. Being a beginner in java, I don't know much how to do this.
Note: I did some googling, came up with Collections.sort() but I'm not sure how to tell sort to sort on a specific field of the object.
Any help is appreciated.
Edit: Does it change the sorted output if I consider epoch(TIME) as just string instead of time.?
You can use Collections.sort with a Comparator, consider you have this :
List<Item> arrlist = Arrays.asList(new Item("1493275005401", 2), new Item("14932", 5),
new Item("778888", 1));
If you want to sort using the first Item which is String you can use :
Collections.sort(arrlist, (o1, o2) -> {
return o1.getName().compareTo(o2.getName());
});
If you want to sort using the second Item which is and int you can use :
Collections.sort(arrlist, (o1, o2) -> {
return o1.getId() - o2.getId();
});
Note : You can also sort your List with multiple itmes.
Edit
because epoch is an Integer you can convert it to BigInteger and compare your dates :
Collections.sort(arrlist, (o1, o2) -> {
return new BigInteger(o1.getName()).compareTo(new BigInteger(o2.getName()));
});
You can use Collections.sort(List<T>, Comparator<T>) to do this. You just need to specify a Comparator that uses the epoch component of your datatype.
Lets assume your type looks like this (the epoch seems to be a String):
class Container {
String epoch;
Integer i;
Container(String e, Integer ii){
epoch = e;
i = ii;
}
}
Then you can create a Comparator that compares the numerical (!) value of the epoch String:
class ContComparator implements Comparator<Container> {
#Override
public int compare(Container o1, Container o2) {
final Long epoch2 = Long.valueOf(o2.epoch);
final Long epoch1 = Long.valueOf(o1.epoch);
return epoch1.compareTo(epoch2);
}
}
And use it like this:
List<Container> arrlist = ... ;
Collections.sort(arrlist, new ContComparator());
Try Comparators:
list.sort(Comparator.comparing(obj -> Long.parseLong(obj.getEpoch())));
This is the newer syntax and Comparator API introduced with Java 8. The comparing method takes a function as a parameter that will extract the relevant attributes from the elements in the list.
If you read the JavaDoc of the comparing methods of Comparator you will find more information on how to modify the sorting further.
For a better understanding on what is happening:
list.sort(new Comparator<MyObject>() {
public int compare(MyObject o1, MyObject o2) {
return Long.parseLong(o1.getEpoch().toMillis()) - Long.parseLong(o2.getEpoch().toMillis());
}
})
For sorting by epoch time, we can do this
anyList.sort(Comparator.comparing(o.getEpoch()))

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);

Compare two sets of different types

I'm looking for a way to tell if two sets of different element types are identical if I can state one-to-one relation between those element types. Is there a standard way for doing this in java or maybe guava or apache commons?
Here is my own implementation of this task. For example, I have two element classes which I know how to compare. For simplicity, I compare them by id field:
class ValueObject {
public int id;
public ValueObject(int id) { this.id=id; }
public static ValueObject of(int id) { return new ValueObject(id); }
}
class DTO {
public int id;
public DTO(int id) { this.id=id; }
public static DTO of(int id) { return new DTO(id); }
}
Then I define an interface which does the comparison
interface TwoTypesComparator<L,R> {
boolean areIdentical(L left, R right);
}
And the actual method for comparing sets looks like this
public static <L,R> boolean areIdentical(Set<L> left, Set<R> right, TwoTypesComparator<L,R> comparator) {
if (left.size() != right.size()) return false;
boolean found;
for (L l : left) {
found = false;
for (R r : right) {
if (comparator.areIdentical(l, r)) {
found = true; break;
}
}
if (!found) return false;
}
return true;
}
Example of a client code
HashSet<ValueObject> valueObjects = new HashSet<ValueObject>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<DTO>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
System.out.println(areIdentical(valueObjects, dtos, new TwoTypesComparator<ValueObject, DTO>() {
#Override
public boolean areIdentical(ValueObject left, DTO right) {
return left.id == right.id;
}
}));
I'm looking for the standard solution to to this task. Or any suggestions how to improve this code are welcome.
This is what I would do in your case. You have sets. Sets are hard to compare, but on top of that, you want to compare on their id.
I see only one proper solution where you have to normalize the wanted values (extract their id) then sort those ids, then compare them in order, because if you don't sort and compare you can possibly skip pass over duplicates and/or values.
Think about the fact that Java 8 allows you to play lazy with streams. So don't rush over and think that extracting, then sorting then copying is long. Lazyness allows it to be rather fast compared to iterative solutions.
HashSet<ValueObject> valueObjects = new HashSet<>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
boolean areIdentical = Arrays.equals(
valueObjects.stream()
.mapToInt((v) -> v.id)
.sorted()
.toArray(),
dtos.stream()
.mapToInt((d) -> d.id)
.sorted()
.toArray()
);
You want to generalize the solution? No problem.
public static <T extends Comparable<?>> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted()
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted()
.toArray()
);
}
And for a T that is not comparable:
public static <T> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor, Comparator<T> comparator) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted(comparator)
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted(comparator)
.toArray()
);
}
You mention Guava and if you don't have Java 8, you can do the following, using the same algorithm:
List<Integer> voIds = FluentIterables.from(valueObjects)
.transform(valueObjectIdGetter())
.toSortedList(intComparator());
List<Integer> dtoIds = FluentIterables.from(dtos)
.transform(dtoIdGetter())
.toSortedList(intComparator());
return voIds.equals(dtoIds);
Another solution would be to use List instead of Set (if you are allowed to do so). List has a method called get(int index) that retrieves the element at the specified index and you can compare them one by one when both your lists have the same size. More on lists: http://docs.oracle.com/javase/7/docs/api/java/util/List.html
Also, avoid using public variables in your classes. A good practice is to make your variables private and use getter and setter methods.
Instantiate lists and add values
List<ValueObject> list = new ArrayList<>();
List<DTO> list2 = new ArrayList<>();
list.add(ValueObject.of(1));
list.add(ValueObject.of(2));
list.add(ValueObject.of(3));
list2.add(DTO.of(1));
list2.add(DTO.of(2));
list2.add(DTO.of(34));
Method that compares lists
public boolean compareLists(List<ValueObject> list, List<DTO> list2) {
if(list.size() != list2.size()) {
return false;
}
for(int i = 0; i < list.size(); i++) {
if(list.get(i).id == list2.get(i).id) {
continue;
} else {
return false;
}
}
return true;
}
Your current method is incorrect or at least inconsistent for general sets.
Imagine the following:
L contains the Pairs (1,1), (1,2), (2,1).
R contains the Pairs (1,1), (2,1), (2,2).
Now if your id is the first value your compare would return true but are those sets really equal? The problem is that you have no guarantee that there is at most one Element with the same id in the set because you don't know how L and R implement equals so my advise would be to not compare sets of different types.
If you really need to compare two Sets the way you described I would go for copying all Elements from L to a List and then go through R and every time you find the Element in L remove it from the List. Just make sure you use LinkedList instead of ArrayList .
You could override equals and hashcode on the dto/value object and then do : leftSet.containsAll(rightSet) && leftSet.size().equals(rightSet.size())
If you can't alter the element classes, make a decorator and have the sets be of the decorator type.

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()));

Categories

Resources