13

在 Guava 中,给定我知道集合中的一个和一个类型Collection<E>的元素,我想创建一个自定义,首先排序,然后是集合的其余部分。然而,到达那里的方式似乎非常复杂:eEOrdering<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在指定的 Order 中创建一个的解决方法,但同样,这非常复杂:

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

7 回答 7

10

好吧,这是一种方法,但您可能不会发现它更好。

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

比较链文档

相关 Guava 功能请求——请添加任何详细信息。

于 2013-01-19T15:44:41.847 回答
2

如果您有更多特殊值,这会更方便且重复性更少:

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);
    }
}
于 2016-01-22T23:04:33.430 回答
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));
于 2013-01-18T17:00:46.773 回答
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,其余部分保持不变。

于 2013-01-18T21:02:51.117 回答
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);

如果您只有一个 Collection (如问题的正文所述),则需要使用 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 将 Comparator 转换为 Ordering。

于 2014-09-23T19:17:43.307 回答
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

于 2016-09-26T08:32:27.507 回答
0

这听起来也像“排名”排序,其中“第一”的对象具有更高的权重:所以 1-liner 排序将是:

Ordering.explicit(true, false).onResultOf(first::equals);
   or the more general
Ordering.natural().reverse().onResultOf(rankFunction);
于 2016-09-28T13:19:23.920 回答