Java Sorting Based on Enum Constants

Java Sorting based on Enum constants

Enum<E> implements Comparable<E> via the natural order of the enum (the order in which the values are declared). If you just create a list of the enum values (instead of strings) via parsing, then sort that list using Collections.sort, it should sort the way you want. If you need a list of strings again, you can just convert back by calling name() on each element.

How to sort a list of objects by an enum field?

name returns the name of the enum constants, so you can use that in Comparator.comparing:

items.sort(Comparator.comparing(item -> item.level.name()));

After thinking about a bit further, I don't actually think this might not be a good design. If you do this, then you can't freely change your enum constant names, because that might also change the sort order. This is especially problematic if someone else who doesn't know that you are relying on enum names, reads your code and renames the constants for whatever reason (renaming normally won't break things if you use the IDE's rename tool), then your sorting code would sort them in a different order, which could potentially break many other things.

How can I sort Employee Objects using enum constants?

Here is one option. You can have the Enum implement the Comparator interface for type Employee then delegate the Comparator to each element by having a constructor that provides a Comparator. You can then have the sort function use Stream#sorted and the enum object since it implements Comparator to handle the sorting and return a new list without modifying the old one.

        enum SortMethod implements Comparator<Employee> {
NAME(Comparator.comparing(Employee::getName)),

SALARY(Comparator.comparingInt(Employee::getSalary));

private final Comparator<Employee> comparator;

SortMethod(Comparator<Employee> comparator) {
this.comparator = comparator;
}

@Override
public int compare(Employee o1, Employee o2) {
return comparator.compare(o1, o2);
}
};

public List<Employee> sort(List<Employee> emps,final SortMethod method){
return emps.stream().sorted(method).collect(Collectors.toList());
}

On an unrelated note

Try to make Employee immutable. Always make objects immutable unless you otherwise need to. If you need to change the state of an Object just return a new object by using the old one.

Employee employeeWithRaise = underpaidEmployee.increaseSalary(10_000);

Employee#increaseSalaray might look like this;

public Employee increaseSalary(int salaryIncrease) {
return new Employee(name, salary + salaryIncrease);
}

Typescript Sorting based on Enum constants

If you look at the compiled javascript of your enum:

var MyEnum;
(function (MyEnum) {
MyEnum[MyEnum["VALUE_1"] = 0] = "VALUE_1";
MyEnum[MyEnum["VALUE_3"] = 1] = "VALUE_3";
MyEnum[MyEnum["VALUE_2"] = 2] = "VALUE_2";
})(MyEnum || (MyEnum = {}));

You'll see that each gets an ordinal number based on the position, so the first is 0 and the last is 2.

If you refer to an enum you'll just get a number back:

console.log(MyEnum.VALUE_3); // 1

If you want to sort your list:

let list = [MyEnum.VALUE_3, MyEnum.VALUE_1, MyEnum.VALUE_2];
console.log(list); // [1, 0, 2]
list.sort((a, b) => a - b);
console.log(list); // [0, 1, 2]

If you want the list of the string names of the enum sorted by the ordinal then you can do:

let names = list.map(ordinal => MyEnum[ordinal]);
console.log(names); // ["VALUE_1", "VALUE_3", "VALUE_2"]

(code in playground)


Edit

You can sort in the same way, regardless of how you set the enum values, you just need to change the compare function.

For example, this will sort the list based on the lexicographical order of the enum string values:

enum MyEnum {
VALUE_1 = "value 1" as any,
VALUE_3 = "value 3" as any,
VALUE_2 = "value 2" as any
}

let list = [MyEnum.VALUE_3, MyEnum.VALUE_1, MyEnum.VALUE_2];
console.log(list); // ["value 3", "value 1", "value 2"]
list.sort((a, b) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
});
console.log(list); // ["value 1", "value 2", "value 3"]

let names = list.map(ordinal => MyEnum[ordinal]);
console.log(names); // ["VALUE_1", "VALUE_2", "VALUE_3"]

(code in playground)


Edit #2

Sorting by the original order of the enum is tricky, you can try:

enum MyEnum {
VALUE_1 = "value 1" as any,
VALUE_3 = "value 3" as any,
VALUE_2 = "value 2" as any
}

let list = [MyEnum.VALUE_3, MyEnum.VALUE_1, MyEnum.VALUE_2];
console.log(list); // ["value 3", "value 1", "value 2"]

let sorted = [] as MyEnum[];
let index = 0;
for (let key in MyEnum) {
if (index % 2 === 0) {
sorted.push(key as any);
}

index++;
}

console.log(sorted); // ["VALUE_1", "VALUE_3", "VALUE_2"]

let names = sorted.map(ordinal => MyEnum[ordinal]);
console.log(names); // ["value 1", "value 3", "value 2"]

(code in playground)

This seems to work, but you shouldn't count on the order which is received in the for/in loop, unless you don't care about cross-browser behavior (or to be specific explorer support), you can read about it in MDN.

Sort elements in list by enum value or string value

It’s not clear which order you want for the enum types, declaration order (PROFILE, ROLE, USER) or lexicographic order of their toString() representation.

