How to print the sum of values from the same objects of 2 parameter in array

Viewed 440

I am learning java and I hit into a snag as I could not figure out my loop or array. I have an array which contains class objects containing a string and integer parameters, in my code, it will be name and dollars.

I am trying to print out the array in which, if there is a same name, it is to print once and with the sum of the dollars (from the same name).

In my Dollars.java

public class Dollars
{
    private String name;
    private int dollars;
    
    public dollars (String name, int dollars)
    {
        this.name = name;
        this.dollars = dollars;
    }

    public String getName()
    {
        return name;
    }
    
    public int getChange()
    {
        return dollars;
    }
}

In my main file/ TestDollars.java

public class TestDollars 
{
    
    public static void displayArray(Dollars[] dol)
    {
        int sum = 0;
        for (int n=0; n<dol.length; n++)
        {
            for (int m=n+1; m<dol.length; m++)
            {
                if (dol[n].getName().equals(dol[m].getName()))
                {
//                    System.out.printf("%s -- %d\n", dol[n].getName(), dol[n].getChange());
                    sum = dol[n].getChange() + dol[m].getChange();
                    System.out.printf("%s -- %d\n", dol[m].getName(), sum);
                    break;
                }
                
            }
            System.out.printf("%s -- %d\n", dol[n].getName(), dol[n].getChange());     
        }
    }
    
    public static void main(String[] args)
    {
        // Test with 5 records
        Dollars[] dollarsArr = new Dollars[5];
        dollarsArr[0] = new Dollars("john", 10);
        dollarsArr[1] = new Dollars("peter", 12);
        dollarsArr[2] = new Dollars("sam", 5);
        dollarsArr[3] = new Dollars("alvin", 16);
        dollarsArr[4] = new Dollars("peter", 30);
        displayArray(dollarsArr);
    }
}

Irregardless where I place my print statement in the displayArray, the record 'peter' will gets printed twice.

Expected output:

john -- 10
peter -- 42
sam -- 5
alvin -- 16

Current output:

john -- 10
peter -- 42
peter -- 12
sam -- 5
alvin -- 16
peter -- 30
3 Answers

You want to group your list by name, please use JAVA 8+ API Stream and the collector group by

public static void displayArray(Dollars[] dol)
    {
       Stream.of(dol)
               // Group by name
               .collect(Collectors.groupingBy(Dollars::getName))
                .entrySet().stream()
               // Collect a map name and calculate the sum
               .collect(
                    Collectors.toMap(x -> {
                    int total= x.getValue().stream().mapToInt(Dollars::getChange).sum();
                    return new Dollars(x.getKey(),total);
                }, Map.Entry::getValue))
               // Print
               .forEach((dollarsTotal, vals) -> {
                    System.out.println(dollarsTotal.getName()+ " -- "+ dollarsTotal.getChange());
                    // Bonus : Display transactions :
                    for(Dollars transaction : vals)
                    {
                        System.out.println(" \t "+transaction.getName() + " add  -- " + transaction.getChange());
                    }
       });
    }

If you want only the values you can collect the keyset

Set<Dollars> groupedByName = Stream.of(dol)
               // Group by name
               .collect(Collectors.groupingBy(Dollars::getName))
                .entrySet().stream()
               // Collect a map name and calculate the sum
               .collect(
                        Collectors.toMap(x -> {

                    int total= x.getValue().stream().mapToInt(Dollars::getChange).sum();
                    return new Dollars(x.getKey(),total);
                }, Map.Entry::getValue)).keySet();

The other answer guides you on fixing your code (Buy they require more work to avoid double counting).

You can reduce the time complexity from O(n2) to O(n) (and make it simpler) by having a data structure (like a map) to aggregate the result.

Let us create a Map<String, Integer> to map a name to the total dollars for that name.

Map<String, Integer> result = new HashMap<>();
for (int i = 0; i < dol.length; i++) {
    if (!result.containsKey(dol[i].getName())) { //first time you encounter a name
        result.put(dol[i].getName(), dol[i].getChange());
    } else {
         //add the current change to the already existing sum
         int sumSoFar= result.get(dol[i].getName());
         result.put(dol[i].getName(), sumSoFar + dol[i].getChange());
    }
}
System.out.println(result);

Result is,

{peter=42, alvin=16, john=10, sam=5}

You can simplify the above code using Map's merge method as:

for (Dollars dollars : dol) {
    result.merge(dollars.getName(), dollars.getChange(), Integer::sum);
}

The third argument is a BiFunction which sums up the old value and new value (the sum accumulated so far and the current change value). When written as a lambda expression, Integer::sum can be written as (sumSoFar, currentChange) -> sumSoFar + currentChange.


A stream evangelist way would be to use Collectors.groupingBy and Collectors.summingInt.

Arrays.stream(dol)
        .collect(Collectors.groupingBy(Dollars::getName, Collectors.summingInt(Dollars::getChange)));

While navigating the array, assign the change of each non-null array-element to a variable e.g. sum and then add the change of the succeeding duplicate elements to it. Make sure to assign null to the indices where duplicate elements are found so that they can not be counted again. It also means that you will have to perform a null check before performing any operation on the array elements. Print the value of sum once you have checked the complete array for duplicate elements.

public static void displayArray(Dollars[] dol) {
    for (int n = 0; n < dol.length; n++) {
        if (dol[n] != null) {
            int sum = dol[n].getChange();
            for (int m = n + 1; m < dol.length; m++) {
                if (dol[m] != null && dol[n].getName().equals(dol[m].getName())) {
                    sum += dol[m].getChange();
                    dol[m] = null;
                }
            }
            System.out.printf("%s -- %d\n", dol[n].getName(), sum);
        }
    }
}

Output:

john -- 10
peter -- 42
sam -- 5
alvin -- 16

Note: If you want to keep the original array intact, pass the clone of the array to the method, displayArray instead of passing the array itself as shown below:

displayArray(dollarsArr.clone());
Related