Розрахунок розподілу з колекції в Java

Перетворення колекції чисел (або об’єктів, поля яких ви хотіли б перевірити) на розподіл цих чисел є загальною статистичною технікою, яка використовується в різних контекстах у звітах і програмах, керованих даними.

Дано колекцію:

1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3

Ви можете перевірити їх розподіл як кількість (частота кожного елемента) і зберегти результати на карті:

{
"1": 5,
"2": 2,
"3": 2,
"4": 1,
"5": 1
}

Або ви можете нормалізувати значення на основі загальної кількості значень, тобто виражаючи їх у відсотках:

{
"1": 0.45,
"2": 0.18,
"3": 0.18,
"4": 0.09,
"5": 0.09
}

Або навіть виразіть ці відсотки в a 0..100 формат замість a 0..1 Формат.

У цьому посібнику ми розглянемо, як можна обчислити розподіл із колекції – як за допомогою примітивних типів, так і об’єктів, поля яких ви можете повідомити у своїй програмі.

Завдяки підтримці функціонального програмування в Java розраховувати розподіли стало легше, ніж будь-коли. Ми будемо працювати з колекцією чисел і колекцією Books:

public class Book {

    private String id;
    private String name;
    private String author;
    private long pageNumber;
    private long publishedYear;

   
}

Розрахунок розподілу колекції в Java

Давайте спочатку розглянемо, як можна обчислити розподіл для примітивних типів. Робота з об’єктами просто дозволяє викликати власні методи з класів домену, щоб забезпечити більшу гнучкість обчислень.

За замовчуванням ми представлятимемо відсотки як подвійне значення 0.00 до 100.00.

Примітивні типи

Давайте створимо список цілих чисел і надрукуємо їх розподіл:

List integerList = List.of(1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3);
System.out.println(calculateIntegerDistribution(integerList));

Розподіл обчислюється за допомогою:

public static Map calculateIntegerDistribution(List list) {
    return list.stream()
            .collect(Collectors.groupingBy(Integer::intValue,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count * 100.00 / list.size()))))));
}

Цей метод приймає список і передає його. Під час потокової передачі значення є згруповані за їх ціле значення – і їхні значення є враховуються використання Collectors.counting(), перш ніж зібрати в a Map де ключі представляють вхідні значення, а подвійні символи представляють їхні відсотки в розподілі.

Ключові методи тут collect() який приймає два колектори. Збирач ключів збирає, просто групуючи за значеннями ключів (вхідними елементами). Колекціонер цінностей збирає через collectingAndThen() метод, який дозволяє нам підрахувати значення а потім відформатувати їх в іншому форматі, наприклад count * 100.00 / list.size() що дозволяє нам виразити підраховані елементи у відсотках:

{1=45.45, 2=18.18, 3=18.18, 4=9.09, 5=9.09}

Сортування розподілу за значенням або ключем

Під час створення розповсюдження – ви зазвичай хочете відсортувати значення. Частіше за все це мине ключ. Java HashMaps не гарантують збереження порядку вставки, тому нам доведеться використовувати a LinkedHashMap який робить. Крім того, найпростіше повторно передати карту та повторно зібрати її тепер, оскільки вона значно меншого розміру та набагато легша для керування.

Попередня операція може швидко згорнути кілька тисяч записів у малі карти, залежно від кількості ключів, з якими ви маєте справу, тому повторна трансляція не є дорогою:

public static Map calculateIntegerDistribution(List list) {
    return list.stream()
            .collect(Collectors.groupingBy(Integer::intValue,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count.doubleValue() / list.size()))))))
            
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

Об'єкти

Як це можна зробити для об’єктів? Застосовується та сама логіка! Замість функції ідентифікації (Integer::intValue), замість цього ми використовуватимемо потрібне поле, наприклад рік видання наших книг. Давайте створимо кілька книг, збережемо їх у списку, а потім обчислимо розподіл за роками видання:

Ознайомтеся з нашим практичним практичним посібником із вивчення Git з передовими методами, прийнятими в галузі стандартами та включеною шпаргалкою. Припиніть гуглити команди Git і фактично вчитися це!

Book book1 = new Book("001", "Our Mathematical Universe", "Max Tegmark", 432, 2014);
Book book2 = new Book("002", "Life 3.0", "Max Tegmark", 280, 2017);
Book book3 = new Book("003", "Sapiens", "Yuval Noah Harari", 443, 2011);
Book book4 = new Book("004", "Steve Jobs", "Water Isaacson", 656, 2011);

List books = Arrays.asList(book1, book2, book3, book4);

Давайте обчислимо розподіл publishedYear поле:

public static Map calculateDistribution(List books) {
    return books.stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count * 100.00 / books.size()))))))
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

Налаштуйте "%.2f" щоб встановити точність числа з плаваючою комою. Це призводить до:

{2011=50.0, 2014=25.0, 2017=25.0}

50% наведених книжок (2/4) були опубліковані в 2011 році, 25% (1/4) були опубліковані в 2014 році і 25% (1/4) в 2017 році. Що робити, якщо ви хочете відформатувати цей результат по-іншому та нормалізувати діапазон в 0..1?

Розрахунок нормалізованого (відсоткового) розподілу колекції в Java

Щоб нормалізувати відсотки від a 0.0...100.0 діапазон до a 0..1 діапазон – ми просто адаптуємо collectingAndThen() зателефонувати до НЕ помножте рахунок на 100.0 перед поділом на розмір колекції.

Раніше Long кількість повернуто Collectors.counting() було неявно перетворено на подвійне (множення на подвійне значення) – тому цього разу ми хочемо явно отримати doubleValue() в count:

    public static Map calculateDistributionNormalized(List books) {
        return books.stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.4f", count.doubleValue() / books.size()))))))
            
            .entrySet()
            .stream()
            .sorted(comparing(e -> e.getKey()))
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

Налаштуйте "%.4f" щоб встановити точність числа з плаваючою комою. Це призводить до:

{2011=0.5, 2014=0.25, 2017=0.25}

Розрахувати кількість елементів (частоту) збирання

Нарешті, ми можемо отримати кількість елементів (частоту всіх елементів) у колекції, просто не ділячи кількість на розмір колекції! Це повністю ненормалізована кількість:

   public static Map calculateDistributionCount(List books) {
        return books
            .stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Integer.parseInt(String.format("%s", count.intValue()))))))
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

Це призводить до:

{2011=2, 2014=1, 2017=1}

Дійсно, є дві книжки 2011 року та по одній 2014 та 2017 років.

Висновок

Розрахунок розподілу даних є звичайним завданням у програмах із великим вмістом даних і не потребує використання зовнішніх бібліотек або складного коду. Завдяки підтримці функціонального програмування Java зробила роботу з колекціями простою!

У цій короткій чернетці ми розглянули, як можна обчислити підрахунок частоти всіх елементів у колекції, а також як обчислити карти розподілу, нормалізовані до відсотків між 0 та 1 а також 0 та 100 на Java.

Часова мітка:

Більше від Stackabuse