Подтвердить что ты не робот

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

Ответ 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);