2016-06-29 14 views
1

Ich habe ein Objekt Prozess, der ein Datum und einen booleschen Fehler Indikator hat. Ich möchte für jedes Datum eine Zählung der Gesamtprozesse und eine Anzahl der Prozesse mit Fehlern erhalten. So wird zum Beispiel Jun 01 Zählungen 2, 1; Jun 02 wird 1, 0 und Jun 03 1, 1 haben. Der einzige Weg, wie ich das geschafft habe, ist zweimal zu streamen, um die Zählungen zu bekommen. Ich habe versucht, einen benutzerdefinierten Collector zu implementieren, war aber nicht erfolgreich. Gibt es eine elegante Lösung anstelle meiner kludgy-Methode?Java 8 Streams groupby und zählen mehrere Eigenschaften

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; 
    } 
} 

-Code nach @ glee8e-Lösung und @ Holger Tipps

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

haben, können Sie auch die Art von 'list' schwächen zu' Collection' auf Ihrer zweiten Zeile. – bphilipnyc

Antwort

4

Es ist vorzuziehen, dass wir ein POJO zu speichern das Ergebnis oder die Kombinierer Funktion kann sieht ein bisschen dunkel hinzuzufügen. Ich habe das POJO als öffentlich erklärt, aber Sie können es ändern, wenn Sie es für besser halten, es zu verstecken.

public class Result { 
    public int all, error; 
} 

Hauptcode:

// 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: Ich nehme an, Sie import static java.util.stream.Collectors

+0

Danke glee8e. Ihre Lösung gibt die Anzahl von Wahr/Falsch an. Ich habe versucht, eine Gesamtzahl und eine Anzahl von Fehlern zu erhalten. Also für Jun 01 gibt es mir 1, 1 für 1 mit Fehler und 1 ohne. Ich schätze, ich kann es außerhalb des Lambda-Ausdrucks zusammenfassen, um eine Gesamtanzahl zu erhalten. Aber ich wollte es wirklich innerhalb des Ausdrucks machen. – golfradio

+0

@golfradio aktualisiert. jetzt sieht der Code ein bisschen hässlich aus ... – glee8e

+2

Achte auf die Existenz von ['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 ...-), mit dem Sie die drei Funktionen (ohne den Identity Finisher) ohne die Notwendigkeit einer inneren Klasse. Sie müssen nicht einmal das 'IDENTITY_FINISH'-Merkmal spezifizieren, diese Methode leitet dies bereits aus dem Fehlen einer solchen Funktion ab. Aber Sie könnten in Betracht ziehen, das "UNORDERED" Merkmal zu spezifizieren ... – Holger