How to get sorted paged map by couple conditions using java 8 streams

Viewed 1428

I have some model object Account with list of fields, in simple case with two fields String name and double margin

And I need to get exact page of this accounts which is over some margin limit, sorted by margin & name. Each record should have margin as key, and Account as value.

I made this minimal example of code, but seems like sorting doesn't works well for me. So I wrap it in TreeMap, but it will cost extra memory and I hate it. Probably should be wait to fix this inside of streams and add losted sort by name in case of equals margin

@Data
class Account {
    private final String name;
    private final double margin;

    private final static double MARGIN_LIMIT = 100;
    private final static int PAGE_SIZE = 3;

    private static Set<Account> accounts = new HashSet<>();

    public static void main(String[] args) {
        accounts.add(new Account("user1", 200));
        accounts.add(new Account("user2", 100));
        accounts.add(new Account("user3", 150));
        accounts.add(new Account("user4", 175));
        accounts.add(new Account("user5", 75));
        accounts.add(new Account("user6", 110));

        Map<Double,Account> val = new TreeMap<Double,Account>(Comparator.reverseOrder());
        val.putAll(getClientsForClosing(2));
        System.out.println(val);
    }

    private static Map<Double, Account> getClientsForClosing(int page) {
        return accounts.stream()
                .filter(account -> account.getMargin() >= MARGIN_LIMIT)
                .sorted(Comparator.comparingDouble(Account::getMargin).reversed())
                .skip(PAGE_SIZE * (page - 1))
                .limit(PAGE_SIZE)
                .collect(Collectors.toMap(Account::getMargin, o -> o));
    }
}
3 Answers

Your question and solution somehow are contradictory, on one hand you show code that sorts your entries by margin - which is a double; in this case there is a simpler way:

accounts.stream()
        .filter(account -> account.getMargin() >= MARGIN_LIMIT)
        .skip(PAGE_SIZE * (page - 1))
        .limit(PAGE_SIZE)
        .collect(Collectors.groupingBy(
               Account::getMargin,
               () -> new TreeMap<Double, List<Account>>(Comparator.reverseOrder()),
               Collectors.toList()));

If you want to sort by both margin and name and still keep the definition as Map<Double, List<Account>>, you will have to stick with a LinkedHashMap:

accounts.stream()
        .filter(account -> account.getMargin() >= MARGIN_LIMIT)
        .sorted(Comparator.comparingDouble(Account::getMargin)
                          .reversed()
                          .thenComparing(Comparator.comparing(Account::getName)))
        .skip(PAGE_SIZE * (page - 1))
        .limit(PAGE_SIZE)
        .collect(Collectors.toMap(
              Account::getMargin,
              x -> {
                 List<Account> list = new ArrayList<>();
                 list.add(x);
                 return list;
              },
              (left, right) -> {
                  left.addAll(right);
                  return left;
              },
              LinkedHashMap::new));

Seems like that fix exactly what you are looking for

private static Map<Double, Account> getClientsForClosing(int page) {
    return accounts.stream()
            .filter(account -> account.getMargin() >= MARGIN_LIMIT)
            .sorted(Comparator.comparingDouble(Account::getMargin)
                    .reversed().thenComparing(Comparator.comparing(Account::getName)))
            .skip(PAGE_SIZE * (page - 1))
            .limit(PAGE_SIZE)
            .collect(Collectors.toMap(Account::getMargin, o -> o,
                    (oldVal, newVal) -> oldVal, LinkedHashMap::new));
}

Other answers are correct, however I think TreeSet (instead of TreeMap) could be of great help to you:

TreeSet<Account> accounts = new TreeSet<>(
    Comparator.comparingDouble(Account::getMargin).reversed()
        .thenComparing(Account::getName));

// Then add all the accounts

Map<Double, List<Account>> result = accounts
    .headSet(new Account("zzzz", MARGIN_LIMIT)) // zzzz is the greatest string 
    .stream()                                   // I could think of at this time
    .skip(PAGE_SIZE * (page - 1))               // (it's quite late here)
    .limit(PAGE_SIZE)
    .collect(Collectors.groupingBy(Account::getMargin));

This solution uses the TreeSet.headSet method, which fits like a glove for what you are trying to do. Then, we turn to stream to skip and limit elements and finally collect to the desired data structure.

Related