0

I have a case where I need to aggregate a list of Beans/Objects to display their total characteristics. Let me explain my requirement first. I have a List of person details as shown :

    Person  Bank  Balance
    ---------------------
    Sam      GS   200
    Sam      JP   200
    Sam      WF   200
    John     GS   200
    John     JP   200
    Robin    JP   200
    Robin    JP   200

I want to aggregate the balances per person.

    Sam           700   <---- Key
    ------------------
        Sam      GS   200
        Sam      JP   300
        Sam      WF   200

    John          500   <---- Key
    ------------------
        John     GS   300
        John     JP   200

    Robin         200   <---- Key
    ------------------
        Robin    JP   100
        Robin    JP   100

Now, let's switch to what I have in my code - a List of such objects and I need to put them in a Map. The key would be the aggregated details, and its value will be the list of details. Here's my attempt, although its not that great:

public Map<Bean, List<Bean>> getAggregation(List<Bean> beans)
{
    Map<Bean, ArrayList<Bean>> aggreagtedBeans = new HashMap<Bean, new ArrayList<Bean>>();

    for(Bean bean : beans)
    {
        String name = bean.getName();
        boolean presentALready = false;
        Bean correspondingKey = null;
        for(Bean key :aggreagtedBeans.keySet())
        {
            if(key.getName().equals(name))
            {
                presentALready = true;
                correspondingKey = key;
                }
            }

            if(presentALready)
            {
                aggreagtedBeans.put(correspondingKey, aggreagtedBeans.get(correspondingKey).add(bean));
            }

            else
            {
                aggreagtedBeans.put(bean, aggreagtedBeans.get(correspondingKey).add(bean));
            }

        }
    return aggreagtedBeans;     
}

Problems :

Even with this Map approach, the key gets fixed and so am not able to update the balances as each row is added for a specific person.

Limitations :

I know this type of use case is ideal for database order by clauses, but I cannot use them. These are Java Objects.

Also, if you think that I should use a different data structure as per my use case, please suggest so, and if possible please provide a code snippet.

EDIT :

Attaching the Bean class code for reference :

public class Bean 
{
    String name;
    String bank;
    int balance;

    // constructors and getters
} 

5 Answers 5

2

Couldn't help myself since Java 8 is awesome:

public static void main(String [] args) {
    List<Bean> list = new ArrayList<>();
    list.add(new Bean("John", 10));
    list.add(new Bean("Sam", 666));
    list.add(new Bean("Sam", 9));
    list.add(new Bean("John", 1));
    list.add(new Bean("John", 7));

    Map<String, Integer> sum = list.stream().collect(Collectors.groupingBy(Bean::getName, Collectors.summingInt(Bean::getBalance)));
    sum.entrySet().forEach(x -> list.add(new Bean(x.getKey(),x.getValue(), true)));
    list.sort((x,y) -> {
        int nameComp = x.getName().compareTo(y.getName());
        if (nameComp == 0)
            return x.isSum() ? -1 : 1;
        return nameComp;
    });
    list.forEach(System.out::println);
}

// Bean class with no "bank" variable, but with a new constructor and overloaded .toString()
static class Bean {
    private String name;
    private int balance;
    private final boolean isSum;

    Bean(String name, int balance, boolean isSum) {
        this.isSum = isSum;
        this.name = name;
        this.balance = balance;
    }

    Bean(String name, int balance) {
        this.isSum = false;
        this.name = name;
        this.balance = balance;
    }

    public String getName() { return name; }
    public int getBalance() { return balance; }
    public boolean isSum() { return isSum; }
    @Override
    public String toString() { return name + " | " + balance + (isSum() ? " *Sum*" : ""); }
}

Output:

John | 18 Sum

John | 10

John | 1

John | 7

Sam | 675 Sum

Sam | 666

Sam | 9

Sign up to request clarification or add additional context in comments.

2 Comments

I haven't used Java 8, neither does my organisation. Looks good though :) :) Will make my life easier. Thanks a ton !!
If you want the sum-bean to be inside the list along with the others it's going to get ugly since they're not really on the same "level". One is an entry, other is a sum of entries. You can do it however (add a flag to the bean), and rather easily sort it too - Java 8 of course. Want the code?
1

What you need to accumulate the balances is a

Map<String, Integer> name2balance = new HashMap<>();

A Bean is not a very good key in a map where you try to accumulate data for a person, as you have a 1:n relationship between name and Bean (name, bank, balance).

Especially with potentially volatile data (balance!) this may lead to trouble.

