Een element uit een set halen

Waarom biedt Setgeen bewerking om een ​​element te krijgen dat gelijk is aan een ander element?

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

Ik kan vragen of de Seteen element bevat dat gelijk is aan bar, dus waarom kan ik dat element niet krijgen? 🙁

Ter verduidelijking: de methode equalswordt overschreven, maar controleert slechts één van de velden, niet alle. Dus twee foo-objecten die als gelijk worden beschouwd, kunnen verschillende waarden hebben, daarom kan ik niet zomaar foogebruiken.


Antwoord 1, autoriteit 100%

Het heeft geen zin om het element te krijgen als het gelijk is. Een Mapis beter geschikt voor dit gebruik.


Als je het element nog steeds wilt vinden, heb je geen andere keuze dan de iterator te gebruiken:

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

Antwoord 2, autoriteit 99%

Om de precieze vraag “Waarombiedt Setgeen bewerking te geven om een ​​element te krijgen dat gelijk is aan een ander element?”, zou het antwoord zijn: omdat de ontwerpers van het collectiekader waren niet erg toekomstgericht. Ze anticipeerden niet op uw zeer legitieme gebruiksscenario, probeerden naïef om “de wiskundige set-abstractie te modelleren” (van de javadoc) en vergaten gewoon de nuttige get()-methode toe te voegen.

Nu op de impliciete vraag “hoekrijg je het element dan”: ik denk dat de beste oplossing is om een ​​Map<E,E>te gebruiken in plaats van een Set<E>, om de elementen aan zichzelf toe te wijzen. Op die manier kun je efficiënteen element uit de “set” halen, omdat de methode get() van de Maphet element zal vinden met behulp van een efficiënte hashtabel of boom algoritme. Als je wilt, kun je je eigen implementatie van Setschrijven die de aanvullende get()-methode biedt, die de Mapomvat.

De volgende antwoorden zijn naar mijn mening slecht of fout:

“Je hoeft het element niet te krijgen, omdat je al een gelijk object hebt”: de bewering is fout, zoals je al liet zien in de vraag. Twee objecten die gelijk zijn, kunnen nog steeds een verschillende status hebben die niet relevant is voor de objectgelijkheid. Het doel is om toegang te krijgen tot deze status van het element in de Set, niet de status van het object dat als “query” wordt gebruikt.

“Je hebt geen andere keuze dan de iterator te gebruiken”: dat is een lineaire zoekopdracht in een collectie die totaal inefficiënt is voor grote sets (ironisch genoeg is de Setintern georganiseerd als hash-map of boom die efficiënt kan worden opgevraagd). Doe het niet! Ik heb ernstige prestatieproblemen gezien in real-life systemen door die benadering te gebruiken. Naar mijn mening is het vreselijke aan de ontbrekende get()-methode niet zozeer dat het een beetje omslachtig is om er omheen te werken, maar dat de meeste programmeurs de lineaire zoekbenadering zullen gebruiken zonder na te denken over de implicaties .


Antwoord 3, autoriteit 20%

Als je een gelijk object hebt, waarom heb je dan dat van de set nodig? Als het alleen “gelijk” is met een sleutel, zou een Mapeen betere keuze zijn.

Hoe dan ook, het volgende zal het doen:

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

Met Java 8 kan dit een oneliner worden:

return all.stream().filter(sample::equals).findAny().orElse(null);

Antwoord 4, autoriteit 13%

Standaardset in Java is helaas niet ontworpen om een ​​”get”-bewerking te bieden, zoals jschreinernauwkeurig is uitgelegd.

De oplossingen voor het gebruik van een iterator om het betreffende element te vinden (aanbevolen door dacwe) of om het element te verwijderen en voeg het opnieuw toe met bijgewerkte waarden (aanbevolen door KyleM), zou kunnen werken, maar kan erg inefficiënt zijn.

De implementatie van gelijken negeren zodat niet-gelijke objecten “gelijk” zijn, zoals correct vermeld door David Ogren, kan gemakkelijk onderhoudsproblemen veroorzaken.

En het gebruik van een kaart als expliciete vervanging (zoals door velen wordt gesuggereerd), imho, maakt de code minder elegant.

Als het doel is om toegang te krijgen tot de originele instantie van het element in de set (hoop dat ik je gebruiksvoorbeeld goed heb begrepen), is hier een andere mogelijke oplossing.


Persoonlijk had ik dezelfde behoefte bij het ontwikkelen van een client-server-videogame met Java. In mijn geval had elke client kopieën van de componenten die op de server waren opgeslagen en het probleem was wanneer een client een object van de server moest wijzigen.

Het doorgeven van een object via internet betekende dat de client toch verschillende instanties van dat object had. Om deze “gekopieerde” instantie te matchen met de originele, heb ik besloten om Java UUID’s te gebruiken.

