I have an ImmutableSortedMap that holds data in the following datastructure.
<String, Pair<Long, Double>>
My goal is to sort this map in descending order starting with the largest Double
and if two Doubles
are the same, sort it by the Long
. If both the Double
and Long
are the same, sort it by String
My current comparator only deals with the first case, and looks like this.
private ImmutableSortedMap<String, Pair<Long, Double>> sortInDescendingSizeOrder(final Map<String, Pair<Long, Double>> statsByType) {
Ordering<String> ordering = Ordering
.from(new Comparator<Pair<Long, Double>>() {
@Override
public int compare(Pair<Long, Double> o, Pair<Long, Double> o2) {
return o.getRight().equals(o2.getRight()) ? o.getLeft().compareTo(o2.getLeft()) : o.getRight().compareTo(o2.getRight());
}
})
.reverse()
.onResultOf(Functions.forMap(statsByType, null));
return ImmutableSortedMap.copyOf(statsByType, ordering);
}
However, it finds two Doubles
that are the same and throws the following exception: Exception in thread "main" java.lang.IllegalArgumentException: Duplicate keys in mappings
I don't understand what am I doing wrong...
EDIT: I have tried to add the compound
method and order by String (at least).
.compound(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
This makes the code not throw any Exception, however, there is no ordering whatsoever in the resulted map.
EDIT 2: At this point, any ordering like the one described above of a
Map<String, Pair<Long, Double>>
will do. Doesn't necessarily need to be an ImmutableSortedMap
EDIT: See Louis' answer for actual proper solution (ImmutableMap.Builder#orderEntriesByValue(Comparator)
). Continue reading this answer for fix of an incorrect approach ;)
It's a documented behavior of ImmutableSortedMap#copyOf(Map, Comparator)
:
Throws:
NullPointerException
- if any key or value in map is null
IllegalArgumentException
- if any two keys are equal according to the comparator
What you get is a result of values being equal according to comparator, so the problem is there. See this answer for comparing map by value - as Louis mentions (he's from Guava team), using Functions.forMap
is tricky.
In your case adding .nullsLast()
to first ordering and appending .compound(Ordering.natural())
should work. For example:
Map<String, Pair<Long, Double>> map = ImmutableMap.of(
"a", Pair.of(1L, 1.0d),
"b", Pair.of(1L, 1.0d),
"c", Pair.of(1L, 1.0d),
"d", Pair.of(1L, 1.0d)
);
@Test
public void should2()
{
final ImmutableSortedMap<String, Pair<Long, Double>> sortedMap = sortInDescendingSizeOrder(map);
assertThat(sortedMap).hasSize(4);
System.out.println(sortedMap); // {a=(1,1.0), b=(1,1.0), c=(1,1.0), d=(1,1.0)}
}
and for
Map<String, Pair<Long, Double>> map = ImmutableMap.of(
"a", Pair.of(1L, 1.0d),
"b", Pair.of(2L, 1.0d),
"c", Pair.of(1L, 2.0d),
"d", Pair.of(2L, 2.0d)
);
it outputs {d=(2,2.0), b=(2,1.0), c=(1,2.0), a=(1,1.0)}
.
(sortInDescendingSizeOrder
for reference)
private ImmutableSortedMap<String, Pair<Long, Double>> sortInDescendingSizeOrder(
final Map<String, Pair<Long, Double>> statsByType) {
Ordering<String> ordering = Ordering.from(new Comparator<Pair<Long, Double>>() {
@Override
public int compare(Pair<Long, Double> o, Pair<Long, Double> o2) {
return ComparisonChain.start()
.compare(o.getRight(), o2.getRight())
.compare(o.getLeft(), o2.getLeft())
.result();
}
})
.reverse()
.nullsLast()
.onResultOf(Functions.forMap(statsByType, null))
.compound(Ordering.natural());
return ImmutableSortedMap.copyOf(statsByType, ordering);
}
ComparisonChain
is another goodie from Guava you can use here.