Uzyskanie elementu z zestawu

Dlaczego Set nie dostarcza 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ę dostać tego elementu? :(

Dla wyjaśnienia, metoda equals jest nadpisana, ale sprawdza tylko jedno z pól, nie wszystkie. 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.

Author: Nathan, 2011-09-02

20 answers

Nie ma sensu uzyskiwać elementu, jeśli jest równy. A Map lepiej nadaje się do tego zastosowania.


Jeśli nadal chcesz znaleźć element, nie masz innej opcji niż użyć iteratora:

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() { 
        return string.hashCode(); 
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}
 87
Author: dacwe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-02 12:31:21

Aby odpowiedzieć na dokładne pytanie " dlaczego nie dostarcza operacji, aby uzyskać element równy innemu elementowi?", odpowiedź brzmiałaby: ponieważ projektanci RAM kolekcji nie byli bardzo przyszłościowe. Nie przewidzieli twojego bardzo uzasadnionego przypadku użycia, naiwnie próbowali "modelować abstrakcję zbioru matematycznego" (z javadoc) i po prostu zapomnieli dodać użyteczną metodę get().

Teraz do dorozumianego pytania " Jak uzyskać element then": myślę, że najlepszym rozwiązaniem jest użycie Map<E,E> zamiast Set<E>, aby odwzorować elementy do siebie. W ten sposób możesz efektywnie pobrać element z "set", ponieważ metoda get () Map znajdzie element za pomocą wydajnego algorytmu tabeli skrótów lub drzewa. Jeśli chcesz, możesz napisać własną implementację Set, która oferuje dodatkową metodę get(), zamykającą Map.

Poniższe odpowiedzi są moim zdaniem złe lub błąd:

"nie musisz pobierać elementu, ponieważ masz już równy obiekt": twierdzenie jest błędne, jak już pokazałeś w pytaniu. Dwa obiekty, które są równe, 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, jak tylko użyć iteratora": jest to liniowe przeszukiwanie kolekcji co jest całkowicie nieefektywne w przypadku dużych zbiorów (ironicznie, wewnętrznie {[0] } jest zorganizowane jako mapa hashowa lub drzewo, które można skutecznie odpytywać). Nie rób tego! Widziałem poważne problemy z wydajnością w rzeczywistych systemach, stosując to podejście. Moim zdaniem to, co jest straszne w brakującej metodzie get(), jest nie tyle kłopotliwe, że obejście jej jest nieco uciążliwe, ale to, że większość programistów będzie używać podejścia wyszukiwania liniowego bez myślenia o implikacjach.

 312
Author: jschreiner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-25 20:35:48

Konwertuj zestaw na Listę, a następnie użyj metody get Listy

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
 15
Author: To Kra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-25 13:25:21

Jeśli masz równy obiekt, po co ci ten z zestawu? Jeśli jest ona" równa " tylko przez klucz, lepszym wyborem będzie Map.

W każdym razie, zrobi to:

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

W Javie 8 to może stać się jednym linerem:

return all.stream().filter(sample::equals).findAny().orElse(null);
 11
Author: Arne Burmeister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-24 19:54:36

Jeśli twój zestaw jest w rzeczywistości NavigableSet<Foo> (np. TreeSet) i Foo implements Comparable<Foo>, możesz użyć

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(dzięki komentarzowi @ eliran-malka za podpowiedź.)

 9
Author: Jesse Glick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-19 21:22:28

Domyślnie ustawiony w Javie nie jest, niestety, zaprojektowany, aby zapewnić operację "get", jak to dokładnie wyjaśniono jschreiner .

Rozwiązania wykorzystania iteratora do znalezienia interesującego elementu (sugerowanego przez dacwe) lub usunięcia elementu i ponownego dodania go z jego wartościami zaktualizowanymi (sugerowanymi przez KyleM), mogą działać, ale mogą być bardzo nieefektywne.

Nadpisanie implementacji equals tak, że obiekty nie równe są "równe", zgodnie z poprawnym zapisem David Ogren , może łatwo powodować problemy konserwacyjne.

A użycie Mapy jako jawnego zamiennika (jak sugerują wielu), imho, sprawia, że kod jest mniej elegancki.

Jeśli celem jest uzyskanie dostępu do oryginalnej instancji elementu zawartego w zbiorze (mam nadzieję, że dobrze zrozumiałem Twój przypadek użycia), oto inne możliwe rozwiązanie.


Ja osobiście miałem taką samą potrzebę podczas tworzenia gry wideo klient-serwer z Javą. W moim przypadku każdy klient miał kopie komponenty przechowywane na serwerze i problem polegał na tym, że klient musiał zmodyfikować obiekt serwera.

Przejście obiektu przez internet oznaczało, że klient i tak miał różne instancje tego obiektu. Aby dopasować tę "skopiowaną" instancję do oryginalnej, zdecydowałem się użyć Java uuid.

Stworzyłem więc abstrakcyjną klasę UniqueItem, która automatycznie nadaje losowy unikalny identyfikator każdej instancji jej podklas.

Ten UUID jest współdzielony pomiędzy Klientem a instancją serwera, więc w ten sposób można łatwo dopasować je po prostu za pomocą mapy.

Jednak bezpośrednie użycie mapy w podobnej bazie danych było nadal nieeleganckie. Ktoś mógłby argumentować, że Używanie mapy może być bardziej skomplikowane do obsługi i obsługi.

Z tych powodów zaimplementowałem bibliotekę MagicSet, która sprawia, że korzystanie z mapy jest "przejrzyste" dla programisty.

Https://github.com/ricpacca/magicset


Jak oryginalna Java HashSet, MagicHashSet (która jest jedną z implementacji MagicSet udostępnianych w bibliotece) używa zapasowej Hashmapy, ale zamiast elementów jako kluczy i wartości obojętnych jako wartości, używa UUID elementu jako klucza i samego elementu jako wartości. Nie powoduje to nadmiernego zużycia pamięci w porównaniu do normalnego HashSet.

Co więcej, MagicSet może być używany dokładnie jako zestaw, ale z kilkoma innymi metodami zapewniającymi dodatkowe funkcje, takimi jak getFromId(), popFromId (), removeFromId (), itd.

Jedynym wymogiem jest to, że każdy element, który chcesz przechowywać w MagicSet, musi rozszerzyć klasę abstrakcyjną UniqueItem.


Oto przykład kodu, wyobrażający sobie, aby pobrać oryginalną instancję miasta z MagicSet, biorąc pod uwagę inną instancję tego miasta z tym samym UUID (lub nawet tylko jego UUID).

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}
 9
