3

I have a simple Customer class like so

public class Customer {
    public int age;
    public int discount;
    public String name;

    public Customer(String name) {
        this.name = name;
    }
    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Customer(String name, int age, int discount) {
        this.name = name;
        this.age = age;
        this.discount = discount;
    }

    @Override
    public String toString() {
        return "Customer [age=" + age + ", discount=" + discount + ", name=" + name + "]";
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Integer getDiscount() {
        return discount;
    }
    public void setDiscount(int discount) {
        this.discount = discount;
    }
}

I populate a list of these objects using this

List<Customer> customerList = new ArrayList<>(Arrays.asList(
        new Customer("John",   2, 15),
        new Customer("John",   4, 15),
        new Customer("John",   6, 25),
        new Customer("Joe",    3, 15),
        new Customer("Joe",    3, 15),
        new Customer("Joe",    3, 15),
        new Customer("Goerge", 6, 25),
        new Customer("Goerge", 6, 25),
        new Customer("Mary",   7, 25),
        new Customer("Jane",   1, 15),
        new Customer("Jane",   2, 15),
        new Customer("Jane",   8, 25),
        new Customer("Jane",   8, 25)
        ));

Now I want to group and count the names and discounts, using a collector like this

Map<Object, Long> collected = customerList
    .stream()
    .collect(Collectors.groupingBy(x -> Arrays.asList(x.name, x.discount), Collectors.counting()));

I can review my output using this

collected.entrySet().forEach(c -> {
    System.out.println(c);
});

Which outputs the following

[Jane, 15]=2
[Joe, 15]=3
[John, 15]=2
[Mary, 25]=1
[John, 25]=1
[Jane, 25]=2
[Goerge, 25]=2

The question is how do I sort the Map by name and discount so it looks like this

[Goerge, 25]=2
[Jane, 15]=2
[Jane, 25]=2
[Joe, 15]=3
[John, 15]=2
[John, 25]=1
[Mary, 25]=1

I keep bumping up against the Object type that is returned by the collector?

Can I cast the collector so that it returns a class, maybe something like

private class DiscountCounts
{
    public String name;
    public Integer discount;
}

Is it possible to convert the Map<**Object**, Long>() to something like Map<DiscountCounts, Long>(), would this allow access to the fields of the Map key using lambda or Comparator constructs?

I tried something like this, iterate over the map and manually convert to the Map I want but I can't get to the original collection's keys?

    Map<DiscountCounts, Long> collected2 = new HashMap<>();
    collected.entrySet().forEach(o -> {
        DiscountCounts key1 = (DiscountCounts)o.getKey();  //--> Fails here
        collected2.put((DiscountCounts)o.getKey(), o.getValue());
    });
2
  • Have you considered using a TreeMap instead of a HashMap? It sorts its keys automatically. Commented Mar 18, 2020 at 4:23
  • Dawood, from the responses below I can see how a TreeMap would've helped here but I would never have understood the Collector object well enough to implement it on my own, thanks for the suggestion. Commented Mar 18, 2020 at 15:52

2 Answers 2

5

One way you can do it without using DiscountCounts class is, first sort the list and then perform the groping by operation, and use LinkedHashMap to save the sorted order

Map<List<Object>, Long> map = customerList.stream()
                .sorted(Comparator.comparing(Customer::getName).thenComparing(Customer::getDiscount))
                .collect(Collectors.groupingBy(x -> Arrays.asList(x.name, x.discount),LinkedHashMap::new, Collectors.counting()));

The another way using DiscountCounts class is, by override the equals and hashcode of DiscountCounts class and do a groupingBy creating DiscountCounts object for every Customer object as key in Map and use TreeMap with Comparator to sort the result

Map<DiscountCounts, Long> result = customerList.stream().collect(Collectors.groupingBy(
            c -> new DiscountCounts(c.getName(), c.getDiscount()),
            () -> new TreeMap<DiscountCounts, Long>(
                    Comparator.comparing(DiscountCounts::getName).thenComparing(DiscountCounts::getDiscount)),
            Collectors.counting()));

@Andreas suggest in the comment enlighten me another way of doing it, and i feel this is one of the best approach you can implement Comparable on DiscountCounts and provide the sorting logic so that you don't need to provide Comparator to TreeMap

@Override
public int compareTo(DiscountCounts cust) {

      int last = this.getName().compareTo(cust.getName());

     return last == 0 ? this.getDiscount().compareTo(cust.getDiscount()) : last;
}

Map<DiscountCounts, Long> result1 = customerList.stream().collect(Collectors.groupingBy(
            c -> new DiscountCounts(c.getName(), c.getDiscount()), TreeMap::new, Collectors.counting()));
Sign up to request clarification or add additional context in comments.

2 Comments

If using DiscountCounts, it would probably be better to make DiscountCounts implement Comparable, so you don't have to build a Comparator for the TreeMap.
Thanks, the second solution worked and gives me the greatest functionality for what I need to do. Full credit for a complete and coherent solution.
0

With proper equals and hashcode implementation for DiscountCounts, you might be looking for something over the lines of :

Map<DiscountCounts, Long> collectSortedEntries = customerList
        .stream()
        .collect(Collectors.groupingBy(x -> new DiscountCounts(x.name, x.discount),
                Collectors.counting()))
        .entrySet()
        .stream()
        .sorted(Comparator.comparing((Map.Entry<DiscountCounts, Long> e) -> e.getKey().getName())
                .thenComparing(e -> e.getKey().getDiscount()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                (a, b) -> a, LinkedHashMap::new));

3 Comments

Naman, I like where you;re going with this, your code throws a compiler error on the e -> e.getKey().getName(), but that will be an opportunity for me to learn more about the Comparator.
@vscoder have updated the answer to fix the compilation, but indeed Andreas comment on the other answer makes sense to update DiscountCounts to implement Comparable<DiscountCounts> and then further simplify collecting to a TreeMap.
Man, just wanted to tell You that You saved my life with that answer. Have a good life!

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.