Dlaczego nie Set
zapewnia operacji, aby uzyskać element równy innemu elementowi?
Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo); // get the Foo element from the Set that equals foo
Mogę zapytać, czy Set
zawiera element równy bar
, więc dlaczego nie mogę uzyskać tego elementu? :(
Aby wyjaśnić, equals
metoda jest zastępowana, ale sprawdza tylko jedno z pól, a nie wszystkie. Tak więc dwa Foo
obiekty, które są uważane za równe, mogą mieć różne wartości, dlatego nie mogę po prostu użyć foo
.
java
collections
set
equals
foobar
źródło
źródło
SortedSet
i jego implementacji, które są oparte na mapie (np.TreeSet
Umożliwia dostępfirst()
).NSSet
) ma taką metodę. Jest wywoływanymember
i zwraca obiekt w zestawie, który porównuje „równy” z parametremmember
metody (który może oczywiście być innym obiektem i mieć również inne właściwości, które równe mogą nie sprawdzać).Odpowiedzi:
Nie byłoby sensu pozyskiwać elementu, jeśli jest on równy. A
Map
lepiej nadaje się do tego przypadku użycia.Jeśli nadal chcesz znaleźć element, nie masz innej opcji niż użycie iteratora:
źródło
Map
lepiej pasuje (Map<Foo, Foo>
w tym przypadku.)Map<Foo, Foo>
jako zamiennika, wadą jest to, że mapa zawsze musi przechowywać co najmniej klucz i wartość (a dla wydajności powinna również przechowywać skrót), podczas gdy zestaw może uciec po prostu przechowując wartość (i może skrót do wydajności). Dobra implementacja zestawu może być równie szybka,Map<Foo, Foo>
ale zużywa do 50% mniej pamięci. W przypadku Javy nie będzie to miało znaczenia, ponieważ HashSet jest wewnętrznie oparty na HashMap.Aby odpowiedzieć na dokładne pytanie „ Dlaczego nie
Set
zapewnia się operacji uzyskania elementu, który jest równy innemu elementowi?”, Odpowiedź brzmiałaby: ponieważ projektanci frameworku kolekcji nie byli zbyt przyszłościowi. Nie przewidzieli twojego bardzo uzasadnionego przypadku użycia, naiwnie próbowali „modelować abstrakcję zestawu matematycznego” (z javadoc) i po prostu zapomnieli dodać użytecznąget()
metodę.Przejdźmy do dorozumianego pytania „ skąd masz ten element”: Myślę, że najlepszym rozwiązaniem jest użycie
Map<E,E>
zamiast aSet<E>
, aby zmapować elementy do siebie. W ten sposób można skutecznie odzyskać element z „zestawu”, ponieważ metoda get ()Map
znajdzie element przy użyciu wydajnego algorytmu tabeli skrótów lub algorytmu drzewa. Jeśli chcesz, możesz napisać własną implementację,Set
która oferuje dodatkowąget()
metodę, obejmującąMap
.Następujące odpowiedzi są moim zdaniem złe lub złe:
„Nie musisz zdobywać elementu, ponieważ masz już równy przedmiot”: twierdzenie jest błędne, jak już wskazano w pytaniu. Dwa obiekty, które są równe, wciąż mogą mieć inny stan, który nie jest istotny dla równości obiektu. Celem jest uzyskanie dostępu do tego stanu elementu zawartego w
Set
, a nie stanu obiektu używanego jako „zapytanie”.„Nie masz innej opcji niż użycie iteratora”: jest to liniowe wyszukiwanie w kolekcji, która jest całkowicie nieefektywna w przypadku dużych zbiorów (jak na ironię, wewnętrznie
Set
jest zorganizowana jako mapa hash lub drzewo, które można wydajnie przeszukiwać). Nie rób tego! Za pomocą tego podejścia widziałem poważne problemy z wydajnością w rzeczywistych systemach. Moim zdaniem okropne w brakującejget()
metodzie jest nie tyle, że obejście jej jest nieco kłopotliwe, ale większość programistów zastosuje metodę wyszukiwania liniowego bez zastanowienia się nad konsekwencjami.źródło
get()
. W twoim przykładzie byłbym bardzo zdezorientowany przez customerSet.get (thisCustomer). (Podczas gdy mapa, jak sugeruje wiele odpowiedzi) byłaby w porządku z canonicalCustomerMap.get (ten klient). Byłbym również w porządku z metodą, która jest wyraźniej nazwana (taka jak metoda członka Objective-C w NSSet).Jeśli masz równy przedmiot, dlaczego potrzebujesz tego z zestawu? Jeśli „równa się” tylko kluczem,
Map
byłby lepszym wyborem.W każdym razie, zrobią to:
W Javie 8 może stać się jednym linerem:
źródło
Konwertuj zestaw na listę, a następnie użyj
get
metody listyźródło
Domyślny zestaw w Javie nie jest niestety przeznaczony do wykonywania operacji „get”, jak dokładnie wyjaśnił jschreiner .
Rozwiązania polegające na użyciu iteratora w celu znalezienia interesującego elementu (sugerowanego przez dacwe ) lub usunięcia tego elementu i ponownego dodania go ze zaktualizowanymi wartościami (sugerowanymi przez KyleM ), mogą działać, ale mogą być bardzo nieefektywne.
Przesłonięcie implementacji równości, tak aby nierównomierne obiekty były „równe”, jak poprawnie stwierdził David Ogren , może łatwo powodować problemy z konserwacją.
A użycie mapy jako wyraźnego zamiennika (jak sugeruje wielu), imho sprawia, że kod jest mniej elegancki.
Jeśli celem jest uzyskanie dostępu do oryginalnej instancji elementu zawartego w zestawie (mam nadzieję, że poprawnie zrozumiałem twój przypadek użycia), oto inne możliwe rozwiązanie.
Osobiście miałem taką samą potrzebę podczas opracowywania gry wideo klient-serwer w Javie. W moim przypadku każdy klient miał kopie komponentów przechowywane na serwerze, a problem występował zawsze, gdy klient musiał zmodyfikować obiekt serwera.
Przekazywanie obiektu przez Internet oznaczało, że klient i tak miał różne instancje tego obiektu. Aby dopasować tę „skopiowaną” instancję do oryginalnej, postanowiłem użyć identyfikatorów UUID Java.
Stworzyłem więc abstrakcyjną klasę UniqueItem, która automatycznie nadaje losowy unikalny identyfikator każdemu wystąpieniu jego podklasy.
Ten identyfikator UUID jest współużytkowany przez klienta i instancję serwera, więc w ten sposób można łatwo dopasować je za pomocą mapy.
Jednak bezpośrednie użycie mapy w podobnej sprawie było nadal nieeleganckie. Ktoś może argumentować, że korzystanie z mapy może być bardziej skomplikowane w utrzymaniu i obsłudze.
Z tych powodów wdrożyłem bibliotekę o nazwie MagicSet, która sprawia, że korzystanie z mapy jest „przezroczyste” dla programisty.
https://github.com/ricpacca/magicset
Podobnie jak oryginalny Java HashSet, MagicHashSet (który jest jedną z implementacji MagicSet dostarczonych w bibliotece) używa kopii zapasowej HashMap, ale zamiast elementów jako kluczy i wartości zastępczej jako wartości, używa UUID elementu jako klucza a sam element jako wartość. Nie powoduje to obciążenia pamięci w porównaniu do normalnego zestawu HashSet.
Ponadto MagicSet może być używany dokładnie jako Zestaw, ale z kilkoma innymi metodami zapewniającymi dodatkowe funkcje, takie jak getFromId (), popFromId (), removeFromId () itp.
Jedynym wymaganiem do korzystania z niego jest to, że każdy element, który chcesz przechowywać w MagicSet, musi rozszerzyć klasę abstrakcyjną UniqueItem.
Oto przykład kodu, który wyobraża sobie odzyskanie oryginalnej instancji miasta z MagicSet, biorąc pod uwagę inną instancję tego miasta o tym samym UUID (lub nawet jego UUID).
źródło
Jeśli Twój zestaw jest w rzeczywistości
NavigableSet<Foo>
(np. ATreeSet
), iFoo implements Comparable<Foo>
możesz użyć(Dzięki komentarzowi @ eliran-malka za podpowiedź.)
źródło
Dzięki Javie 8 możesz:
Ale bądź ostrożny, .get () zgłasza NoSuchElementException, lub możesz manipulować Opcjonalnym elementem.
źródło
item->item.equals(theItemYouAreLookingFor)
można skrócić dotheItemYouAreLookingFor::equals
Jeśli zdobędziesz tylko jedną nagrodę, nie będzie to bardzo skuteczne, ponieważ będziesz zapętlać wszystkie swoje elementy, ale podczas wykonywania wielu operacji wyszukiwania na dużym zestawie zauważysz różnicę.
źródło
Dlaczego:
Wygląda na to, że Set odgrywa użyteczną rolę w zapewnianiu możliwości porównania. Został zaprojektowany tak, aby nie przechowywać zduplikowanych elementów.
Z powodu tego zamiaru / projektu, jeśli ktoś otrzyma () odwołanie do przechowywanego obiektu, a następnie zmutuje go, możliwe jest, że zamiary projektowe Seta zostaną udaremnione i mogą spowodować nieoczekiwane zachowanie.
Z JavaDocs
W jaki sposób:
Po wprowadzeniu strumieni można wykonać następujące czynności
źródło
Co z użyciem klasy Arrays?
wyjście:
pozycje pierwsza, druga
źródło
Lepiej do tego celu użyj obiektu Java HashMap http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
źródło
Wiem, że zostało to zadane i udzielone odpowiedzi dawno temu, jednak jeśli ktoś jest zainteresowany, oto moje rozwiązanie - klasa niestandardowego zestawu wspierana przez HashMap:
http://pastebin.com/Qv6S91n9
Możesz łatwo wdrożyć wszystkie inne metody Set.
źródło
Zostałem tam zrobiony !! Jeśli używasz Guawy, szybkim sposobem przekonwertowania jej na mapę jest:
źródło
możesz użyć klasy Iterator
źródło
Jeśli chcesz n-ty element z HashSet, możesz przejść z poniższym rozwiązaniem, tutaj dodałem obiekt ModelClass w HashSet.
źródło
Jeśli spojrzysz na kilka pierwszych wierszy realizacji
java.util.HashSet
, zobaczysz:Tak czy inaczej
HashSet
używa wewnętrznieHashMap
, co oznacza, że jeśli użyjeszHashMap
bezpośrednio i użyjesz tej samej wartości co klucz, a wartość uzyskasz pożądany efekt i zaoszczędzisz sobie trochę pamięci.źródło
wygląda na to, że właściwym obiektem do użycia jest Interner z guawy:
Ma również kilka bardzo interesujących dźwigni, takich jak concurrencyLevel lub rodzaj używanych odniesień (warto zauważyć, że nie oferuje SoftInterner, który uważam za bardziej przydatny niż WeakInterner).
źródło
Ponieważ jakakolwiek konkretna implementacja zestawu może, ale nie musi, mieć dostęp losowy .
Zawsze możesz uzyskać iterator i przejść przez zestaw, używając metody iteratorów,
next()
aby zwrócić pożądany wynik po znalezieniu równego elementu. Działa to niezależnie od implementacji. Jeśli implementacja NIE ma dostępu losowego (wyobrażenie zestawu wspieranego przez listę połączoną),get(E element)
metoda w interfejsie byłaby myląca, ponieważ musiałaby iterować kolekcję, aby znaleźć element do zwrócenia, iget(E element)
wydaje się, że sugeruje to, że konieczne, aby Zestaw mógł przejść bezpośrednio do elementu, aby uzyskać.contains()
może, ale nie musi, robić to samo, oczywiście w zależności od implementacji, ale nazwa nie wydaje się narażać na takie same nieporozumienia.źródło
Tak, używaj
HashMap
... ale w wyspecjalizowany sposób: pułapka, którą przewiduję, próbując użyćHashMap
jako pseudonimu,Set
jest możliwym pomyleniem między „rzeczywistymi” elementamiMap/Set
i „kandydującymi” elementami, tj. Elementami używanymi do testowania, czyequal
element jest już obecny. Jest to dalekie od niezawodności, ale odsuwa cię od pułapki:Następnie zrób to:
Ale ... chcesz teraz
candidate
autodestrukcji w jakiś sposób, chyba że programista faktycznie natychmiast umieści go wMap/Set
... chciałbyścontains
"skazić"candidate
tak, aby każde użycie go, chyba że dołączy doMap
anatemy „. Być może mógłbyśSomeClass
zaimplementować nowyTaintable
interfejs.Bardziej satysfakcjonującym rozwiązaniem jest GettableSet , jak poniżej. Jednak aby to zadziałało, musisz być odpowiedzialny za projektowanie
SomeClass
, aby wszystkie konstruktory były niewidoczne (lub ... zdolne i chętne do zaprojektowania i użycia do tego klasy opakowania):Realizacja:
Twoje
NoVisibleConstructor
zajęcia wyglądają następująco:PS jeden problem techniczny z taką
NoVisibleConstructor
klasą: można się sprzeciwić, że taka klasa jest z naturyfinal
, co może być niepożądane. W rzeczywistości zawsze można dodać fałszywyprotected
konstruktor bez parametrów :... co pozwoliłoby przynajmniej na kompilację podklasy. Będziesz musiał pomyśleć o tym, czy musisz dołączyć inną
getOrCreate()
podrzędną metodę fabryczną.Ostatnim krokiem jest abstrakcyjna klasa podstawowa (NB „element” dla listy, „element członkowski” dla zestawu), taki jak ten dla członków zestawu (jeśli to możliwe - ponownie, zakres zastosowania klasy opakowania, w której klasa nie jest pod twoją kontrolą, lub już ma klasę podstawową itp.), aby maksymalnie ukryć implementację:
... użycie jest dość oczywiste (wewnątrz
SomeClass
„sstatic
metody fabryki):źródło
Umowa kodu skrótu wyjaśnia, że:
Więc twoje założenie:
jest źle i zrywasz umowę. Jeśli spojrzymy na metodę „zawiera” interfejsu Set, mamy to:
Aby osiągnąć to, czego chcesz, możesz użyć mapy, w której definiujesz klucz i przechowujesz swój element za pomocą klucza, który określa, w jaki sposób obiekty różnią się między sobą lub są sobie równe.
źródło
Metoda szybkiego pomocnika, która może rozwiązać tę sytuację:
źródło
Podejście może być podejściem
źródło
Spróbuj użyć tablicy:
źródło