Számítsa ki az eloszlást a Java gyűjteményből

A számok gyűjteményének (vagy olyan objektumoknak, amelyeknek a mezőit ellenőrizni szeretné) alakítása e számok eloszlására egy általános statisztikai technika, amelyet különféle kontextusokban alkalmaznak jelentéskészítésben és adatvezérelt alkalmazásokban.

Adott egy gyűjtemény:

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

Megtekintheti eloszlásukat számként (az egyes elemek gyakorisága), és az eredményeket térképen tárolhatja:

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

Vagy tudsz szabványosít az értékek az összes érték alapján – százalékban kifejezve:

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

Vagy akár kifejezni ezeket a százalékokat a 0..100 formátum helyett a 0..1 formátumban.

Ebben az útmutatóban megvizsgáljuk, hogyan számíthat ki eloszlást egy gyűjteményből – mind primitív típusok, mind objektumok használatával, amelyek mezőiről érdemes jelentést készíteni az alkalmazásban.

A Java funkcionális programozási támogatásával a disztribúciók kiszámítása egyszerűbb, mint valaha. Számgyűjteményével és egy gyűjteményével fogunk dolgozni Books:

public class Book {

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

   
}

Számítsa ki a gyűjtemény eloszlását Java nyelven

Először nézzük meg, hogyan számítható ki a primitív típusok eloszlása. Az objektumokkal végzett munka egyszerűen lehetővé teszi egyéni metódusok meghívását a tartományosztályaiból, hogy nagyobb rugalmasságot biztosítson a számításokban.

Alapértelmezés szerint a százalékokat duplájaként jelenítjük meg 0.00 nak nek 100.00.

Primitív típusok

Hozzuk létre az egész számok listáját, és nyomtassuk ki az eloszlásukat:

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

Az eloszlás kiszámítása a következőképpen történik:

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()))))));
}

Ez a módszer elfogad egy listát és streamel. Streamelés közben az értékek a következők által csoportosított egész értékük – és értékeik azok számítani segítségével Collectors.counting(), mielőtt összegyűjtik a Map ahol a kulcsok a bemeneti értékeket, a kettősök pedig azok százalékos arányát jelentik az eloszlásban.

A legfontosabb módszerek itt a következők collect() amely elfogadja két gyűjtő. A kulcsgyűjtő egyszerűen a kulcsértékek (bemeneti elemek) szerinti csoportosítással gyűjt. Az értékgyűjtő a collectingAndThen() módszer, amely lehetővé teszi számunkra számolja meg az értékeket majd formázza őket más formátumba, mint pl count * 100.00 / list.size() amely lehetővé teszi, hogy a megszámlált elemeket százalékban fejezzük ki:

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

Rendezze az eloszlást érték vagy kulcs szerint

A disztribúciók létrehozásakor általában érdemes rendezni az értékeket. Leggyakrabban ez lesz kulcs. Jáva HashMaps nem garantálja a beillesztési sorrend megőrzését, ezért használnunk kell a LinkedHashMap ami igen. Ezen túlmenően a legegyszerűbb a térkép újbóli streamelése és újragyűjtése, mivel az sokkal kisebb méretű és sokkal könnyebben kezelhető.

Az előző művelet gyorsan összecsukhat több ezer rekordot kis térképekké, a kezelt kulcsok számától függően, így az újrastreamelés nem drága:

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));
}

tárgyak

Hogyan lehet ezt megtenni tárgyaknál? Ugyanez a logika érvényesül! Azonosító függvény helyett (Integer::intValue), helyette a kívánt mezőt használjuk – például könyveink kiadási évét. Hozzunk létre néhány könyvet, tároljuk egy listában, majd számítsuk ki a kiadási évek eloszlását:

Tekintse meg gyakorlatias, gyakorlati útmutatónkat a Git tanulásához, amely tartalmazza a bevált gyakorlatokat, az iparág által elfogadott szabványokat és a mellékelt csalólapot. Hagyd abba a guglizást a Git parancsokkal, és valójában tanulni meg!

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);

Számítsuk ki az eloszlását publishedYear terület:

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));
}

Állítsa be a "%.2f" a lebegőpontos pontosság beállításához. Ennek eredménye:

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

Az adott könyvek 50%-a (2/4) 2011-ben, 25%-a (1/4) 2014-ben, 25%-a (1/4) 2017-ben jelent meg. Mi van, ha ezt az eredményt másképp szeretné formázni, normalizálni a tartomány 0..1?

A gyűjtemény normalizált (százalékos) eloszlásának kiszámítása Java nyelven

A százalékok normalizálásához a 0.0...100.0 tartomány a 0..1 tartomány – egyszerűen adaptáljuk a collectingAndThen() hívja nem szorozzuk meg a számlálást 100.0 mielőtt elosztaná a gyűjtemény méretével.

Korábban a Long által visszaadott szám Collectors.counting() implicit módon duplává lett konvertálva (dupla értékkel való szorzás) – ezért ezúttal explicit módon szeretnénk megkapni a doubleValue() az 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));
}

Állítsa be a "%.4f" a lebegőpontos pontosság beállításához. Ennek eredménye:

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

Számítsa ki a gyűjtemény elemszámát (gyakoriságát).

Végül – úgy kaphatjuk meg a gyűjtemény elemszámát (az összes elem gyakoriságát), hogy egyszerűen nem osztjuk el a számot a gyűjtemény méretével! Ez egy teljesen nem normalizált szám:

   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));
}

Ennek eredményeként:

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

Valóban, két könyv van 2011-ből, és egy-egy 2014-ből és 2017-ből.

Következtetés

Az adatok eloszlásának kiszámítása gyakori feladat az adatban gazdag alkalmazásokban, és nincs szükség külső könyvtárak vagy összetett kód használatára. A funkcionális programozási támogatással a Java gyerekjáték tette a gyűjteményekkel való munkát!

Ebben a rövid vázlatban megvizsgáltuk, hogyan számíthatja ki a gyűjtemény összes elemének gyakorisági számát, valamint hogyan lehet kiszámítani a százalékos arányokra normalizált eloszlási térképeket. 0 és a 1 szintén 0 és a 100 Java-ban.

Időbélyeg:

Még több Stackabus