Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sum up the individual fields of the object list and return the results as a single object

I have a method which is calculating nutrients for a list of object which we are receiving from API request call.

The method looks like:

public Nutrients nutrientsCalculator(DailyMeals dailyMeals) {

    String foodNamesForRequest = prepareFoodNamesForRequest(dailyMeals);

    HttpEntity<NutrientsBodyForRequest> requestBody = prepareRequestForAPICall(foodNamesForRequest);

    ResponseEntity<List<FoodNutritional>> response =
        //create request here

    if (nonNull(response.getBody())) {

      double totalFat = response.getBody()
          .stream()
          .map(FoodNutritional::getTotalFat)
          .mapToDouble(Double::doubleValue)
          .sum();

      double totalProtein = response.getBody()
          .stream()
          .map(FoodNutritional::getProtein)
          .mapToDouble(Double::doubleValue)
          .sum();

      double totalCarbohydrates = response.getBody()
          .stream()
          .map(FoodNutritional::getTotalCarbohydrate)
          .mapToDouble(Double::doubleValue)
          .sum();

      double totalDietaryFiber = response.getBody()
          .stream()
          .map(FoodNutritional::getDietaryFiber)
          .mapToDouble(Double::doubleValue)
          .sum();

      return Nutrients.builder()
          .carbohydrates(totalCarbohydrates)
          .protein(totalProtein)
          .fat(totalFat)
          .dietaryFiber(totalDietaryFiber)
          .build();
    }
    return new Nutrients();
  }

My FoodNutritional.class looks like:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
class FoodNutritional {

  @JsonProperty("food_name")
  private String foodName;

  @JsonProperty("brand_name")
  private String brandName;

  @JsonProperty("serving_qty")
  private Integer servingQuantity;

  @JsonProperty("serving_unit")
  private String servingUnit;

  @JsonProperty("serving_weight_grams")
  private String servingWeightGrams;

  @JsonProperty("nf_calories")
  private Double calories;

  @JsonProperty("nf_total_fat")
  private Double totalFat;

  @JsonProperty("nf_saturated_fat")
  private Double saturatedFat;

  @JsonProperty("nf_cholesterol")
  private Double cholesterol;

  @JsonProperty("nf_sodium")
  private Double sodium;

  @JsonProperty("nf_total_carbohydrate")
  private Double totalCarbohydrate;

  @JsonProperty("nf_dietary_fiber")
  private Double dietaryFiber;

  @JsonProperty("nf_sugars")
  private Double sugars;

  @JsonProperty("nf_protein")
  private Double protein;

  @JsonProperty("nf_potassium")
  private Double potassium;
}

My solution in method works but I started thinking If it is possible to rid off this sum stream method boilerplate for this approach.

All I want to achieve is summing single fields: totalFat, protein, dietaryFiber, totalCarbohydrate and return them as a components fields of a new object.

I will be grateful for suggestions on how to improve the quality of the current version of the code.

Edit:

On the weekend I spent a moment to found a different, additional approach that will meet the requirements and is a little bit more functional. Finally, I created two static methods like:


private static Nutrients reduceNutrients(Nutrients n1, Nutrients n2) {
    return Nutrients.builder()
        .protein(n1.getProtein() + n2.getProtein())
        .carbohydrates(n1.getCarbohydrates() + n2.getCarbohydrates())
        .dietaryFiber(n1.getDietaryFiber() + n2.getDietaryFiber())
        .fat(n1.getFat() + n2.getFat())
        .build();
  }

  private static Nutrients fromFoodNutritionalToNutrients(FoodNutritional foodNutritional) {
    return Nutrients.builder()
        .dietaryFiber(foodNutritional.getDietaryFiber())
        .carbohydrates(foodNutritional.getTotalCarbohydrate())
        .fat(foodNutritional.getTotalFat())
        .protein(foodNutritional.getProtein())
        .build();
  }

and after all I used it like:

Stream<FoodNutritional> foodNutritionalStream = Optional.ofNullable(response.getBody()).stream()
        .flatMap(List::stream);

Nutrients nutrients = foodNutritionalStream
        .map(NutrientsCalculatorService::fromFoodNutritionalToNutrients)
        .reduce(NutrientsCalculatorService::reduceNutrients)
        .orElseThrow(() -> new CustomException("custom_exception");

Nonetheless, greetings for @Koziołek, @Nir Levy and @Naman for being my muse. Thanks for every single commitment.

like image 749
Martin Avatar asked Oct 11 '19 08:10

Martin


1 Answers

Let introduce class NutritionAccumulator:

class NutritionAccumulator{
    private double fat = 0.;
    private double carbs = 0.;
    private double fiber = 0.;
    private double protein = 0.;

    public NutritionAccumulator() {
    }

    public NutritionAccumulator(double fat, double carbs, double fiber, double protein) {
        this.fat = fat;
        this.carbs = carbs;
        this.fiber = fiber;
        this.protein = protein;
    }

    public NutritionAccumulator add(NutritionAccumulator that){
        return new NutritionAccumulator(this.fat + that.fat,
        this.carbs + that.carbs,
        this.fiber + that.fiber,
        this.protein + that.protein
        );
    }
}

And now we can write simple stream reduce:

Optional.ofNullable(response.body())
.stream()
.reduce(
                        new NutritionAccumulator(),
                        (acc, fudNut) -> new NutritionAccumulator(
                                fudNut.getTotalFat(),
                                fudNut.getTotalCarbohydrate(),
                                fudNut.getDietaryFiber(),
                                fudNut.getProtein()
                        ).add(acc),
                        NutritionAccumulator::add

                );

And finally you can pass result from above to builder.

like image 157
Koziołek Avatar answered Oct 26 '22 21:10

Koziołek