Comments

1

public class JavTest {

public static void main(String[] args) {

    List<Bean> list = new ArrayList<>();
    list.add(new Bean("Sam", "GS",  200));
    list.add(new Bean("Sam", "JP",  300));
    list.add(new Bean("Sam", "WF",  200));
    list.add(new Bean("John", "GS",  300));
    list.add(new Bean("John", "JP",  200));
    list.add(new Bean("Robin", "JP",  100));
    list.add(new Bean("Robin", "JP",  100));


    HashMap<Bean, ArrayList<Bean> > beans = (HashMap<Bean, ArrayList<Bean>>) getAggregation(list);

    Set<Bean> sb = beans.keySet();

    for(Bean b : sb){
        System.out.println(b.getName() + " " + b.bank + " " + b.balance);
        for(Bean bb : beans.get(b)){
            System.out.println(bb.getName() + " " + bb.bank + " " + bb.balance);
        }
        System.out.println("---------------");
    }
}

public static Map<Bean, ArrayList<Bean>> getAggregation(List<Bean> beans)
{
    //ArrayList<Bean> al = new ArrayList<Bean>();
    HashMap<Bean, ArrayList<Bean> > aggreagtedBeans = new HashMap<Bean, ArrayList<Bean>>();

    for(Bean bean : beans)
    {

        String name = bean.getName();

        Set<Bean> keys = aggreagtedBeans.keySet();
        boolean found = false;
        Bean y = null;

        for(Bean x : keys){
            if(x.name == name){
                y = x;
                found = true;
                break;
            }
        }

        if(found == true){
            ArrayList<Bean> al = aggreagtedBeans.get(y);
            al.add(bean);

            Bean newBean = new Bean(y.name, bean.bank, y.balance + bean.balance);
            aggreagtedBeans.remove(y);

            aggreagtedBeans.put(newBean, al);               

        }else{

            ArrayList<Bean> tmp = new ArrayList<Bean>();
            tmp.add(bean);
            aggreagtedBeans.put(bean, tmp);
        }
    }



    return aggreagtedBeans;     
}

}

output :

Sam WF 700 Sam GS 200 Sam JP 300

Sam WF 200

Robin JP 200 Robin JP 100

Robin JP 100

John JP 500 John GS 300

John JP 200

It should work , but it's not efficient to use Bean as key because every time you want to edit it you have to remove it with its array list then re-add them !

Comments

1

Try something like this Assuming that one person can have only one account in one bank.

        int balOfSam = 0;
        int balOfJohn = 0;
        int balOfRobin = 0;
        for(Bean bean : list){
                map.put(bean.getName()+bean.getBank(),bean.getBalance());
        }
        Set<String> set = map.keySet();
        for(String key :set){
            if(key.startsWith("Sam"))
                balOfSam+=map.get(key);
            else if(key.startsWith("John"))
                balOfJohn+=map.get(key);
            else
                balOfRobin+=map.get(key);
        }
        System.out.println(balOfJohn);
        System.out.println(balOfSam);
        System.out.println(balOfRobin);

2 Comments

Well, Sam, John and Robin are just examples. I might have hundreds of such persons.
Ya it wil work if you have two similar names having same bank you need to associate some unique things to the key may be like accountno
1

Using java 8:

List<Bean> allData = Arrays.asList(
    new Bean("Sam", "GS", 200),
    new Bean("Sam", "JP", 200),
    new Bean("Sam", "WF", 200),
    new Bean("John", "GS", 200),
    new Bean("John", "JP", 200),
    new Bean("Robin", "JP", 200),
    new Bean("Robin", "JP", 200)
);

allData.stream().collect(
        groupingBy(Bean::getName)
).forEach((name, dataForPerson) -> {   // dataForPerson is a List<Bean> for the name

    int totalForName = dataForPerson.stream()
            .mapToInt(Bean::getBalance)
            .sum(); 

    System.out.printf(       /* print header for each person */
            "%n%-14s%d%n-----------------%n", 
            name, 
            totalForName);

    dataForPerson.forEach(    /* print each entry for the person *//
            b->System.out.printf(
                    "%7s%5s%5s%n", 
                    b.getName(), 
                    b.getBank(), 
                    b.getBalance()
            )
    );      
});

Output:

John          400
-----------------
   John   GS  200
   John   JP  200

Robin         400
-----------------
  Robin   JP  200
  Robin   JP  200

Sam           600
-----------------
    Sam   GS  200
    Sam   JP  200
    Sam   WF  200

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.