Guava:如何从List和单个元素创建显式排序?
在Guava中,如果我知道Collection
和Collection
类型的元素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 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,其余代码保持不变。
如果您查看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);
- 在Java中重载方法中使用null
- HttpClient登录,搜索并获取XML内容
- hbase api – 按行ID列表获取数据行信息
- 如果需要处理太多数据,我如何让ThreadPoolExecutor命令等待?
- java.lang.NoClassDefFoundError:无法初始化类javax.media.jai.JAI
- 如何在创建二进制搜索算法时使用递归
- 将OAuth 2.0和Google Spreadsheet API与Java结合使用的示例是什么?
- ClassNotFoundException启动Tomcat时的DispatcherServlet(Maven依赖项未复制到wtpwebapps)
- HashMap中的桶数是什么意思?