How to Sort an Arraylist Using Multiple Sorting Criteria

How to sort an ArrayList using multiple sorting criteria?

Create an appropiate Comparator that will compare two items according to your desired criteria. Then use Collections.sort() on your ArrayList.

If at a later time you want to sort by different criteria, call Collections.sort() again with a different Comparator.

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

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

sort an ArrayList with multiple conditions

You can create a Comparator to first compare by city, then by age. Something like this:

Comparator<MyObject> comparator = new Comparator<MyObject>(){
@Override
public int compare(final MyObject o1, final MyObject o2){
if(!o1.getCity().equals(o2.getCity())){
return o1.getCity().compareTo(o2.getCity());
}else{
return o1.getAge().compareTo(o2.getAge());
}
}
};

Collections.sort(myArrayList,comparator);

Edit:

I used the "compareTo" method of the Integer class, which you can't call on an int primitive type. If you use int for the age, you could just write out an if statement for the comparison.

Collections.sort for multiple conditions

You can use Java Streams. This is also used when using Collection.sort():

myList.sort(Comparator.comparing(MyObject::getAttributeX)
.thenComparing(i -> i.getSomething().getSubValue())
.thenComparing((a, b) -> a.getInt() - b.getInt()));

If you are using a lower version than Java 8 you have to implement the sort logic yourself in a Comparator or use an external library:

Collections.sort(myList, new Comparator<MyObject>() {
@Override
public int compare(MyObject a, MyObject b) {
int cmp0 = a.getAttributeX().compareTo(b.getAttributeX());
if (cmp0 != 0) {
return cmp0;
}
int cmp1 = a.getSomething().getSubValue().compareTo(b.getSomething().getSubValue());
if (cmp1 != 0) {
return cmp1;
}
return a.getInt() - b.getInt();
}
});

Sort list by multiple fields and multiple criteria

As I understood this problem you rely on an existing API that expects a list of events and want them to be processed altogether as a single list ordered accordingly with their start and end date-time. And that is achievable.

I assume that the time-related event data stored inside a ProgrammData object is of type String. If it's not the case, and these fields are for instance of legacy type Date then only a couple of small changes need to be done.

My idea is to encapsulate all functionality inside the utility class TwoWaySorting so that all implementation details are abstracted away. And in the client code only this line TwoWaySorting.getSorted(programmData) is needed to produce a sorted list of events.

Since the responsibility of the TwoWaySorting class isn't related to the state all its behavior is marked with static modifier and its constructor is private. It has a nested static class Element which is a wrapper on top of the ProgrammData (if my assumption that ProgrammData is part of the existing IPA is correct then it must be used as is without any changes). So Element's class concern is to represent the time-related data of the wrapped ProgrammData object in a convenient form for sorting.

That is a brief overview of what the method getSorted() does:

  • it obtains an instance the Comparator, that is based on the current time;
  • creates the stream over the list of events and wraps each event with an instance of the Element class;
  • sorts the elements;
  • extracts original events and collects them into a list.

To wrap an event the static method LocalDateTime.parse() which accept a CharSequence and an appropriate formatter is used to parse string-based time-related data.

The centerpiece of TwoWaySorting class is a comparator returned by the getComparator() method, let's examine it closely.

The first part of it is responsible for dividing the elements into two groups based on currentTime:

Comparator.<Element, Boolean>comparing(element -> element.getEnd().isBefore(currentTime))

As its name suggest the instance methed isBefore() of the LocalDateTime class returns true if this date-time object is before the date-time object passed as an argument.

According to the natural sorting order of boolean values false comes before true. So for an invent that ends in the future isBefore() will yield false, which means that it'll appear at the beginning of the sorted list.

The second part of the comparator is responsible for ordering of the past and future events:

.thenComparingLong(element -> element.getEnd().isAfter(currentTime) ?
element.getStart().toEpochSecond(ZoneOffset.of("+00:00")) :
element.getEnd().toEpochSecond(ZoneOffset.of("+00:00")) * -1);

Returns:
a lexicographic-order comparator composed of this and then the long sort key

Method thenComparingLong() (a quote from the javadoc is shown above) returns an aggregated comparator comprised of the comparator that was obtained previously (that separates past and future events) and a comparator thas is based on the ToLongFunction provided as an argument, which compares elements accordingly with long values extracted by that function.

Method toEpochSecond() extructs from a date-time object the number of seconds from the epoch as long.

I assume that it is sufficient for this task because in the example time is described with a precision of minutes. ZoneOffset that is expected by toEpochSecond() as an argument, in this case, has no influence on the result, and offset for Greenwich can be substituted with any other valid offset.

Since future events have to be sorted in ascending order value produced by the toEpochSecond() is used as is, for past events that must be sorted in descending order it's multiplied by -1 to reverse the result of the comparison.

