2016-06-29 3 views
1

У меня есть объект Process, у которого есть дата и индикатор булевой ошибки. Я хочу получить количество итоговых процессов и количество процессов с ошибками для каждой даты. Так, например, 01 июня будет иметь счет 2, 1; Июнь 02 будет иметь 1, 0 и 3 июня 1, 1. Единственный способ, которым я смог это сделать, - это потоковое воспроизведение дважды, чтобы получить подсчеты. Я попытался реализовать пользовательский коллекционер, но не был успешным. Есть ли элегантное решение вместо моего метода kludgy?Java 8 streams groupby и count multiple properties

final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 
    final List<Process> processes = new ArrayList<>(); 
    processes.add(new Process(sdf.parse("2016-06-01"), false)); 
    processes.add(new Process(sdf.parse("2016-06-01"), true)); 
    processes.add(new Process(sdf.parse("2016-06-02"), false)); 
    processes.add(new Process(sdf.parse("2016-06-03"), true)); 

    System.out.println(processes.stream() 
      .collect(
        Collectors.groupingBy(Process::getDate, Collectors.counting()))); 

    System.out.println(processes.stream().filter(order -> order.isHasError()) 
       .collect(
         Collectors.groupingBy(Process::getDate, Collectors.counting()))); 

private class Process { 
    private Date date; 
    private boolean hasError; 

    public Process(Date date, boolean hasError) { 
     this.date = date; 
     this.hasError = hasError; 
    } 

    public Date getDate() { 
     return date; 
    } 

    public boolean isHasError() { 
     return hasError; 
    } 
} 

код после того, как @ glee8e, решение которого и @ советы Holger в

Collector<Process, Result, Result> ProcessCollector = Collector.of(
     () -> Result::new, 
     (r, p) -> { 
      r.increment(0); 
      if (p.isHasError()) { 
       r.increment(1); 
      } 
     }, (r1, r2) -> { 
      r1.add(0, r2.get(0)); 
      r1.add(1, r2.get(1)); 
      return r1; 
}); 

Map<Date, Result> results = Processs.stream().collect(groupingBy(Process::getDate, ProcessCollector)); 
results.entrySet().stream().sorted(Comparator.comparing(Entry::getKey)).forEach(entry -> System.out 
     .println(String.format("date = %s, %s", sdf.format(entry.getKey()), entry.getValue()))); 



private class Result { 

    private AtomicIntegerArray array = new AtomicIntegerArray(2); 

    public int get(int index) { 
     return array.get(index); 
    } 

    public void increment(int index) { 
     array.getAndIncrement(index); 
    } 

    public void add(int index, int delta) { 
     array.addAndGet(index, delta); 
    } 

    @Override 
    public String toString() { 
     return String.format("totalProcesses = %d, totalErrors = %d", array.get(0), array.get(1)); 
    } 
} 
+0

вы можете также ослабит тип из '' list' к Collection' на вашей второй строке. – bphilipnyc

ответ

4

Предпочтительно, чтобы мы добавим POJO для хранения результата, или функция комбайнер может выглядеть немного неясным. Я объявил POJO публичным, но вы можете изменить его, если считаете, что лучше скрыть его.

public class Result { 
    public int all, error; 
} 

Главный код:

// Add it somewhere in this file. 
private static final Set <Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); 

//... 
// This is main processing code 
processes.stream().collect(collectingAndThen(groupingBy(Process::getDate, new Collector<Process, Result, Result> { 
      @Override 
      public Supplier<Result> supplier() { 
       return Result::new; 
      } 

      @Override 
      public BiConsumer<Process, Result> accumlator() { 
       return (p, r) -> { 
        r.total++; 
        if (p.isHasError()) 
         r.error++; 
       }; 
      } 

      @Override 
      public BinaryOperator<Result> combiner() { 
       return (r1, r2) -> { 
        r1.total += r2.total; 
        r1.error += r2.error; 
        return r1; 
       }; 
      } 

      @Override 
      public Function<Result, Result> finisher() { 
       return Function.identity(); 
      } 

      @Override 
      public Set<Characteristics> characteristics() { 
       return CH_ID; 
      } 
}))); 

PS: Я предполагаю, что у вас есть import static java.util.stream.Collectors

+0

Спасибо, glee8e. Ваше решение дает значение true/false. Я искал, чтобы получить общее количество ошибок. Так что за июнь 01 это дает мне 1, 1 за 1 с ошибкой и 1 без. Думаю, я могу подвести итог вне выражения лямбды, чтобы получить общее количество. Но я действительно хотел сделать это в выражении. – golfradio

+0

@golfradio обновлен. теперь код выглядит немного уродливым ... – glee8e

+2

Помните о существовании ['Collector.of (...)'] (https://docs.oracle.com/javase/8/docs/api/java/util/stream/ Collector.html # of-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BinaryOperator-java.util.stream.Collector.Characteristics ...-), который позволяет вам указать три функции (исключая финишер идентичности) без необходимости внутреннего класса. Вам даже не нужно беспокоиться о указании атрибута IDENTITY_FINISH, этот метод уже выведет, что из-за отсутствия такой функции. Но вы можете подумать о том, чтобы указать атрибут 'UNORDERED' ... – Holger

Смежные вопросы