Author: ricpacca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-23 17:18:17

Z Javą 8 możesz zrobić:

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();
Ale uważaj .get() rzuca wyjątek NoSuchElementException, lub można manipulować opcjonalnym elementem.
 7
Author: daniele,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-20 16:02:09
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

Jeśli zrobisz tylko jedno get to nie będzie bardzo wydajne, ponieważ będziesz pętlą na wszystkich elementach, ale podczas wykonywania wielu pobrań na dużym zestawie zauważysz różnicę.

 5
Author: Xymon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-10 09:23:45
 1
Author: nurxyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-02 12:25:59

Wiem, że to było pytanie i odpowiedź dawno temu, jednak jeśli ktoś jest zainteresowany, oto moje rozwiązanie-Klasa custom set wspierana przez HashMap:

Http://pastebin.com/Qv6S91n9

Możesz łatwo zaimplementować wszystkie inne metody Set.

 1
Author: Lukas Z.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-10 22:15:48

Dlaczego:

Wydaje się, że Set odgrywa użyteczną rolę w dostarczaniu środków do porównania. Nie jest przeznaczony do przechowywania duplikatów elementów.

Z powodu tej intencji/projektu, jeśli ktoś miałby uzyskać() odniesienie do przechowywanego obiektu, a następnie go zmutować, jest możliwe, że intencje projektowe Set mogłyby zostać udaremnione i spowodować nieoczekiwane zachowanie.

