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

在Guava中,如果我知道CollectionCollection类型的元素e在集合中,我想创建一个自定义Ordering ,首先对e进行排序,然后对集合的其余部分进行排序。 然而,到达那里的方式似乎非常复杂:

 Collection values = ImmutableList.of("apples", "oranges", "pears"); String first = "oranges"; List remainingValues = newArrayList(values); // this remainingValues.remove(first); // seems Ordering myOrdering = // very Ordering.explicit(first, remainingValues.toArray( // complicated! new String[remainingValues.size()])); // is there an easier way? 

我想要的是这样的事情:

 Ordering.explicit(first); 

(我希望这首先排序到开头并保留所有其他元素的顺序,但是文档说生成的Ordering会为未明确列出的元素抛出ClassCastException 。)

或者像这样:

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

(但这会失败,因为first是重复值)

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

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

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

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

 final String special = "oranges"; Collections.sort( list, new Comparator() { public int compare(String left, String right) { return ComparisonChain.start() .compareTrueFirst(left.equals(special), right.equals(special)) .compare(left, right) .result(); } }); 

ComparisonChain docs

相关的番石榴function请求 – 请添加任何详细信息。

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

 class FirstOrdering extends Ordering { 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)); 

只需使用NullsFirstOrdering作为模板,并创建一个排序第一个元素的排序,委托另一个排序:

 public class ItemFirstComparator implements Comparator implements Serializable { private final Comparator comparator; private final Object item; ItemFirstComparator(Object item, Comparator 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,其余代码保持不变。

如果您查看com.google.common.collect.ExplicitOrdering的来源,它会维护一个包含每个项目排名的地图,并compare只是比较排名。 你可以自己做同样的事情,但强制指定的第一项的等级为-1,这是在所有其他项之前。

如果你有一个List(正如问题的标题所述),Java 8流使得构建地图变得相当方便:

 Map rankMap = IntStream.range(0, list.size()).boxed().collect( Collectors.toMap(list::get, i -> list.get(i).equals(first) ? -1 : i)); Comparator cmp = Comparator.comparing(rankMap::get); 

如果你只有一个Collection(作为问题的正文状态),你需要使用for循环来构建地图:

 Map rankMap = new HashMap<>(coll.size()); int rank = 0; for (T t : coll) rankMap.put(t, t.equals(first) ? -1 : rank++); Comparator cmp = Comparator.comparing(rankMap::get); 

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

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

 class PriorityComparator implements Comparator { private final List 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> implements Comparator { private final List 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); } } 

如果您正在考虑使用显式排序,那么可以假设您的列表没有重复。 此时, FluentIterable.toSet()可以使这个变得微不足道。 将简单地忽略重复项(而不是错误输出)。

 Iterable sorted = FluentIterable.of(first).append(values).toSet(); or ImmutableList sorted = FluentIterable.of(first).append(values).toSet().toList(); 

IMO,你的第一个建议实际上并没有那么糟糕,如果你的列表有非重复的重复值也可以。 如果您使用FluentIterable,它看起来更好,因为您可以连接混合的Iterable和元素类型:

 Iterable others = Iterables.filter(values, not(equalTo(first))); Iterable sorted = FluentIterable.of(first).append(others); 

这里的问题是,如果你有超过1个“第一”元素,你将丢失副本。

虽然修复很简单:

 Iterable firsts = Iterables.filter(values, equalTo(first))); Iterable others = Iterables.filter(values, not(equalTo(first)); Iterable sorted = FluentIterable.from(firsts).append(others); 

这需要对您的集合进行两次迭代,但算法很简单,并且可能比基于Comparator的任何算法都要快。 如果我不得不对这样的实现进行代码审查,我会毫不犹豫地接受这一点,因为它具有超级可读/可维护性,并且我100%相信它可以按预期工作。

如果所有其他方法都失败了,那么手工迭代永远不会伤害任

 List firsts = new ArrayList<>(); List others = new ArrayList<>(); values.forEach(element -> (first.equal(element) ? firsts : others).add(element)); Iterable sorted = FluentIterable.from(firsts).append(others); 

最后,请注意,由于这些使用FluentIterable ,从这些中获取集合( ImmutableList )与将.toList()附加.toList()一样简单。

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

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