4

I think it's a pretty basic question and there should be solutions out there, but I didn't manage to find any. I think I need more help in terminology, on what term I should look up to learn about my problem, but I really appreciate any help.

Anyway, I would like to implement the following:

I have a list of Objects. Each object is in the form of ExampleClass below:

public class ExampleClass {
    private String name;
    private Double firstDouble;
    private Double secondDouble;

    + constructor and a bunch of methods

}

Basically I have a name variable and a bunch of numbers associated with each instance of ExampleClass. The name variable is not an id, so there may be several ExampleClasses in the list with the same name, all with different numbers associated with them. What I would like to do is to create a "summary" from this list:

  • Filtering out each instance of ExampleClass with the same name, so in my final list of objects, I do not have two objects with the same name variable.
  • I want to make operations with the Double variables of the objects with the same name.

So lets imagine I have the following ExampleClasses in my list:

ExampleClass first = new ExampleClass("apple",1,4);
ExampleClass second = new ExampleClass("pear",6,12);
ExampleClass third = new ExampleClass("apple",5,2);
ExampleClass fourth = new ExampleClass("peach",1,2);
ExampleClass fifth = new ExampleClass("plum",10,25);

In this case I want to remove from the list the first or third element, since they have the same name and I want to make an operation with the numbers, like adding up 1 and 5 and multiplying 4 and 2.

Thanks for any help in advance!

EDIT: I can solve it with a bunch of loops. However, I need my code to be readable and as efficient as it can get. I'm not looking for a brute force solution with nested loops, I'm interested if there is a nice, or nicer solution.

handris
  • 1,999
  • 8
  • 27
  • 41
  • So... what have you tried *yourself* trying to solve it?. Instead of creating N objects from `ExampleClass` better create an array, loop through it and compare each element's name with the rest of the array, after that, do the operations you need. – Frakcool Dec 03 '15 at 00:01
  • @Frakcool I can solve it with a head first method, creating a bunch of loops and interating through the whole thing. Isn't there a better option? – handris Dec 03 '15 at 00:04
  • Google: "java group elements by name" -> http://stackoverflow.com/questions/21678430/group-a-list-of-objects-by-an-attribute-java – Aracurunir Dec 03 '15 at 00:06
  • Use some sorting algorithm (Quicksort, ...) and sort by "fruit", then in one loop compare neighbor element and do your operations. – onetwo12 Dec 03 '15 at 00:10

3 Answers3

3

Finding the sum of all X values grouped by name is pretty easy in Java 8:

// Find the sum of X and group by name
Map<String, Integer> sumXByName = Stream.of(first, second, third, fourth, fifth)
        .collect(groupingBy(ExampleClass::getName, 
                Collectors.<ExampleClass>summingInt(e -> e.getX())));

// Print the results
sumXByName.entrySet().stream()
    .map(e -> e.getKey() + " -> " + e.getValue())
    .forEach(System.out::println);

Prints:

plum -> 10
apple -> 6
pear -> 6
peach -> 1

However, finding the sum of X and product of Y requires a custom collector.

static class ExampleStatistics implements Consumer<ExampleClass> {

    private Integer sum = 0;
    private Integer product = null;

    @Override
    public void accept(ExampleClass value) {
        sum += value.getX();

        if (product == null) {
            product = value.getY();
        } else {
            product *= value.getY();
        }
    }

    public ExampleStatistics combine(ExampleStatistics other) {
        sum += other.sum;
        product *= other.product;
        return this;
    }

    public Integer getSum() {
        return sum;
    }

    public Integer getProduct() {
        return product;
    }

    @Override
    public String toString() {
        return String.format("Sum X = %d, Product Y = %d", sum, product);
    }

}

static class ExampleSummarizer 
    implements Collector<ExampleClass, ExampleStatistics, ExampleStatistics> {

    @Override
    public Supplier<ExampleStatistics> supplier() {
        return ExampleStatistics::new;
    }

    @Override
    public BiConsumer<ExampleStatistics, ExampleClass> accumulator() {
        return (r, t) -> r.accept(t);
    }

    @Override
    public BinaryOperator<ExampleStatistics> combiner() {
        return (r, t) -> r.combine(t);
    }

    @Override
    public Function<ExampleStatistics, ExampleStatistics> finisher() {
        return i -> i; // identity finish
    }

    @Override
    public Set<Collector.Characteristics> characteristics() {
        return Stream.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED)
                .collect(toSet());
    }

};

Now you can easily summarize the objects:

// Summarize all examples and group by name
Map<String, ExampleStatistics> statsByName = Stream.of(first, second, third, fourth, fifth)
        .collect(groupingBy(ExampleClass::getName, new ExampleSummarizer()));

Printing this map will yield the following:

plum -> Sum X = 10, Product Y = 25
apple -> Sum X = 6, Product Y = 8
pear -> Sum X = 6, Product Y = 12
peach -> Sum X = 1, Product Y = 2

EDIT: I used integers for convenience. However, there are summarizing equivalents available for doubles e.g. summingDouble.

class ExampleClass {
    private final String name;
    private final Integer x;
    private final Integer y;

    public ExampleClass(String name, Integer x, Integer y) {
        super();
        this.name = name;
        this.x = x;
        this.y = y;
    }

    public String getName() {
        return name;
    }

    public Integer getX() {
        return x;
    }

    public Integer getY() {
        return y;
    }
}
hoipolloi
  • 7,984
  • 2
  • 27
  • 28
1

Instead of

ExampleClass first = new ExampleClass("apple",1,4);
ExampleClass second = new ExampleClass("pear",6,12);
ExampleClass third = new ExampleClass("apple",5,2);
ExampleClass fourth = new ExampleClass("peach",1,2);
ExampleClass fifth = new ExampleClass("plum",10,25);

use List

List<ExampleClass> list = new ArrayList<>(5);
list.add(new ExampleClass("apple",1,4));
...

delete the first or third element

list.remove(1); 
list.remove(3);

Other you can do by yourself, I think. You should just read about List, Map, Set and so on.

Slava Vedenin
  • 58,326
  • 13
  • 40
  • 59
  • Deleting the first and third element would be done with `list.remove(0); list.remove(1);` (or `list.remove(2); list.remove(0);` if you were so minded). But I don't think that was the point of the question. – Tom Hawtin - tackline Dec 03 '15 at 04:38
1

Filtering out each instance of ExampleClass with the same name, so in my final list of objects, I do not have two objects with the same name variable.

  1. Consider making your class implement equals(Object obj) and Comparable interface or provides some means of creating a Comparator object.

  2. Implement a SortedSet collection for your list such as TreeSet

I want to make operations with the Double variables of the objects with the same name.

You could use the contains(Object obj) method provided by any Set inherited collection before adding an item to your list so that if the method returns true, you could do something with the duplicate. It is recommended that you keep your equals implementation consistent with your Comparable implementation.