Guter Weg, um * irgendeinen * Wert von einem Java-Set zu erhalten?

71

Bei einem einfachen Set<T>, was ist ein guter Weg (schnell, wenige Zeilen Code) zu bekommen jeden Wert aus dem Set?

Mit a Listist es einfach:

List<T> things = ...;
return things.get(0);

Aber mit a Setgibt es keine .get(...)Methode, weil Sets nicht geordnet sind.

Jacob Marmor
quelle

Antworten:

101

A Set<T>ist ein Iterable<T>, also funktioniert das Iterieren zum ersten Element:

Set<T> things = ...;
return things.iterator().next();

Guave hat eine Methode , um dies zu tun, obwohl das obige Snippet wahrscheinlich besser ist .

Jacob Marmor
quelle
1
Wenn Sie keine Standardeinstellung wünschen, möchten wir, dass Sie diese verwenden iterator().next(). (Deshalb haben wir keine getFirst(Iterable<E>), nur eine getFirst(Iterable<E>, E default).)
Louis Wasserman
2
@GanGnaMStYleOverFlowErroR: Ja. blog.stackoverflow.com/2011/07/…
SLaks
1
...Was?! Nein, ist es nicht. Für jede Setmir bekannte Implementierung ist dies höchstens O (log n).
Louis Wasserman
1
Richtig, aber in der Praxis äußerst selten. (Auch leicht mit einem zu adressieren LinkedHashSet.)
Louis Wasserman
2
(Ich behaupte auch, dass es keine schnellere Lösung gibt.)
Louis Wasserman
12

Da Streams vorhanden sind, können Sie dies auch so tun, aber Sie müssen die Klasse verwenden java.util.Optional. Optionalist eine Wrapper-Klasse für ein Element oder explizit kein Element (unter Vermeidung des Nullzeigers).

//returns an Optional.
Optional <T> optT = set.stream().findAny();

//Optional.isPresent() yields false, if set was empty, avoiding NullpointerException
if(optT.isPresent()){
    //Optional.get() returns the actual element
    return optT.get();
}

Bearbeiten: Wie ich es selbst Optionalziemlich oft benutze : Es gibt eine Möglichkeit, auf das Element zuzugreifen oder eine Standardeinstellung zu erhalten, falls es nicht vorhanden ist:
optT.orElse(other)Gibt entweder das Element zurück oder, falls nicht vorhanden other,. otherkann nullübrigens sein.

Dániel Somogyi
quelle
1
Ich frage mich, ob das schneller oder langsamer ist als set.iterator().next();.
Gustavo
set.iterator (). next () löst eine NoSuchElementException aus, falls die Sammlung leer ist. Die Verwendung von findAny of Stream ist sicherer, da Sie eine leere Option erhalten. Optional, wenn die Sammlung leer ist. Ich würde mich in Bezug auf die Sicherheit irren, wenn die Leistung nicht wirklich wichtig ist. Kommt wirklich darauf an, was du brauchst.
lost_trekkie
3

Das Abrufen eines Elements aus einem Satz oder einer Sammlung mag als ungewöhnliche Anforderung erscheinen - wenn auch nicht willkürlich oder vielseitig -, ist jedoch durchaus üblich, wenn beispielsweise Statistiken für Schlüssel- oder Werteobjekte in einer Karte berechnet und min / initialisiert werden müssen. Maximalwerte . Das beliebige Element aus einem Set / einer Sammlung (von Map.keySet () oder Map.values ​​() zurückgegeben) wird für diese Initialisierung verwendet, bevor die Min / Max-Werte jeweils aktualisiert werden Element .

Welche Möglichkeiten hat man also, wenn man mit diesem Problem konfrontiert wird und gleichzeitig versucht, Speicher und Ausführungszeit klein und Code klar zu halten?

Oft erhalten Sie das Übliche: " Set in ArrayList konvertieren und das erste Element erhalten ". Großartig! Ein weiteres Array mit Millionen von Elementen und zusätzlichen Verarbeitungszyklen zum Abrufen von Objekten aus Set, Zuweisen eines Arrays und Auffüllen :

HashMap<K,V> map;
List<K> list = new ArrayList<V>(map.keySet()); // min/max of keys
min = max = list.get(0).some_property(); // initialisation step
for(i=list.size();i-->1;){
 if( min > list.get(i).some_property() ){ ... }
 ...
}

Oder man kann eine Schleife mit einem Iterator verwenden, wobei ein Flag verwendet wird, um anzuzeigen, dass min / max initialisiert werden muss, und eine bedingte Anweisung, um zu überprüfen, ob dieses Flag für alle Iterationen in der Schleife gesetzt ist. Dies impliziert eine Menge bedingter Überprüfungen.

boolean flag = true;
Iterator it = map.keySet().iterator();
while( it.hasNext() ){
  if( flag ){
    // initialisation step
    min = max = it.next().some_property();
    flag = false;
  } else {
    if( min > list.get(i).some_property() ){ min = list.get(i).some_property() }
  ...
  }
}

Oder führen Sie die Initialisierung außerhalb der Schleife durch:

HashMap<K,V> map;
Iterator it = map.keySet().iterator();
K akey;
if( it.hasNext() ){
  // initialisation step:
  akey = it.next();
  min = max = akey.value();
  do {
    if( min > list.get(i).some_property() ){ min = akey.some_property() }
  } while( it.hasNext() && ((akey=it.next())!=null) );
}

Aber ist es das ganze Manöver im Namen des Programmierers (und des Einrichtens des Iterators im Auftrag der JVM) wirklich wert, wann immer Min / Max benötigt werden?

Der Vorschlag eines javally-korrekten alten Sports könnte lauten: "Wickeln Sie Ihre Karte in eine Klasse ein, die die Min- und Max-Werte beim Setzen oder Löschen verfolgt!"

Es gibt eine andere Situation , in der meiner Erfahrung nach nur ein beliebiger Gegenstand aus einer Karte benötigt wird. In diesem Fall enthält die Karte Objekte, die eine gemeinsame Eigenschaft haben - für alle in dieser Karte gleich - und Sie müssen diese Eigenschaft lesen . Angenommen, es gibt eine Karte mit Aufbewahrungsfächern desselben Histogramms mit derselben Anzahl von Dimensionen. Bei einer solchen Karte müssen Sie möglicherweise die Anzahl der Dimensionen eines beliebigen Histobins in der Karte kennen, um beispielsweise einen weiteren Histobin mit denselben Dimensionen erstellen zu können. Muss ich einen Iterator erneut einrichten und entsorgen, nachdem ich next () nur einmal aufgerufen habe? Ich werde den Vorschlag der javally-korrekten Person zu dieser Situation überspringen.

Und wenn all die Mühe , die in immer jedes Element verursacht unbedeutend Speicher und CPU - Zyklen zu erhöhen, was ist dann mit all dem Code zu schreiben hat gerade das schwer zu bekommen zu bekommen jedes Element.

Wir brauchen das beliebige Element. Gib es uns!

bliako
quelle