Bawię się leniwymi operacyjnymi funkcjami w Javie SE 8 i chcę map
indeksować i
do pary / krotki (i, value[i])
, a następnie filter
bazować na drugim value[i]
elemencie, a na koniec wyprowadzać tylko indeksy.
Czy nadal muszę cierpieć z tego powodu: Jaki jest odpowiednik pary C ++ <L, R> w Javie? w nowej, odważnej erze lambd i strumieni?
Aktualizacja: Przedstawiłem raczej uproszczony przykład, który ma fajne rozwiązanie oferowane przez @dkatzel w jednej z poniższych odpowiedzi. Jednak nie uogólnia. Dlatego dodam bardziej ogólny przykład:
package com.example.test;
import java.util.ArrayList;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
boolean [][] directed_acyclic_graph = new boolean[][]{
{false, true, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, false}
};
System.out.println(
IntStream.range(0, directed_acyclic_graph.length)
.parallel()
.mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
.filter(j -> directed_acyclic_graph[j][i])
.count()
)
.filter(n -> n == 0)
.collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
);
}
}
Daje to niepoprawne dane wyjściowe, [0, 0, 0]
które odpowiadają liczbie wszystkich trzech kolumn false
. Potrzebuję wskaźników tych trzech kolumn. Prawidłowe wyjście powinno być [0, 2, 4]
. Jak mogę uzyskać ten wynik?
java
lambda
functional-programming
java-8
java-stream
nekromanta
źródło
źródło
AbstractMap.SimpleImmutableEntry<K,V>
od lat ... Ale w każdym razie, zamiast mapowaniai
do(i, value[i])
tylko do filtrowania przezvalue[i]
i mapowanie z powrotemi
: dlaczego nie tylko przez filtrvalue[i]
w pierwszej kolejności, bez mapowania?i
w strumieniu. Potrzebuję równieżvalue[i]
kryteriów. Właśnie dlatego potrzebuję(i, value[i])
[0, 2, 4]
?Odpowiedzi:
AKTUALIZACJA: Ta odpowiedź jest odpowiedzią na pierwotne pytanie: Czy Java SE 8 ma pary czy krotki? (I domyślnie, jeśli nie, dlaczego nie?) OP zaktualizował pytanie bardziej kompletnym przykładem, ale wydaje się, że można je rozwiązać bez użycia jakiejkolwiek struktury par. [Uwaga od OP: oto inna poprawna odpowiedź .]
Krótka odpowiedź brzmi: nie. Musisz albo stworzyć własną, albo wprowadzić jedną z kilku bibliotek, które ją implementują.
Posiadanie
Pair
zajęć z Java SE zostało zaproponowane i odrzucone przynajmniej raz. Zobacz ten wątek dyskusyjny na jednej z list mailingowych OpenJDK. Kompromisy nie są oczywiste. Z jednej strony istnieje wiele implementacji Par w innych bibliotekach i kodzie aplikacji. To pokazuje potrzebę, a dodanie takiej klasy do Java SE zwiększy ponowne użycie i udostępnianie. Z drugiej strony posiadanie klasy Pair zwiększa pokusę tworzenia skomplikowanych struktur danych z par i kolekcji bez tworzenia niezbędnych typów i abstrakcji. (To parafraza przesłania Kevina Bourilliona z tego wątku.)Polecam wszystkim przeczytanie tego całego wątku e-mail. Jest niezwykle wnikliwy i nie ma ognia. To całkiem przekonujące. Kiedy się zaczęło, pomyślałem: „Tak, powinna być klasa Pair w Java SE”, ale zanim wątek dobiegł końca, zmieniłem zdanie.
Należy jednak pamiętać, że JavaFX ma klasę javafx.util.Pair . Interfejsy API JavaFX ewoluowały niezależnie od interfejsów API Java SE.
Jak widać z połączonego pytania Co jest równoważne parze C ++ w Javie? wokół dość podobnego API jest dość duża przestrzeń projektowa. Czy obiekty powinny być niezmienne? Czy powinny być możliwe do serializacji? Czy powinny być porównywalne? Czy klasa powinna być ostateczna, czy nie? Czy należy zamówić dwa elementy? Czy powinien to być interfejs czy klasa? Po co zatrzymywać się w parach? Dlaczego nie trzy, czterokąty lub N-krotki?
I oczywiście istnieje nieunikniona nazwa dla elementów:
Jednym wielkim problemem, o którym prawie nie wspomniano, jest związek par z prymitywami. Jeśli masz układ
(int x, int y)
odniesienia reprezentujący punkt w przestrzeni 2D, reprezentowanie go jakoPair<Integer, Integer>
pochłania trzy obiekty zamiast dwóch 32-bitowych słów. Co więcej, obiekty te muszą znajdować się na stercie i będą obciążone GC.Wydaje się jasne, że podobnie jak w przypadku strumieni, niezbędne byłoby istnienie prymitywnych specjalizacji dla par. Czy chcemy zobaczyć:
Nawet i
IntIntPair
nadal wymagałoby jednego obiektu na stercie.Przypominają one oczywiście rozpowszechnianie się funkcjonalnych interfejsów w
java.util.function
pakiecie w Javie SE 8. Jeśli nie chcesz rozdętego API, które byś pominął? Można również argumentować, że to nie wystarczy i żeBoolean
należy dodać także specjalizacje, powiedzmy, do .Mam wrażenie, że gdyby Java dodała klasę Pair już dawno temu, byłoby to proste, a nawet uproszczone i nie spełniłoby wielu przypadków użycia, które teraz przewidujemy. Weź pod uwagę, że gdyby Para została dodana w ramach czasowych JDK 1.0, prawdopodobnie byłaby zmienna! (Spójrz na java.util.Date.) Czy ludzie byliby z tego zadowoleni? Domyślam się, że gdyby w Javie istniała klasa Pair, byłaby to trochę nieprzydatna i wszyscy nadal będą się rozwijać, aby zaspokoić swoje potrzeby, w bibliotekach zewnętrznych będą różne implementacje Pair i Tuple, a ludzie nadal będą się kłócić / dyskutować o tym, jak naprawić klasę Java pary. Innymi słowy, jakby w tym samym miejscu, w którym jesteśmy dzisiaj.
Tymczasem trwają prace nad fundamentalnym problemem, którym jest lepsza obsługa JVM (i ostatecznie języka Java) dla typów wartości . Zobacz ten dokument Stan wartości . Jest to wstępna, spekulacyjna praca, która obejmuje tylko kwestie z perspektywy JVM, ale ma już za sobą wiele przemyśleń. Oczywiście nie ma gwarancji, że wejdzie to w Javę 9 lub kiedykolwiek wejdzie gdziekolwiek, ale pokazuje aktualny kierunek myślenia na ten temat.
źródło
Pair<T,U>
. Ponieważ leki generyczne muszą być typu odniesienia. Wszelkie prymitywy zostaną zapakowane, gdy zostaną zapisane. Do przechowywania prymitywów naprawdę potrzebujesz innej klasy.valueOf
powinny być jedynym sposobem na uzyskanie wystąpienia w pudełku. Ale są tam od wersji Java 1.0 i prawdopodobnie nie warto w tym momencie próbować ich zmieniać.Pair
lubTuple
klasa z fabryczną metodą tworzącą niezbędne klasy specjalizacji (ze zoptymalizowanym przechowywaniem) w sposób przezroczysty w tle. Ostatecznie lambdas robią dokładnie to: mogą przechwycić dowolną liczbę zmiennych dowolnego typu. A teraz wyobraź sobie obsługę języka pozwalającą na stworzenie odpowiedniej klasy krotek w czasie wykonywania wyzwalanym przezinvokedynamic
instrukcję…invokedynamic
fabryce opartej na zasadzie podobnej do lambda takie późniejsze modernizacje nie stanowiłyby problemu. Nawiasem mówiąc, jagnięta również nie mają tożsamości. Jak wyraźnie powiedziano, tożsamość, którą możesz dziś postrzegać, jest artefaktem obecnej implementacji.Możesz spojrzeć na te wbudowane klasy:
AbstractMap.SimpleEntry
AbstractMap.SimpleImmutableEntry
źródło
SimpleImmutableEntry
gwarantuje tylko, że odniesienia przechowywane wEntry
nie zmieniają się, a nie, że pola połączonego obiektukey
ivalue
obiekty (lub te obiektów, do których się łączą) się nie zmieniają.Niestety Java 8 nie wprowadziła par ani krotek. Zawsze możesz oczywiście użyć org.apache.commons.lang3.tuple (którego osobiście używam w połączeniu z Javą 8) lub możesz stworzyć własne opakowania. Lub użyj Map. Lub coś w tym rodzaju, jak wyjaśniono w zaakceptowanej odpowiedzi na pytanie, z którym się łączysz.
AKTUALIZACJA: JDK 14 wprowadza rekordy jako funkcję podglądu. Nie są to krotki, ale można je wykorzystać do zapisania wielu takich samych problemów. W twoim konkretnym przykładzie z góry może to wyglądać mniej więcej tak:
Po skompilowaniu i uruchomieniu z JDK 14 (w chwili pisania tej wersji kompilacji z wczesnym dostępem) przy użyciu
--enable-preview
flagi otrzymujesz następujący wynik:źródło
Wygląda na to, że pełny przykład można rozwiązać bez użycia jakiejkolwiek struktury par. Kluczem jest filtrowanie według indeksów kolumn, przy czym predykat sprawdza całą kolumnę, zamiast mapować indeksy kolumn na liczbę
false
wpisów w tej kolumnie.Kod, który to robi, znajduje się tutaj:
Wynikiem tego
[0, 2, 4]
jest, moim zdaniem, poprawny wynik wymagany przez PO.Zwróć także uwagę na
boxed()
operację dzieleniaint
wartości naInteger
obiekty. Umożliwia to korzystanie z istniejącegotoList()
kolektora zamiast wypisywania funkcji kolektora, które same wykonują boks.źródło
true
). W związku z tym zaakceptuję Twoją drugą odpowiedź jako poprawną, ale również wskaż tę! Dziękuję bardzo :)Vavr (wcześniej nazywany JavaSlang) ( http://www.vavr.io ) zapewnia również krotki (do rozmiaru 8). Oto javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .
To jest prosty przykład:
Dlaczego sam JDK nie przyszedł z prostymi krotkami do teraz, jest dla mnie tajemnicą. Pisanie klas opakowań wydaje się być codziennym zajęciem.
źródło
Od wersji Java 9 możesz tworzyć instancje
Map.Entry
łatwiejsze niż wcześniej:Map.entry
zwraca niemodyfikowalneEntry
i zabrania zerowania.źródło
Ponieważ zależy Ci tylko na indeksach, nie musisz w ogóle mapować krotek. Dlaczego po prostu nie napisać filtru, który korzysta z elementów wyszukiwania w tablicy?
źródło
Tak.
Map.Entry
może być używany jakoPair
.Niestety nie pomaga w strumieniach Java 8, ponieważ problem polega na tym, że chociaż lambdas mogą przyjmować wiele argumentów, język Java pozwala na zwrócenie tylko jednej wartości (typu obiektowego lub pierwotnego). Oznacza to, że ilekroć masz strumień, kończysz się przekazaniem jednego obiektu z poprzedniej operacji. Jest to brak w języku Java, ponieważ jeśli obsługiwanych jest wiele wartości zwracanych ORAZ obsługiwane są strumienie, moglibyśmy wykonywać o wiele ładniejsze nietrywialne zadania wykonywane przez strumienie.
Do tego czasu wykorzystanie jest niewielkie.
EDYCJA 2018-02-12: Podczas pracy nad projektem napisałem klasę pomocnika, która pomaga w rozwiązaniu szczególnego przypadku posiadania identyfikatora wcześniej w strumieniu, którego potrzebujesz później, ale część strumienia pomiędzy nie wie o tym. Dopóki się nie obejrzę i nie wydam go samodzielnie, jest on dostępny na IdValue.java z testem jednostkowym na IdValueTest.java
źródło
Kolekcje Eclipse mają
Pair
i wszystkie kombinacje par pierwotnych / obiektowych (dla wszystkich ośmiu pierwotnych).Tuples
Fabryka może tworzyć instancjePair
iPrimitiveTuples
fabryczne mogą być wykorzystywane do tworzenia wszystkich kombinacji par pierwotnych / obiektów.Dodaliśmy je przed wydaniem Java 8. Przydały się one do implementacji iteratorów klucz / wartość dla naszych prymitywnych map, które obsługujemy również we wszystkich kombinacjach prymitywów / obiektów.
Jeśli chcesz dodać dodatkowy narzut biblioteki, możesz użyć zaakceptowanego rozwiązania Stuarta i zebrać wyniki w prymitywne,
IntList
aby uniknąć boksu. Dodaliśmy nowe metody w Eclipse Collections 9.0, aby umożliwić tworzenieInt/Long/Double
kolekcji zeInt/Long/Double
strumieni.Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.
źródło