2015-06-29 12 views
9

Angenommen, ich habe eine Liste mit Elementen (34, 11, 98, 56, 43).Java 8: Finde den Index des Minimalwerts aus einer Liste

Mit Java 8-Streams, wie finde ich den Index des minimalen Elements der Liste (z. B. 1 in diesem Fall)?

Ich weiß, dass dies leicht in Java unter Verwendung list.indexOf(Collections.min(list)) getan werden kann. Ich betrachte jedoch eine Scala-ähnliche Lösung, in der wir einfach List(34, 11, 98, 56, 43).zipWithIndex.min._2 sagen können, um den Index des Mindestwerts zu erhalten.

Gibt es etwas, was mit Streams oder Lambda-Ausdrücken (sagen wir Java 8-spezifische Features) getan werden kann, um das gleiche Ergebnis zu erzielen.

Hinweis: Dies ist nur für Lernzwecke. Ich habe kein Problem mit Collections Utility-Methoden.

+0

mögliche Duplikate von [Wie erhalten Sie den Index und den Maximalwert eines Arrays auf einmal?] (Http://stackoverflow.com/questions/30730861/how-to-get-the-index-and-max-) Value-of-a-array-in-one-shot) – CKing

Antwort

14
import static java.util.Comparator.comparingInt; 

int minIndex = IntStream.range(0,list.size()).boxed() 
      .min(comparingInt(list::get)) 
      .get(); // or throw if empty list 

Wie @TagirValeev erwähnt in his answer, können Sie Boxen überprüfen vermeiden, indem IntStream#reduce statt Stream#min, aber auf Kosten der Verschleierung der Absicht mit:

int minIdx = IntStream.range(0,list.size()) 
      .reduce((i,j) -> list.get(i) > list.get(j) ? j : i) 
      .getAsInt(); // or throw 
+1

Ja das ist eine nette +1. Ich habe mich auf den 'zipWithIndex'-Fall konzentriert, aber dies könnte der Idiom-Weg sein, dies zu lösen. Ich würde stattdessen 'orElse (-1)' stattdessen verwenden. –

2

Man könnte es wie folgt tun:

int indexMin = IntStream.range(0, list.size()) 
       .mapToObj(i -> new SimpleEntry<>(i, list.get(i))) 
       .min(comparingInt(SimpleEntry::getValue)) 
       .map(SimpleEntry::getKey) 
       .orElse(-1); 

Wenn die Liste ein Direktzugriffsliste ist, get ist ein konstanter Zeitbetrieb. Der API fehlt eine Standard Tuple Klasse, daher habe ich die SimpleEntry aus der AbstractMap Klasse als Ersatz verwendet.

So generiert IntStream.range einen Strom von Indizes aus der Liste, aus der Sie jeden Index seinem entsprechenden Wert zuordnen. Dann erhalten Sie das minimale Element, indem Sie einen Komparator für die Werte (die in der Liste) bereitstellen. Von dort aus ordnen Sie die Optional<SimpleEntry<Integer, Integer>> einer Optional<Integer> zu, von der Sie den Index erhalten (oder -1, wenn das optionale leer ist).

Nebenbei würde ich wahrscheinlich eine einfache for-Schleife verwenden, um den Index des Mindestwerts zu erhalten, da Ihre Kombination von min/indexOf 2 über die Liste passiert.

Sie auch interessieren könnten Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

+0

Danke @Alexis, scheint komplizierter zu sein als die einfache Verwendung von 'Collections' oder 'Scalas Implementierung. – Mubin

1

Hier sind zwei mögliche Lösungen unter Verwendung meiner StreamEx-Bibliothek:

int idx = IntStreamEx.ofIndices(list).minBy(list::get).getAsInt(); 

Oder:

int idx = EntryStream.of(list).minBy(Entry::getValue).get().getKey(); 

Die zweite Lösung intern ist sehr nah an einem von @AlexisC vorgeschlagen. Die erste ist wahrscheinlich die schnellste, da sie nicht boxt (internally ist es eine Operation zu reduzieren).

Ohne die Verwendung von Drittanbieter-Code @ Misha Antwort sieht am besten für mich.

2

Da dies zu Lernzwecken ist, versuchen wir eine Lösung zu finden, die nicht nur irgendwie einen Stream verwendet, sondern tatsächlich im Stream unserer Liste arbeitet. Wir wollen auch keinen zufälligen Zugriff annehmen.

Es gibt also zwei Möglichkeiten, ein nicht-triviales Ergebnis aus einem Stream zu erhalten: collect und reduce.Here ist eine Lösung, die einen Kollektor verwendet:

class Minimum { 
    int index = -1; 
    int range = 0; 
    int value; 

    public void accept(int value) { 
     if (range == 0 || value < this.value) { 
      index = range; 
      this.value = value; 
     } 
     range++; 
    } 

    public Minimum combine(Minimum other) { 
     if (value > other.value) { 
      index = range + other.index; 
      value = other.value; 
     } 
     range += other.range; 
     return this; 
    } 

    public int getIndex() { 
     return index; 
    } 
} 

static Collector<Integer, Minimum, Integer> MIN_INDEX = new Collector<Integer, Minimum, Integer>() { 
     @Override 
     public Supplier<Minimum> supplier() { 
      return Minimum::new; 
     } 
     @Override 
     public BiConsumer<Minimum, Integer> accumulator() { 
      return Minimum::accept; 
     } 
     @Override 
     public BinaryOperator<Minimum> combiner() { 
      return Minimum::combine; 
     } 
     @Override 
     public Function<Minimum, Integer> finisher() { 
      return Minimum::getIndex; 
     } 
     @Override 
     public Set<Collector.Characteristics> characteristics() { 
      return Collections.emptySet(); 
     } 
    }; 

Schreiben einen Sammlers schafft eine lästige Menge an Code, aber es kann leicht mit jedem vergleichbaren Wert unterstützen verallgemeinert werden. Außerdem sieht der Sammler Aufruf sehr idiomatisch:

List<Integer> list = Arrays.asList(4,3,7,1,5,2,9); 
int minIndex = list.stream().collect(MIN_INDEX); 

Wenn wir die accept und combine Methoden ändern, um immer eine neue Minimum Instanz zurückgeben (. Dh wenn wir Minimum unveränderlich machen), können wir auch verwenden reduce:

int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex(); 

Ich fühle ein großes Potenzial für die Parallelisierung in diesem.

+0

Ziemlich interessante Lösung. Zuerst dachte ich, es wird nicht für parallele Streams funktionieren, aber es scheint so. Beachten Sie, dass Sie 'Collector.of (Minimum :: neu, Minimum :: akzeptieren, Minimum :: kombinieren, Minimum :: getIndex)' verwenden können, anstatt eine anonyme Klasse zu definieren. –

+1

Dieser Ansatz hat den Vorteil, dass die Liste nicht wahlfrei verwendet werden muss. Beachten Sie, dass die "combine" -Methode den Fall, dass "andere" leer ist, korrekt behandeln sollte. "Collector" javadoc fordert dies speziell für die Parallelfreundlichkeit als "Identity Constraint". Es sagt nichts darüber aus, den Fall zu behandeln, dass "dieses" leer und "anders" nicht leer ist, aber es scheint auch ratsam, damit umzugehen. – Misha

+0

@Misha, ja, es hat ein paar kleinere Probleme, aber ich mag die Idee. Btw es funktioniert ziemlich schnell, bei einigen Tests übertrifft es sogar meine 'IntStreamEx.minBy'. Ich [fest] (https://github.com/amaembo/streamex/commit/86703a8b787e4769766c99112db5ba42d75ac3c4) eine feste Version, wahrscheinlich wird es in meinem lib enthalten. –

Verwandte Themen