In the latter case, you could implement the method as

private List<Issue> sortList(List<Issue> list, String field, String sort) {
Function<Issue,String> f;
switch(field) {
case "Title": f = Issue::getTitle; break;
case "IssueElement": f = i -> i.getIssueElement().toString(); break;
case "IssueType": f = i -> i.getIssueType().toString(); break;
default: throw new IllegalArgumentException("unknown property '"+field+"'");
}
Comparator<Issue> cmp = Comparator.comparing(f);
if("DESC".equalsIgnoreCase(sort)) cmp = cmp.reversed();
else if(!"ASC".equalsIgnoreCase(sort))
throw new IllegalArgumentException("invalid sort '"+sort+"'");
return list.stream().sorted(cmp).collect(Collectors.toList());
}

If you want to use the enum declaration order instead, you have slightly less common code:

private List<Issue> sortList(List<Issue> list, String field, String sort) {
Comparator<Issue> cmp;
switch(field) {
case "Title": cmp = Comparator.comparing(Issue::getTitle); break;
case "IssueElement": cmp = Comparator.comparing(Issue::getIssueElement); break;
case "IssueType": cmp = Comparator.comparing(Issue::getIssueType); break;
default: throw new IllegalArgumentException("unknown property '"+field+"'");
}
if("DESC".equalsIgnoreCase(sort)) cmp = cmp.reversed();
else if(!"ASC".equalsIgnoreCase(sort))
throw new IllegalArgumentException("invalid sort '"+sort+"'");
return list.stream().sorted(cmp).collect(Collectors.toList());
}

Instead of the switch statement, you could also maintain a map of existing orders, which offers more flexibility:

// in Java 9, you should replace Arrays.asList(...) with List.of(...)
static final Map<List<String>,Comparator<Issue>> ORDER;
static {
Map<List<String>,Comparator<Issue>> m = new HashMap<>();
Comparator<Issue> c = Comparator.comparing(Issue::getTitle);
m.put(Arrays.asList("Title", "asc"), c);
m.put(Arrays.asList("Title", "desc"), c.reversed());
c = Comparator.comparing(Issue::getIssueElement);
m.put(Arrays.asList("IssueElement", "asc"), c);
m.put(Arrays.asList("IssueElement", "desc"), c.reversed());
c = Comparator.comparing(Issue::getIssueType);
m.put(Arrays.asList("IssueType", "asc"), c);
m.put(Arrays.asList("IssueType", "desc"), c.reversed());
ORDER = Collections.unmodifiableMap(m);
}
private List<Issue> sortList(List<Issue> list, String field, String sort) {
Comparator<Issue> cmp = ORDER.get(Arrays.asList(field, sort.toLowerCase(Locale.ROOT)));
if(cmp == null)
throw new IllegalArgumentException("property '"+field+"', sort '"+sort+"'");
return list.stream().sorted(cmp).collect(Collectors.toList());
}

This approach can be adapted to your new requirement, though, I strongly suggest a slight redesign:

enum Direction { ASCENDING, DESCENDING }
public interface ISort {
String getField();
void setField(final String field);
Direction getSort();
void setSort(final Direction type);
}

Adapting the implementation is straight-forward, but you should avoid allowing null for the sorting direction, as then, it’s intrisically only either of the two legal values:

public class SortDTO implements ISort {
private String field;
private Direction sort;
public SortDTO() { this(null, Direction.ASCENDING); }
public SortDTO(String field, Direction sort) {
this.field = field;
this.sort = sort;
}
public String getField() { return field; }
public void setField(String field) { this.field = field; }
public Direction getSort() { return sort; }
public void setSort(Direction sort) { this.sort = Objects.requireNonNull(sort); }
@Override
public String toString() {
return String.format("Sort[field=%s, sort=%s]", this.field, this.sort);
}
}

We augment these types with an immutable key type capable of capturing the current state of an ISort implementation and having proper equals and hashCode implementations:

final class SortKey {
final String field;
final Direction direction;
private SortKey(String f, Direction d) { field=f; direction=d; }
@Override
public int hashCode() {
return field.hashCode()*2+direction.ordinal();
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof SortKey)) return false;
SortKey that = (SortKey)obj;
return this.direction == that.direction && this.field.equals(that.field);
}
static SortKey of(String field, Direction dir) {
return new SortKey(Objects.requireNonNull(field), Objects.requireNonNull(dir));
}
static SortKey of(ISort s) {
return of(s.getField(), s.getSort());
}
}

Then, the adapted solution may look like

