Guava: Как создать явный порядок из списка и один элемент?
В Guava, учитывая Collection<E>
и элемент e
типа e
, который я знаю, находится в коллекции, я хотел бы создать пользовательский Ordering<E>
, который сначала сортирует e
, а затем остальную часть коллекции. Однако способ попасть туда кажется ужасно сложным:
Collection<String> values = ImmutableList.of("apples", "oranges", "pears");
String first = "oranges";
List<String> remainingValues = newArrayList(values); // this
remainingValues.remove(first); // seems
Ordering<String> myOrdering = // very
Ordering.explicit(first, remainingValues.toArray( // complicated!
new String[remainingValues.size()])); // is there an easier way?
То, что я желаю, это что-то вроде этого:
Ordering.explicit(first);
(Я бы хотел, чтобы это сортировало first
в начале и сохраняло порядок всех других элементов, но документы говорят, что полученный Заказ будет вызывать ClassCastException
для элементов, явно не указанных.)
Или вот так:
Ordering.explicit(first, values.toArray(/* etc */));
(Но это не сработает, потому что first
будет повторяющимся значением)
Может ли кто-нибудь придумать краткий способ делать то, что я хочу?
BTW, он не должен быть Ordering
, это также может быть обходным путем для создания Iterable
в указанном порядке, но опять же, это очень сложно:
Iterable<String> sorted = Iterables.concat(
ImmutableList.of(first),
Iterables.filter(values, not(equalTo(first))));
Ответы
Ответ 1
Ну, вот один из способов сделать это, но вы можете не найти его намного лучше.
final String special = "oranges";
Collections.sort(
list,
new Comparator<String>() {
public int compare(String left, String right) {
return ComparisonChain.start()
.compareTrueFirst(left.equals(special), right.equals(special))
.compare(left, right)
.result();
}
});
Документы ComparisonChain
Соответствующий запрос функции Guava - добавьте любые детали.
Ответ 2
Возможно, этот ответ не проще/менее сложным, чем то, что у вас уже есть, но по крайней мере его можно использовать повторно:)
class FirstOrdering<T extends Comparable> extends Ordering<T> {
private T first;
public FirstOrdering(T first) {
this.first = first;
}
@Override
public int compare(@Nullable T left, @Nullable T right) {
// TODO Nullchecks...
if (first.equals(left)) return -1;
if (first.equals(right)) return 1;
return left.compareTo(right);
}
}
final String first = "B";
new FirstOrdering(first).
sortedCopy(Arrays.asList("A", "D", "E", first));
Ответ 3
Просто используйте NullsFirstOrdering в качестве шаблона и создайте упорядочение, которое сортирует первый элемент, делегируя другому заказу для всего остального:
public class ItemFirstComparator<T> implements Comparator<T> implements Serializable {
private final Comparator<? super T> comparator;
private final Object item;
ItemFirstComparator(Object item, Comparator<? super T> comparator) {
this.item = item;
comparator = checkNotNull(comparator);
}
@Override public int compare(@Nullable T left, @Nullable T right) {
if (left == right) {
return 0;
}
if (Objects.equals(left, item)) {
return -1;
}
if (Objects.equals(right, item)) {
return 1;
}
return comparator.compare(left, right);
}
}
Вы можете легко упорядочить порядок: Ordering.from(new ItemFirstComparator("oranges", Ordering.allEqual()))
.
Изменить
Изменен код для использования Компаратора вместо Ordering, остальное остается тем же.
Ответ 4
Если вы посмотрите на источник com.google.common.collect.ExplicitOrdering, он поддерживает карту, содержащую ранг каждого элемента, а compare
просто сравнивает ряды. Вы можете сделать то же самое самостоятельно, но заставляя назначенный первый уровень позиции равным -1, который находится перед всеми другими элементами.
Если у вас есть список (как указано в заголовке вопроса), потоки Java 8 делают создание карты умеренно удобной:
Map<T, Integer> rankMap = IntStream.range(0, list.size()).boxed().collect(
Collectors.toMap(list::get, i -> list.get(i).equals(first) ? -1 : i));
Comparator<T> cmp = Comparator.comparing(rankMap::get);
Если у вас есть только коллекция (поскольку состояния тела вопроса), вам нужно использовать цикл for для построения карты:
Map<T, Integer> rankMap = new HashMap<>(coll.size());
int rank = 0;
for (T t : coll)
rankMap.put(t, t.equals(first) ? -1 : rank++);
Comparator<T> cmp = Comparator.comparing(rankMap::get);
Вы можете превратить Comparator в Ordering с Ordering.from, как обычно.
Ответ 5
Это более удобно и менее повторяемо, если у вас есть специальные значения:
class PriorityComparator<T> implements Comparator<T> {
private final List<T> values;
public PriorityComparator(T... values) {
this.values = Arrays.asList(values);
}
@Override public int compare(T o1, T o2) {
int idx1 = values.indexOf(o1);
int idx2 = values.indexOf(o2);
if (idx1 > -1) {
return idx2 > -1 ? idx1 - idx2 : -1;
}
return idx2 > -1 ? 1 : 0;
}
}
Вы можете использовать его в цепочке сравнения, например
return ComparisonChain.start()
.compare(left, right, new PriorityComparator<>("oranges", "apples"))
.compare(left, right)
.result();
Он сортирует элементы, как указано в PriorityComparator
, другие элементы сообщаются как равные.
Также легко потребовать, чтобы T
был сопоставим и использовал это вместо значения по умолчанию:
class PriorityComparator2<T extends Comparable<T>> implements Comparator<T> {
private final List<T> values;
public PriorityComparator2(T... values) {
this.values = Arrays.asList(values);
}
@Override public int compare(T o1, T o2) {
int idx1 = values.indexOf(o1);
int idx2 = values.indexOf(o2);
if (idx1 > -1) {
return idx2 > -1 ? idx1 - idx2 : -1;
}
return idx2 > -1 ? 1 : o1.compareTo(o2);
}
}
Ответ 6
Если вы планируете использовать явное упорядочение для начала, это предполагает, что ваш список не имеет дубликатов. В этот момент FluentIterable
и .toSet()
могут сделать это тривиальным. Дубликаты будут просто проигнорированы (в отличие от ошибок).
Iterable<String> sorted = FluentIterable.of(first).append(values).toSet();
or
ImmutableList<String> sorted =
FluentIterable.of(first).append(values).toSet().toList();
IMO, ваше первое предложение на самом деле не так уж плохо, а также работает, если ваш список имеет повторяющиеся значения, не являющиеся первыми. Однако, если вы используете FluentIterable, он выглядит лучше, поскольку вы можете использовать смешанные типы Iterable и типов:
Iterable<String> others = Iterables.filter(values, not(equalTo(first)));
Iterable<String> sorted = FluentIterable.of(first).append(others);
Ловушка здесь заключается в том, что если у вас есть более 1 "первый" элемент, вы потеряете копии.
Исправление тривиально:
Iterable<String> firsts = Iterables.filter(values, equalTo(first)));
Iterable<String> others = Iterables.filter(values, not(equalTo(first));
Iterable<String> sorted = FluentIterable.from(firsts).append(others);
Это требует повторного использования вашей коллекции дважды, но алгоритм тривиален и, вероятно, быстрее, чем что-либо на основе компаратора. Если бы мне пришлось пересмотреть такую реализацию, я бы согласился на это, не моргнув глазом, потому что он супер читабельный/поддерживаемый, и я бы на 100% верю, что он работает по назначению.
Если все остальное не удается, ручная итерация никому не повредит:
List<String> firsts = new ArrayList<>();
List<String> others = new ArrayList<>();
values.forEach(element -> (first.equal(element) ? firsts : others).add(element));
Iterable<String> sorted = FluentIterable.from(firsts).append(others);
Наконец, обратите внимание, что поскольку они используют FluentIterable
, получение коллекции (ImmutableList
) из них столь же тривиально, как добавление .toList()
к вам FluentIterable
.
Ответ 7
Это также похоже на сортировку "ранжирования", где объекты, которые "являются первыми", имеют более высокий вес: Таким образом, порядок 1-лайнера будет следующим:
Ordering.explicit(true, false).onResultOf(first::equals);
or the more general
Ordering.natural().reverse().onResultOf(rankFunction);