Z JavaDocs

Należy zachować szczególną ostrożność, jeśli zmienne obiekty są używane jako elementy zestawu. Zachowanie zbioru nie jest określone, jeśli wartość obiektu jest zmieniana w sposób wpływający na porównania równości, podczas gdy obiekt jest elementem w zbiorze.

Jak:

Teraz, gdy strumienie zostały wprowadzone, można wykonać następujące czynności

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
 1
Author: Jason M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-22 20:14:22

Jeśli chcesz nth Element z HashSet, możesz przejść z poniższym rozwiązaniem, tutaj dodałem obiekt ModelClass w HashSet.

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}
 1
Author: Hardik Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-20 16:01:38

Ponieważ każda konkretna implementacja zestawu może być lub nie być dostępem losowym.

Zawsze możesz uzyskać iterator i przejść przez zbiór, używając metody next() iteratorów, aby zwrócić żądany wynik po znalezieniu równego elementu. Działa to niezależnie od wdrożenia. Jeśli implementacja nie jest dostępem losowym (zob. zbiór wspierany linked-list), metoda get(E element) w interfejsie byłaby zwodnicza, ponieważ musiałaby iterować kolekcję do znajdowanie elementu do powrotu, a get(E element) wydaje się sugerować, że byłoby to konieczne, aby zbiór mógł przeskoczyć bezpośrednio do elementu, aby uzyskać.

contains() może, ale nie musi robić tego samego, oczywiście, w zależności od implementacji, ale nazwa nie wydaje się nadawać się do tego samego rodzaju nieporozumień.

 0
Author: Tom Tresansky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-02 12:35:27

Szybka metoda pomocnicza, która może rozwiązać tę sytuację:

<T> T onlyItem(Collection<T> items) {
    if (items.size() != 1)
        throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());

    return items.iterator().next();
}
 0
Author: Chris Willmore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-19 09:50:16

/ Align = "left" / ! Jeśli używasz Guava szybki sposób, aby przekonwertować go na mapę jest:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
 0
Author: Heisenberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-14 11:35:32

Tak, użyj HashMap ... ale w sposób specjalistyczny: pułapka, którą przewiduję, próbując użyć HashMap jako pseudo-Set, jest możliwym pomyleniem "rzeczywistych" elementów Map/Set i elementów "kandydujących", tj. elementów używanych do sprawdzenia, czy element equal jest już obecny. To nie jest niezawodne, ale odsuwa Cię od pułapki: {]}

class SelfMappingHashMap<V> extends HashMap<V, V>{
    @Override
    public String toString(){
        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
        return keySet().toString();
    }

    @Override
    public V get( Object key ){
        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
    }

    @Override
    public V put( V key, V value ){
       // thorny issue here: if you were indavertently to `put`
       // a "candidate instance" with the element already in the `Map/Set`: 
       // these will obviously be considered equivalent 
       assert key.equals( value );
       return super.put( key, value );
    }

    public V tryToGetRealFromCandidate( V key ){
        return super.get(key);
    }
}

Więc zrób to:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}
Ale... teraz chcesz, aby candidate w jakiś sposób samodestrukcja, chyba że programista faktycznie natychmiast umieszcza go w Map/Set... chcesz contains "splamić" candidate, aby każde użycie go, chyba że połączy się z Map, sprawiło, że stał się "anathema". Być może mógłbyś sprawić, że SomeClass zaimplementuje nowy interfejs Taintable.

Bardziej satysfakcjonującym rozwiązaniem jest GettableSet, jak poniżej. Jednak, aby to zadziałało, musisz albo być odpowiedzialny za projekt SomeClass, aby wszystkie konstruktory były niewidoczne (or... zdolnych i chętnych do zaprojektowania i wykorzystania klasy owijarki dla it):

public interface NoVisibleConstructor {
    // again, this is a "nudge" technique, in the sense that there is no known method of 
    // making an interface enforce "no visible constructor" in its implementing classes 
    // - of course when Java finally implements full multiple inheritance some reflection 
    // technique might be used...
    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};