static final Map<SortKey,Comparator<Issue>> ORDER;
static {
Map<SortKey,Comparator<Issue>> m = new HashMap<>();
Comparator<Issue> c = Comparator.comparing(Issue::getTitle);
m.put(SortKey.of("Title", Direction.ASCENDING), c);
m.put(SortKey.of("Title", Direction.DESCENDING), c.reversed());
c = Comparator.comparing(Issue::getIssueElement);
m.put(SortKey.of("IssueElement", Direction.ASCENDING), c);
m.put(SortKey.of("IssueElement", Direction.DESCENDING), c.reversed());
c = Comparator.comparing(Issue::getIssueType);
m.put(SortKey.of("IssueType", Direction.ASCENDING), c);
m.put(SortKey.of("IssueElement", Direction.DESCENDING), c.reversed());
ORDER = Collections.unmodifiableMap(m);
}
private List<Issue> sortList(List<Issue> list, ISort... order) {
if(order.length == 0) return new ArrayList<>(list);
Comparator<Issue> cmp = ORDER.get(SortKey.of(order[0]));
if(cmp == null) throw new IllegalArgumentException(order[0].toString());
for(int ix = 1; ix < order.length; ix++) {
Comparator<Issue> next = ORDER.get(SortKey.of(order[ix]));
if(next == null) throw new IllegalArgumentException(order[ix].toString());
cmp = cmp.thenComparing(next);
}
return list.stream().sorted(cmp).collect(Collectors.toList());
}

This allows an arbitrary number of sort criteria, the first being the primary order, the second being the secondary order and so on.

How do I sort enum members alphabetically in Java?

SortedMap<String, Letter> map = new TreeMap<String, Letter>();
for (Letter l : Letter.values()) {
map.put(l.getDescription, l);
}
return map.values();

Or just reorder the declarations :-)

Edit: As KLE pointed out, this assumes that the Descriptions are unique within the enum.

Sort TreeMap based on a Enum

First, you need a way to lookup the enum value from the key. Best way to do that is a method on the enum itself, so it's reusable:

public static MyActionEnum fromAction(String action) {
for (MyActionEnum e : values())
if (e.action.equals(action))
return e;
if (action == null)
throw new NullPointerException("Action is null");
throw new IllegalArgumentException("Unknown action: " + action);
}

If there were a lot of enum values, you'd want to replace the sequential search done here with a Map lookup.

You can then use that in the compare() method:

@Override
public int compare(String o1, String o2) {
return Integer.compare(MyActionEnum.fromAction(o1).ordinal(),
MyActionEnum.fromAction(o2).ordinal());
}

How to sort a ListEnum, Collection by order of an enum?

You should simply delegate to the enum compareTo method which is already provided and reflects the declaration order (based on the ordinal value):

Collections.sort(list, (a1, a2) -> a1.getType().compareTo(a2.getType()));        

Or, if you think that the component type provides the "natural order" for your elements, you can make the A class itself implement Comparable and also delegate the compareTo method to the ComponentType one.

Sorting by enum on adding to Set

As indicated in the comments, I don't think there is any collection in the JDK that exactly meets your requirements.

IListenerSet is an implementation of Set that meets your needs. The iterator always returns the elements in order of priority. If two elements have the same priority, they are returned in the order they were put into the set. The set supports addition and removal. The iterator supports the remove() method. The set cannot contain null, and throws a NullPointerException if you try to add null. The set cannot contain an IListener for which getPriority() returns null, and throws an IllegalArgumentException if you try to add such an element.

public final class IListenerSet<T extends IListener> extends AbstractSet<T> {

private final Map<IListener.Priority, Set<T>> map;

public IListenerSet() {
map = new EnumMap<>(IListener.Priority.class);
for (IListener.Priority p : IListener.Priority.values())
map.put(p, new LinkedHashSet<>());
}

public IListenerSet(Collection<? extends T> collection) {
this();
addAll(collection);
}

@Override
public int size() {
int size = 0;
for (Set<T> set : map.values())
size += set.size();
return size;
}

@Override
public boolean contains(Object o) {
if (!(o instanceof IListener))
return false;
IListener listener = (IListener) o;
IListener.Priority p = listener.getPriority();
return p != null && map.get(p).contains(listener);
}

@Override
public boolean add(T listener) {
IListener.Priority p = listener.getPriority();
if (p == null)
throw new IllegalArgumentException();
return map.get(p).add(listener);
}

@Override
public boolean remove(Object o) {
if (!(o instanceof IListener))
return false;
IListener listener = (IListener) o;
IListener.Priority p = listener.getPriority();
return p != null && map.get(p).remove(listener);
}

@Override
public void clear() {
for (Set<T> set : map.values())
set.clear();
}

@Override
public Iterator<T> iterator() {
return new Iterator<T>() {

private Iterator<T> iterator = map.get(IListener.Priority.values()[0]).iterator();
private int nextIndex = 1;
private Iterator<T> nextIterator = null;

@Override
public boolean hasNext() {
if (iterator.hasNext() || nextIterator != null)
return true;
IListener.Priority[] priorities = IListener.Priority.values();
while (nextIndex < priorities.length) {
Set<T> set = map.get(priorities[nextIndex++]);
if (!set.isEmpty()) {
nextIterator = set.iterator();
return true;
}
}
return false;
}

@Override
public T next() {
if (iterator.hasNext())
return iterator.next();
if (!hasNext())
throw new NoSuchElementException();
iterator = nextIterator;
nextIterator = null;
return iterator.next();
}

@Override
public void remove() {
iterator.remove();
}
};
}
}


Related Topics



Leave a reply



Submit