2016-09-02 8 views
2

Я ищу способ ограничить количество записей, созданных Collectors.toMap(), с функцией слияния. Рассмотрим следующий пример:Могу ли я ограничить записи Collectors.toMap()?

Map<String, Integer> m = Stream.of("a", "a", "b", "c", "d") 
    .limit(3) 
    .collect(toMap(Function.identity(), s -> 1, Integer::sum)); 

Проблема с вышесказанным, что я буду иметь только 2 элемента в результате отображения (a=2, b=1). Есть ли удобный способ короткого замыкания потока после его обработки 3 Отметить ключей?

+0

Не можете ли вы просто фильтровать? – SamTebbs33

+0

Не могли бы вы сделать что-то вроде этого? Не уверен, разрешено ли это. 'Map m = Stream.of (" a "," a "," b "," c "," d ") .limit (m.size() <3? Integer.MAX_VALUE: 3) .collect (toMap (Function.identity(), 1, Integer :: sum)); ' – nbokmans

+3

*« Есть ли способ? »* Да, напишите свой собственный' toMap() 'collector. Это * удобно *? Вопрос перспективы, я думаю. – Andreas

ответ

2

Возможным решением для этого было бы написать свой собственный Spliterator, который бы обернул разделитель данного Stream. Этот Spliterator делегировал бы наступательные вызовы обернутому разделителю и содержал бы логику подсчета многих различных элементов.

Для этого мы можем подкласса AbstractSpliterator и предоставить нашу собственную логику tryAdvance. В дальнейшем все элементы, которые встречаются, добавляются в набор. Когда размер этого набора становится больше нашего максимума или когда у обернутого разделителя нет оставшихся элементов, мы возвращаем false, чтобы указать, что нет оставшихся элементов для рассмотрения. Это остановится, когда будут достигнуты числа отдельных элементов.

private static <T> Stream<T> distinctLimit(Stream<T> stream, int max) { 
    Spliterator<T> spltr = stream.spliterator(); 
    Spliterator<T> res = new AbstractSpliterator<T>(spltr.estimateSize(), spltr.characteristics()) { 

     private Set<T> distincts = new HashSet<>(); 
     private boolean stillGoing = true; 

     @Override 
     public boolean tryAdvance(Consumer<? super T> action) { 
      boolean hasRemaining = spltr.tryAdvance(elem -> { 
       distincts.add(elem); 
       if (distincts.size() > max) { 
        stillGoing = false; 
       } else { 
        action.accept(elem); 
       } 
      }); 
      return hasRemaining && stillGoing; 
     } 
    }; 
    return StreamSupport.stream(res, stream.isParallel()).onClose(stream::close); 
} 

С вашего пример кода, вы бы:

Map<String, Long> m = 
    distinctLimit(Stream.of("a", "a", "b", "c", "d"), 3) 
     .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); 

и выход будет ожидаемый {a=2, b=1, c=1}, то есть карты с 3 различными ключами.

+0

Похоже, что использовать «Итератор» может быть чище. – shmosel