Don Rhummy
Don Rhummy

Reputation: 25860

How can I combine objects with two fields having the same values in a list into one reduced list?

I have a list of objects of this class:

class Item
{
    private String name;
    private String parentName;
    private Integer someValue;
    private Double anotherValue;

    public Item() {}

    //...elided getters/setters...
}

I have a list of these with the values:

//PSEUDO CODE (Not JavaScript, but using JSON is easier to follow)
List<Item> items = [
    {
        "name": "Joe",
        "parentName": "Frank",
        "someValue": 10,
        "anotherValue": 15.0
    },
    {
        "name": "Joe",
        "parentName": "Frank",
        "someValue": 40,
        "anotherValue": 0.5
    },
    {
        "name": "Joe",
        "parentName": "Jack",
        "someValue": 10,
        "anotherValue": 10.0
    },
    {
        "name": "Jeff",
        "parentName": "Frank",
        "someValue": 10,
        "anotherValue": 10.0
    }
];

I want this to be combined into this list:

List<Item> items = [
    {
        "name": "Joe",
        "parentName": "Frank",
        "someValue": 50,
        "anotherValue": 15.5
    },
    {
        "name": "Joe",
        "parentName": "Jack",
        "someValue": 10,
        "anotherValue": 10.0
    },
    {
        "name": "Jeff",
        "parentName": "Frank",
        "someValue": 10,
        "anotherValue": 10.0
    }
];

Basically the rules are:

How would I do this in Java 8 streams?

I started with putting them into buckets (see below) but am not sure where to go from here:

items
    .stream()
    .collect(
        Collectors.groupingBy(
            item -> new ArrayList<String>( Arrays.asList( item.getName(), item.getParentName() ) ) 
        )
    );

Upvotes: 1

Views: 1426

Answers (2)

Luca Negrini
Luca Negrini

Reputation: 490

The collect(groupingBy()) method returns a map.

If you define the following class:

class Tuple {
    String name;
    String parentName;
}

then you can obtain the following map:

Map<Tuple, List<Item>> groupedItems = items.stream().collect(
            groupingBy(item -> new Tuple(item.getName(), item.getParentName())));

Now you can operate on it like this:

List<Item> finalItems = groupedItems.entrySet().stream().map(entry -> 
               new Item(entry.getKey().name, 
                        entry.getKey().parentName,
                        entry.getValue().stream().mapToInt(
                                  item -> item.someValue).sum(),
                        entry.getValue().stream().mapToDouble(
                                  item -> item.anotherValue).sum()))
               .collect(Collectors.toList());

Upvotes: 5

robjwilkins
robjwilkins

Reputation: 5652

Here is a solution that uses some Java8 constructs (although its essentially a loop through the List, storing Items in a Map temporarily). :

List<Item> items;
Map<String, Item> masterItems;

    items.forEach(item -> {
        if (masterItems.containsKey(item.getName() + item.getParent())) {
            Item matchedItem = masterItems.get(item.getName() + item.getParent());
            matchedItem.setSomeValue(matchedItem.getSomeValue() + item.getSomeValue());
            matchedItem.setOtherValue(matchedItem.getOtherValue() + item.getOtherValue());
        } else {
            masterItems.put(item.getName() + item.getParent(), item);
        }
    });
    items.clear();
    items.addAll(masterItems.values());

It could probably be refined to use more Java8 constructs... I'll have a go and see what I can do

Upvotes: 1

Related Questions