Как этот код переложить на стримы

246
05 июня 2018, 01:40
Map<String, List<Employee>> myMap = new HashMap<>();
    for (int j = 0; j < (MAX_SALARY - MIN_SALARY) / value; j++) {
        List<Employee> list = new ArrayList<>();
        for (Employee emp : employees) {
            if (emp.getSalary() >= (j + 1) * value && emp.getSalary() < (j + 2) * value)
                list.add(emp);
        }
        myMap.put(Integer.toString((j + 1) * value) + "-" + Integer.toString((j + 2) * value), list);
    }
    for (String key : myMap.keySet()) {
        System.out.println(key + " -> " + myMap.get(key).size());
    }
Answer 1

Итак, задача состоит в том, чтобы из списка сотрудников составить Map, ключом которого будет интервал зарплат, а значением - список сотрудников, чьи зарплаты попадают в этот интервал.

Попробуем для начала реализовать это "наивно". Цикл

for (int j = 0; j < (MAX_SALARY - MIN_SALARY) / value; j++)

можно заменить на IntStream:

IntStream.range(0, (MAX_SALARY - MIN_SALARY) / value)

Сотрудников, чья зарплата входит в интервал [(j + 1) * value, (j + 2) * value), можно вычленить так:

employees
        .stream()
        .filter(e -> e.getSalary() >= (j + 1) * value && e.getSalary() < (j + 2) * value)
        .collect(Collectors.toList())

Сложить их в Map можно при помощи Collectors.toMap():

Collectors.toMap(
        i -> (j + 1) * value + "-" + (j + 2) * value,
        i -> employees
                .stream()
                .filter(e -> e.getSalary() >= (j + 1) * value && e.getSalary() < (j + 2) * value)
                .collect(Collectors.toList()))

Складывая всё вместе, получаем

Map<String, List<Employee>> map = IntStream.range(0, (MAX_SALARY - MIN_SALARY) / value)
        .boxed()  // Нужно, чтобы перейти от IntStream
                  // к обычному Stream, в котором доступна 
                  // нужная нам перегрузка метода collect()
        .collect(Collectors.toMap(
                i -> (i + 1) * value + "-" + (i + 2) * value,
                i -> employees
                        .stream()
                        .filter(e -> e.getSalary() >= (i + 1) * value && e.getSalary() < (i + 2) * value)
                        .collect(Collectors.toList())));

Немножко подчищаем и оптимизируем код:

Map<String, List<Employee>> map = IntStream.range(0, (MAX_SALARY - MIN_SALARY) / value)
        .map(i -> (i + 1) * value)
        .boxed()
        .collect(Collectors.toMap(
                i -> i + "-" + (i + value),
                i -> employees
                        .stream()
                        .filter(e -> e.getSalary() >= i)
                        .filter(e -> e.getSalary() < i + value)
                        .collect(Collectors.toList())));

Готово! Теперь идём в документацию и читаем о Collectors.groupingBy(). С учётом прочитанного переписываем наш код так:

Map<String, List<Employee>> map = employees.stream()
        .collect(Collectors.groupingBy(
                e -> e.getSalary() / value * value + "-" + (e.getSalary() / value + 1) * value));

e.getSalary() / value * value и (e.getSalary() / value + 1) * value позволяют определить границы интервала, в который попадает зарплата. Благодаря целочисленному делению, e.getSalary() / value * value не обязательно равно e.getSalary().

Получаем почти такой же результат, как в оригинале. Почти - потому что отсутствуют диапазоны, в которые не попадает ни один сотрудник. Исправляем это недоразумение:

Map<String, List<Employee>> map = employees.stream()
        .filter(e -> e.getSalary() >= MIN_SALARY && e.getSalary() <= MAX_SALARY)
        .collect(Collectors.groupingBy(e -> e.getSalary() / value * value + "-" + (e.getSalary() / value + 1) * value));
IntStream.range(0, (MAX_SALARY - MIN_SALARY) / value)
        .map(i -> (i + 1) * value)
        .mapToObj(i -> i + "-" + (i + value))
        .filter(key -> !map.containsKey(key))
        .forEach(key -> map.put(key, Collections.emptyList()));

Всё, теперь результат в точности такой же, как и в оригинале.

Впрочем, советую дважды подумать, прежде чем переписывать код таким образом. Обычно стримы используют для того чтобы повысить читаемость кода, описать что вы хотите сделать, а не как. В данном случае повышения читаемости я не наблюдаю, наоборот, код становится более запутанным. Переписывание на стримы хорошо работает при обработке коллекции одним циклом, но вложенные циклы на стримах читаются тяжелее.

Answer 2

На сколько я понял ваш код, вы пытаетесь получить количество сотрудников по диапазонам зарплат.

Map<String, Long> map =
    employees.stream()
             .map(Employee::getSalary)
             .collect(Collectors.groupingBy(
                 salary -> ((salary + 1) * value) + "-" + ((salary + 2) * value),
                 Collectors.counting()))
READ ALSO
Вырезка строк Java [закрыт]

Вырезка строк Java [закрыт]

Имеется переменная типа String "daily - ежедневный"Нужно вывести сначала "daily", а потом "ежедневный"

190
Замена слов в строке Java

Замена слов в строке Java

Есть строка, допустим

210
Чистая архитектура Repository это и есть Gatewqay?

Чистая архитектура Repository это и есть Gatewqay?

Добрый день смотрю разные схемы mvp + clean ,так вот в одной из этих реализаций видел некую особеннось где была схема Repository/Gateway -> converter->api

138
Java + JSP + JavaScript

Java + JSP + JavaScript

В первый раз столкнулся с JavaScript и не могу понять, как мне передать Map в JavaScript

156