14 Nisan 2023 Cuma

Collectors.groupingBy İle Polymorphic Gruplama

Örnek
Elimizde şöyle bir kod olsun
// Bir değer nesnesi
record FieldValue(long value){}

// Bir işlem tipi
record Aggregator(long id, Type type) 
    public enum Type {
        SUM, AVG, MAX, MIN;
    }
}

// Bir işlem ve ona uygulanacak değer
Map<Aggregator, FieldValue> values = Map.of(
    new Aggregator(1L, Type.AVG), new FieldValue(10L),
    new Aggregator(2L, Type.AVG), new FieldValue(20L),
    new Aggregator(3L, Type.SUM), new FieldValue(30L),
    new Aggregator(4L, Type.SUM), new FieldValue(40L),
    new Aggregator(5L, Type.MAX), new FieldValue(50L),
    new Aggregator(6L, Type.MAX), new FieldValue(60L),
    new Aggregator(7L, Type.MIN), new FieldValue(70L),
    new Aggregator(8L, Type.MIN), new FieldValue(80L)
);
Normalde groupBy ile işlem tipine göre gruplama yapmak çok kolay. Ama burada işlem tipine göre değer nesnesini bir metoda geçmek istiyoruz. Şöyle yaparız
Map<Aggregator.Type, Double> result = aggregate(values);

public Map<AggregatorType, Double> aggregate(Map<A, B> fields) {
  // Aynı işlem tipine göre değerleri grupla
  Map<AggregatorType, List<Long>> valuesByType = fields.entrySet()
    .stream()
    .collect(Collectors.groupingBy(
      entry -> entry.getKey().type(),
      Collectors.mapping(
        entry -> entry.getValue().value(),
        Collectors.toList())
    ));

  // Her işlem tipine göre listeyi hesapla
  return valuesByType.entrySet()
    .stream()
    .collect(Collectors.toMap(
      entry -> entry.getKey(),
      entry -> collectValues(entry.getKey(), entry.getValue())
    ));
}

private double collectValues(AggregatorType aggregator, List<Long> values) {
  DoubleStream stream = values.stream().mapToDouble(x -> x + 0.0d);
  return switch (aggregator) {
    case AVG -> stream.average().orElseThrow();
    case SUM -> stream.sum();
    case MAX -> stream.max().orElseThrow();
    case MIN -> stream.min().orElseThrow();
    default -> throw new IllegalArgumentException();
  };
}
Ama burada switch ile işlem tipine bakmak gerekiyor. Biraz daha Polymorphism için bunu görevi Type Enum'una taşıyabiliriz. Şöyle yaparız
@RequiredArgsConstructor
public enum Type {
  SUM(values -> doubleStream(values).sum()),
  AVG(values -> doubleStream(values).average().orElseThrow()),
  MAX(values -> doubleStream(values).max().orElseThrow()),
  MIN(values -> doubleStream(values).min().orElseThrow());

  private final Function<List<Long>, Double> aggregator;

  public Double aggregate(List<Long> values) {
    return aggregator.apply(values);
  }

  private static DoubleStream doubleStream(List<Long> values) {
    return  values.stream().mapToDouble(v -> v + 0.0d);
  }
}
Bu durumda aggregate metodu şöyle olur
public Map<Aggregator.Type, Double> aggregate(Map<Aggregator, FieldValue> fields) {
  // Aynı işlem tipine göre değerleri gruplaa
  Map<Aggregator.Type, List<Long>> valuesByType = ...
    
  return valuesByType.entrySet()
    .stream()
    .collect(Collectors.toMap(
      entry.getKey(),
      entry.getKey().aggregate(entry.getValue()) // Switch yerine Type nesnesini kullan
    ));
}





Hiç yorum yok:

Yorum Gönder