具有限制和自定义比较器的部分排序集合

时间:2018-08-09 10:22:49

标签: java collections comparator

我想像这样对名为imageList的ArrayList进行排序:

Collections.sort(imageList, new MapComparator(Function.KEY_TIMESTAMP, "dsc"));

这很好用,但是现在出于性能原因,我希望能够设置一个限制(仅显示最新的100张图像,其中ArrayList未排序,因此仅创建子列表将不起作用)。

我的MapComparator类如下:

class MapComparator implements Comparator<HashMap<String, String>>
{
    private final String key;
    private final String order;

    public MapComparator(String key, String order)
    {
        this.key = key;
        this.order = order;
    }

    public int compare(HashMap<String, String> first,
                       HashMap<String, String> second)
    {
        String firstValue = first.get(key);
        String secondValue = second.get(key);
        if(this.order.toLowerCase().contentEquals("asc"))
        {
            return firstValue.compareTo(secondValue);
        }else{
            return secondValue.compareTo(firstValue);
        }

    }
}

有人知道如何实现吗? 预先感谢!

2 个答案:

答案 0 :(得分:2)

我不知道此类问题的正式名称,但确实经常出现,并且通常被称为top- k 或great- k < / em>问题。

您当然必须处理输入中的所有元素,因为最后一个元素可能属于“ top k ”集中,并且直到处理完每个最后一个元素后您才知道。但是,您不必对整个输入进行排序。进行排序(例如排序然后获取子列表)或使用流(先调用{{1})后跟sorted()进行流的操作可能非常昂贵,因为使用N个输入元素,排序为O(N log N)。但是,可以通过跟踪列表中一直看到的最大 k 个元素,将时间复杂度降低到O(N)。

番石榴有一个可以做到这一点的收集器:Comparators.greatest(k, comparator)

如果您不想使用番石榴,构建自己的或多或少等效的收集器并不难。 limit()为此非常有用。这是第一个切入点:

PriorityQueue

这使用static <T> Collector<T,PriorityQueue<T>,List<T>> topK(int k, Comparator<? super T> comp) { return Collector.of( () -> new PriorityQueue<>(k+1, comp), (pq, t) -> { pq.add(t); if (pq.size() > k) pq.poll(); }, (pq1, pq2) -> { pq1.addAll(pq2); while (pq1.size() > k) pq1.poll(); return pq1; }, pq -> { int n = pq.size(); @SuppressWarnings("unchecked") T[] a = (T[])new Object[n]; while (--n >= 0) a[n] = pq.poll(); return Arrays.asList(a); }, Collector.Characteristics.UNORDERED); } 作为中间数据结构。随着元素的添加,当队列的大小超过 k 时,最小的元素将被修剪掉。最后,将元素从队列中拉出,并以相反的顺序放入列表中,因此结果列表按从高到低的顺序排序。

例如,给定一个包含

PriorityQueue
List<Integer>

一个人可以做

[920, 203, 880, 321, 181, 623, 496, 576, 854, 323,
 339, 100, 795, 165, 857, 935, 555, 648, 837, 975]

导致

List<Integer> out = input.stream()
                         .collect(topK(5, Comparator.naturalOrder()));

顺便说一句,通过使用[979, 936, 890, 875, 831] 类中的combinator方法,可以更简单地创建映射比较器。例如,假设您的输入如下所示:

Comparator

您可以像这样通过时间戳轻松地对地图进行排序:

    List<Map<String, String>> input =
        List.of(Map.of("name", "map1", "timestamp", "00017"),
                Map.of("name", "map2", "timestamp", "00192"),
                Map.of("name", "map3", "timestamp", "00001"),
                Map.of("name", "map4", "timestamp", "00072"),
                Map.of("name", "map5", "timestamp", "04037"));

或将它们收集到列表中,或使用 input.stream() .sorted(Comparator.comparing(map -> map.get("timestamp"))) .forEach(System.out::println); 进行就地排序,或其他方式。您可以通过执行以下操作来反转排序:

sort(comparator)

后者的输出将是:

    input.stream()
         .sorted(Comparator.comparing(map -> map.get("timestamp"), Comparator.reverseOrder()))
         .forEach(System.out::println);

答案 1 :(得分:0)

使用已排序的Stream

List<HashMap<String, String>> newestImages = 
    imageList.stream()
             .sorted(new MapComparator(Function.KEY_TIMESTAMP, "dsc"))
             .limit(100)
             .collect(Collectors.toList());

但是,这将需要处理List中的所有元素。如果要对输出进行排序,就无法避免。