Note:

  • in order to combine these two comparators described above together we have to provide generic type information explicitly, like that: <Element,Boolean>comparing(). Without an explicit declaration, the compiler has not enough data to determine the type of the variable element, and inside both comparing() and thenComparingLong() its type will be inferred as Object. If we used only one of these static methods the type of the element will be correctly inferred by the compiler as Element based on the return type of the method getComparator(). But for our case, we need to provide this information explicitly.

for information on the syntax of generic methods, take a look at this tutorial

TwoWaySorting class

public class TwoWaySorting {
private static final DateTimeFormatter PD_FORMATTER
= DateTimeFormatter.ofPattern("yyyy-MM-ddHHmm");

private TwoWaySorting() {} // no way and no need to instantiate this class

private static Comparator<Element> getComparator() {
LocalDateTime currentTime = LocalDateTime.now();

return Comparator.<Element, Boolean>comparing(element -> element.getEnd().isBefore(currentTime))
.thenComparingLong(element -> element.getEnd().isAfter(currentTime) ?
element.getStart().toEpochSecond(ZoneOffset.of("+00:00")) :
element.getEnd().toEpochSecond(ZoneOffset.of("+00:00")) * -1);
}

public static List<ProgrammData> getSorted(List<ProgrammData> programmData) {
Comparator<Element> twoWayComparator = getComparator();

return programmData.stream()
.map(TwoWaySorting::parseData)
.sorted(twoWayComparator)
.map(Element::getData)
.collect(Collectors.toList());
}

private static Element parseData(ProgrammData data) {
return new Element(data,
LocalDateTime.parse(data.getStart(), PD_FORMATTER),
LocalDateTime.parse(data.getEnd(), PD_FORMATTER));
}

private static class Element {
private ProgrammData data;
private LocalDateTime start;
private LocalDateTime end;

// constructor and getters
}
}

This solution is meant to be clean and reusable. So in the main() apart from the source list there's only one line that obtains a sorted list and prints it on the console.

Note: getSorted() doesn't cause mutation of the source but creates and new list.

public static void main(String[] args) {
List<ProgrammData> programmData = // a list dummy ProgrammData objects
List.of(new ProgrammData("event1", "2022-02-220100", "2022-02-220300"),
new ProgrammData("event2", "2022-02-220200", "2022-02-241800"),
new ProgrammData("event3", "2022-02-251200", "2022-02-281500"),
new ProgrammData("event4", "2022-02-261600", "2022-02-262100"));

TwoWaySorting.getSorted(programmData)
.forEach(System.out::println);
}

Output (identical with the provided example)

ProgrammData [event3, 2022-02-251200, 2022-02-281500]
ProgrammData [event4, 2022-02-261600, 2022-02-262100]
ProgrammData [event2, 2022-02-220200, 2022-02-241800]
ProgrammData [event1, 2022-02-220100, 2022-02-220300]

Collections.sort with multiple fields

Do you see anything wrong with the code?

Yes. Why are you adding the three fields together before you compare them?

I would probably do something like this: (assuming the fields are in the order you wish to sort them in)

@Override public int compare(final Report record1, final Report record2) {
int c;
c = record1.getReportKey().compareTo(record2.getReportKey());
if (c == 0)
c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
if (c == 0)
c = record1.getSchool().compareTo(record2.getSchool());
return c;
}

How to sort by two fields in Java?

You can use Collections.sort as follows:

private static void order(List<Person> persons) {

Collections.sort(persons, new Comparator() {

public int compare(Object o1, Object o2) {

String x1 = ((Person) o1).getName();
String x2 = ((Person) o2).getName();
int sComp = x1.compareTo(x2);

if (sComp != 0) {
return sComp;
}

Integer x1 = ((Person) o1).getAge();
Integer x2 = ((Person) o2).getAge();
return x1.compareTo(x2);
}});
}

List<Persons> is now sorted by name, then by age.

String.compareTo "Compares two strings lexicographically" - from the docs.

Collections.sort is a static method in the native Collections library. It does the actual sorting, you just need to provide a Comparator which defines how two elements in your list should be compared: this is achieved by providing your own implementation of the compare method.

Java: Sorting objects in an ArrayList with conditions

Create an appropiate Comparator that will compare two items according to your desired criteria. Then use Collections.sort() on your ArrayList.

If at a later time you want to sort by different criteria, call Collections.sort() again with a different Comparator.

Reference: How to sort an ArrayList using multiple sorting criteria?

Just an advice for future questions, please google properly or search on stackoverflow for your problem before asking a new question. Most of the time such trivial questions have already been asked and answered.



Related Topics



Leave a reply



Submit