1

Right now I have the following code, which takes 2 recipes and finds duplicates in the recipes and "merges" them.

public void mergeIngredients(Recipe recipe1, Recipe recipe2) {

    Map<String, Ingredients> recipe1Map = recipe1.getIngredientsMap();
    Map<String, Ingredients> recipe2Map = recipe2.getIngredientsMap();

    for (Map.Entry<String, Ingredients> s : recipe1Map.entrySet()) {

        if (recipe2Map.containsKey(s.getKey())) {
            double newValue = recipe1.getAmount(s.getKey()) + recipe2.getAmount(s.getKey());
            System.out.println(newValue);
        }
    }
}

I want to change this code so instead of only being able to check 2 maps against each other, I need to refactor the code so it can take N number of maps and compare all of them.

Example: The user inputs 8 different recipes, it should loop through all of these and merge ingredients if duplicates are found. What is the best way to achieve this?

  • Possible duplicate of [Merging two Map with Java 8 Stream API](https://stackoverflow.com/questions/23038673/merging-two-mapstring-integer-with-java-8-stream-api) – dbl Dec 14 '18 at 10:22

3 Answers3

2

I would first extract all keys from all Maps into a Set. This gives you all unique ingredients-keys.

Then iterate that Set and get all the values from all the recipes and merge them.

For example:

public void mergeIngredients(Set<Recipe> recipes) {

    Set<String> keys = recipes.stream()         //
            .map(Recipe::getIngredientsMap)     // Get the map
            .flatMap(m -> m.keySet().stream())  // Get all keys and make 1 big stream 
            .collect(Collectors.toSet());       // Collect them to a set

    for (String k : keys)
    {
        double newValue = recipes.stream()       //
                .map(Recipe::getIngredientsMap)  //
                .map(i->i.get(k))                //
                .mapToDouble(i->i.getAmount())   //
                .sum();                          //
        System.out.println(newValue);
    }

}

You problably can do this more efficient; but this is easier to follow I think.

Rob Audenaerde
  • 19,195
  • 10
  • 76
  • 121
  • Thanks! I'm getting a NullPointer on this line though `double newValue = recipes.stream().map(Recipe::getIngredientsMap).map(i->i.get(k)).mapToDouble(Ingredients::getAmount).sum();` Do you know what could be the issue? – Frederik Jorgensen Dec 14 '18 at 10:45
0

You can use Merging Multiple Maps Using Java 8 Streams in the case of duplicate keys:

public void mergerMap() throws Exception {
    Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3);
    Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4);

    Map<String, Integer> mx = Stream.of(m1, m2)
        .map(Map::entrySet)          // converts each map into an entry set
        .flatMap(Collection::stream) // converts each set into an entry stream, then
                                     // "concatenates" it in place of the original set
        .collect(
            Collectors.toMap(        // collects into a map
                Map.Entry::getKey,   // where each entry is based
                Map.Entry::getValue, // on the entries in the stream
                Integer::max         // such that if a value already exist for
                                     // a given key, the max of the old
                                     // and new value is taken
            )
        )
    ;

    Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4);
    assertEquals(expected, mx);
}
Ravindra Kumar
  • 1,842
  • 14
  • 23
0

I don't really see the need of a Map for your ingredients so here is an alternative solution. If you make your Ingredients class implement equals & hashcode you can use it directly in a Set. You will of course also have a method in Recipe that returns all ingredients as a List. Then the following will return all unique ingredients.

Set<Ingredients> merge(List<Recipe> recipies) {
    return recipies.stream().map(s -> s.allIngredients()).collect(Collectors.toSet()); 
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • It's because I need 3 elements for each ingredient: amount + unit + description Example: 1 pound chicken – Frederik Jorgensen Dec 14 '18 at 10:53
  • @FrederikJorgensen, not sure I understand. Wouldn't that mean that your `Ingredients` class has 3 members; `amount`, ´unit` and `description`? Why is a map necessary because of this? – Joakim Danielson Dec 14 '18 at 11:01