Guava:如何从List和单个元素创建显式排序?

时间:2013-01-18 16:21:36

标签: java guava comparator

在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排序到开头并保留所有其他元素的顺序,但是文档说生成的Ordering会为未明确列出的元素抛出ClassCastException。)

或者像这样:

Ordering.explicit(first, values.toArray(/* etc */));

(但这会失败,因为first将是一个重复的值)

任何人都能想出一个简洁的方法来做我想做的事吗?

顺便说一句,它不一定是Ordering,它也可以是在指定订单中创建Iterable的解决方法,但同样,这非常复杂:

Iterable<String> sorted = Iterables.concat(
                             ImmutableList.of(first),
                             Iterables.filter(values, not(equalTo(first))));

7 个答案:

答案 0 :(得分:9)

嗯,这是一种方法,但你可能找不到更好。

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 docs

Relevant Guava feature request - 请添加任何详细信息。

答案 1 :(得分:1)

也许这个答案并不比你现有的答案容易/简单,但至少它可以重复使用:)

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

答案 2 :(得分:1)

只需使用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()))

修改

将代码更改为使用Comparator而不是Ordering,其余部分保持不变。

答案 3 :(得分:1)

如果您查看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);

您可以像往常一样将比较器变为Ordering.from的订购。

答案 4 :(得分:1)

如果您有更多特殊值,这样更方便,重复性更低:

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

答案 5 :(得分:0)

如果您正在考虑使用显式排序,那么可以假设您的列表没有重复。此时,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);

这需要迭代你的集合两次,但算法很简单,并且可能比基于Comparator的任何东西都要快。如果我不得不对这样的实现进行代码审查,我会毫不犹豫地接受这一点,因为它具有超级可读性/可维护性,并且我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一样微不足道。

答案 6 :(得分:0)

这听起来像是排名&#34;排序,其中&#34;是第一个&#34;具有更高的重量:所以1线程订购将是:

Ordering.explicit(true, false).onResultOf(first::equals);
   or the more general
Ordering.natural().reverse().onResultOf(rankFunction);