Dus ik heb een abstracte klasse UniqueItem gemaakt, die automatisch een willekeurig uniek ID geeft aan elke instantie van zijn subklassen.

Deze UUID wordt gedeeld tussen de client en de serverinstantie, dus op deze manier kan het gemakkelijk zijn om ze te matchen door simpelweg een kaart te gebruiken.

Het was echter nog steeds onelegant om een ​​kaart rechtstreeks te gebruiken in een vergelijkbare usecase. Iemand zou kunnen beweren dat het gebruik van een kaart misschien ingewikkelder is om te onderhouden en te hanteren.

Om deze redenen heb ik een bibliotheek met de naam MagicSet geïmplementeerd, die het gebruik van een kaart “transparant” maakt voor de ontwikkelaar.

https://github.com/ricpacca/magicset


Net als de originele Java HashSet, gebruikt een MagicHashSet (een van de implementaties van MagicSet in de bibliotheek) een backing HashMap, maar in plaats van elementen als sleutels en een dummy waarde als waarden, gebruikt het de UUID van de element als sleutel en het element zelf als waarde. Dit veroorzaakt geen overhead in het geheugengebruik in vergelijking met een normale HashSet.

Bovendien kan een MagicSet precies als een Set worden gebruikt, maar met enkele meer methoden die extra functionaliteiten bieden, zoals getFromId(), popFromId(), removeFromId(), enz.

De enige vereiste om het te gebruiken is dat elk element dat u in een MagicSet wilt opslaan, de abstracte klasse UniqueItem moet uitbreiden.


Hier is een codevoorbeeld, waarbij je je voorstelt om de oorspronkelijke instantie van een stad uit een MagicSet op te halen, gegeven een andere instantie van die stad met dezelfde UUID (of zelfs alleen de 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
}

Antwoord 5, autoriteit 10%

Als uw set in feite een NavigableSet<Foo>is (zoals een TreeSet), en Foo implements Comparable<Foo>implementeert, je kunt gebruiken

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

(Met dank aan de opmerking van @eliran-malka voor de hint.)


Antwoord 6, autoriteit 9%

Converteer set naar lijst en gebruik vervolgens de getmethode van lijst

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);

Antwoord 7, autoriteit 9%

Met Java 8 kunt u het volgende doen:

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

Maar wees voorzichtig, .get() gooit een NoSuchElementException, of je kunt een optioneel item manipuleren.


Antwoord 8, autoriteit 5%

Waarom:

Het lijkt erop dat Set een nuttige rol speelt bij het bieden van een vergelijkingsmiddel. Het is ontworpen om geen dubbele elementen op te slaan.

Vanwege deze intentie/ontwerp, als men een verwijzing naar het opgeslagen object zou krijgen() en het vervolgens muteren, is het mogelijk dat de ontwerpintenties van Set worden gedwarsboomd en onverwacht gedrag kunnen veroorzaken.

Van de JavaDocs

Er moet grote zorg worden besteed aan het gebruik van veranderlijke objecten als set-elementen. Het gedrag van een set wordt niet gespecificeerd als de waarde van een object wordt gewijzigd op een manier die gelijk is aan vergelijkingen, terwijl het object een element in de set is.

Hoe:

Nu Streams zijn geïntroduceerd kan men het volgende doen

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();

Antwoord 9, autoriteit 4%

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

Als je maar één get doet, zal dit niet erg presterend zijn omdat je al je elementen doorloopt, maar als je meerdere retrieves op een grote set uitvoert, zul je het verschil merken.


Antwoord 10

u kunt de Iterator-klasse gebruiken

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

Antwoord 11

Ik weet het, dit is al lang geleden gevraagd en beantwoord, maar als iemand geïnteresseerd is, hier is mijn oplossing – aangepaste set-klasse ondersteund door HashMap:

http://pastebin.com/Qv6S91n9

U kunt alle andere Set-methoden eenvoudig implementeren.


Antwoord 12

Ik ben daar geweest!! Als je Guava gebruikt, is een snelle manier om het naar een kaart te converteren:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);

Antwoord 13

Als je het n-de element van HashSet wilt, kun je de onderstaande oplossing gebruiken,
hier heb ik een object van ModelClass in HashSet toegevoegd.

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

Antwoord 14

Als je naar de eerste paar regels van de implementatie van java.util.HashSetkijkt, zie je:

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

Dus HashSetgebruikt HashMaphoe dan ook intern, wat betekent dat als je gewoon een HashMaprechtstreeks gebruikt en dezelfde waarde gebruikt als de sleutel en de waarde krijgt u het gewenste effect en bespaart u wat geheugen.


Antwoord 15

