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