public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
    V getGenuineFromImpostor( V impostor ); // see below for naming
}

Realizacja:

public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
    private Map<V, V> map = new HashMap<V, V>();

    @Override
    public V getGenuineFromImpostor(V impostor ) {
        return map.get( impostor );
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey( o );
    }

    @Override
    public boolean add(V e) {
        assert e != null;
        V result = map.put( e,  e );
        return result != null;
    }

    @Override
    public boolean remove(Object o) {
        V result = map.remove( o );
        return result != null;
    }

    @Override
    public boolean addAll(Collection<? extends V> c) {
        // for example:
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        map.clear();
    }

    // implement the other methods from Set ...
}

Twoje NoVisibleConstructor klasy wtedy wyglądają tak:

class SomeClass implements NoVisibleConstructor {

    private SomeClass( Object param1, Object param2 ){
        // ...
    }

    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
        SomeClass candidate = new SomeClass( param1, param2 );
        if (gettableSet.contains(candidate)) {
            // obviously this then means that the candidate "fails" (or is revealed
            // to be an "impostor" if you will).  Return the existing element:
            return gettableSet.getGenuineFromImpostor(candidate);
        }
        gettableSet.add( candidate );
        return candidate;
    }

    @Override
    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
       // more elegant implementation-hiding: see below
    }
}

PS jeden problem techniczny z taką klasą NoVisibleConstructor: można sprzeciwić się, że taka klasa jest z natury final, co może być niepożądane. W rzeczywistości zawsze można dodać atrapę bez parametru protected konstruktor:

protected SomeClass(){
    throw new UnsupportedOperationException();
}

... co przynajmniej pozwoli na kompilację podklasy. Trzeba wtedy zastanowić się, czy trzeba włączyć inną metodę fabryczną getOrCreate() w podklasa.

Ostatni krok jest abstrakcyjną klasą bazową (NB "element" dla listy, "member" dla zestawu), jak to dla Twoich członków zestawu (jeśli to możliwe-Ponownie, możliwość użycia klasy wrapper , gdzie klasa nie jest pod twoją kontrolą, lub ma już klasę bazową, itd.), dla maksymalnej implementacji-ukrywanie:

public abstract class AbstractSetMember implements NoVisibleConstructor {
    @Override
    public NoVisibleConstructor
            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
        AbstractSetMember member = this;
        @SuppressWarnings("unchecked") // unavoidable!
        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
        if (gettableSet.contains( member )) {
            member = set.getGenuineFromImpostor( member );
            cleanUpAfterFindingGenuine( set );
        } else {
            addNewToSet( set );
        }
        return member;
    }

    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}

... użycie jest dość oczywiste (wewnątrz Twojej metody fabrycznej SomeClass'S static):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
 0
Author: mike rodent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-03 19:41:14

Możesz użyć klasy Iterator

import java.util.Iterator;
import java.util.HashSet;

public class MyClass {
 public static void main(String[ ] args) {
 HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");

Iterator<String> it = animals.iterator();
while(it.hasNext()) {
  String value = it.next();
  System.out.println(value);   
 }
 }
}
 0
Author: chiha asma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 04:58:43

Podążanie może być podejściem

   SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
   Set<String> main = se_get.getStringSet("mydata",null);
   for(int jk = 0 ; jk < main.size();jk++)
   {
      Log.i("data",String.valueOf(main.toArray()[jk]));
   }
 0
Author: Vikrant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-09 07:09:20

Spróbuj użyć tablicy:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
 0
Author: canni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-14 05:32:42

Jeśli spojrzysz na kilka pierwszych linijek implementacji java.util.HashSet zobaczysz:

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

Tak więc HashSet używa HashMap wewnętrznie, co oznacza, że jeśli użyjesz HashMap bezpośrednio i użyjesz tej samej wartości co klucz i wartość, uzyskasz pożądany efekt i zaoszczędzisz sobie trochę pamięci.

 0
Author: rghome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-24 15:53:39