het lijkt erop dat het juiste object om te gebruiken de Internervan guave :

Biedt gelijkwaardig gedrag aan String.intern() voor andere onveranderlijke
types. Algemene implementaties zijn beschikbaar via de Interners
klas.

Het heeft ook een paar zeer interessante hefbomen, zoals concurrencyLevel, of het type referenties dat wordt gebruikt (het is misschien de moeite waard om op te merken dat het geen SoftInterner biedt die ik als nuttiger zou kunnen beschouwen dan een WeakInterner).


Antwoord 16

Omdat een bepaalde implementatie van Set al dan niet willekeurige toegangkan zijn.

Je kunt altijd een iterator krijgen en doorloop de Set, met behulp van de next()methode van de iterators om het gewenste resultaat te retourneren zodra je het gelijke element hebt gevonden. Dit werkt ongeacht de uitvoering. Als de implementatie GEEN willekeurige toegang is (zie een set met gekoppelde lijsten), zou een get(E element)-methode in de interface bedrieglijk zijn, omdat het de verzameling zou moeten herhalen om het element te vinden om terug te keren, en een get(E element)lijkt te impliceren dat dit nodig zou zijn, dat de Set rechtstreeks naar het te krijgen element zou kunnen springen.

contains()kan natuurlijk wel of niet hetzelfde moeten doen, afhankelijk van de implementatie, maar de naam lijkt zich niet voor dezelfde soort misverstanden te lenen.


Antwoord 17

Het contract van de hashcode maakt duidelijk dat:

“Als twee objecten gelijk zijn volgens de Object-methode, moet het aanroepen van de hashCode-methode op elk van de twee objecten hetzelfde integer resultaat opleveren.”

Dus uw aanname:

“Ter verduidelijking, de equals-methode wordt overschreven, maar controleert slechts één van
de velden, niet allemaal. Dus twee Foo-objecten die als gelijk worden beschouwd, kunnen
eigenlijk verschillende waarden hebben, daarom kan ik niet gewoon foo gebruiken.”

is verkeerd en u verbreekt het contract. Als we naar de “bevat”-methode van de Set-interface kijken, zien we dat:

boolean bevat(Object o);
Retourneert true als deze set het opgegeven element bevat. Meer
formeel, retourneert true als en alleen als deze set een element bevat
“e” zodat o==null ? e==null : o.equals(e)

Om te bereiken wat je wilt, kun je een kaart gebruiken waarin je de sleutel definieert en je element opslaat met de sleutel die bepaalt hoe objecten van elkaar verschillen of gelijk zijn.


Antwoord 18

Dit is wat je kunt doen als je een NavigableSethebt (bijvoorbeeld een TreeSet):

public static <E> E get(NavigableSet<E> set, E key) {
    return set.tailSet(key, true).floor(key);
}

De dingen zijn iets lastiger voor HashSeten zijn nakomelingen zoals LinkedHashSet:

import java.util.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
    private static final Field mapField;
    private static final Method hashMethod;
    private static final Method getNodeMethod;
    private static final Field keyField;
    static {
        try {
            mapField = HashSet.class.getDeclaredField("map");
            mapField.setAccessible(true);
            hashMethod = HashMap.class.getDeclaredMethod("hash", Object.class);
            hashMethod.setAccessible(true);
            getNodeMethod = HashMap.class.getDeclaredMethod("getNode",
                    Integer.TYPE, Object.class);
            getNodeMethod.setAccessible(true);
            keyField = Class.forName("java.util.HashMap$Node").getDeclaredField("key");
            keyField.setAccessible(true);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }
    public static <E> E get(HashSet<E> set, E key) {
        try {
            Object map = mapField.get(set);
            Object hash = hashMethod.invoke(null, key);
            Object node = getNodeMethod.invoke(map, hash, key);
            if (node == null)
                return null;
            @SuppressWarnings("unchecked")
            E result = (E)keyField.get(node);
            return result;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }
    public static <E> E get(NavigableSet<E> set, E key) {
        return set.tailSet(key, true).floor(key);
    }
    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>();
//      HashSet<Integer> s = new LinkedHashSet<>();
//      TreeSet<Integer> s = new TreeSet<>();
        for (int i = 0; i < 100_000; i++)
            s.add(i);
        Integer key = java.awt.event.KeyEvent.VK_FIND;
        Integer hidden = get(s, key);
        System.out.println(key);
        System.out.println(hidden);
        System.out.println(key.equals(hidden));
        System.out.println(key == hidden);
    }
}

Antwoord 19

Snelle hulpmethode die deze situatie kan verhelpen:

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

Antwoord 20

Probeer een array te gebruiken:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);

Antwoord 21

Volgen kan een benadering zijn

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

Other episodes