2015-06-29 3 views
9

Скажем, у меня есть список с элементами (34, 11, 98, 56, 43).Java 8: Найти указатель минимального значения из списка

Использование Java 8 потоков, как найти индекс минимального элемента списка (например, 1 в этом случае)?

Я знаю, что это можно сделать легко на Java, используя list.indexOf(Collections.min(list)). Тем не менее, я смотрю на решение Scala, где мы можем просто сказать List(34, 11, 98, 56, 43).zipWithIndex.min._2, чтобы получить индекс минимального значения.

Есть ли что-нибудь, что можно сделать с помощью потоков или лямбда-выражений (например, для Java 8), чтобы достичь того же результата.

Примечание: это только для целей обучения. У меня нет проблем с использованием методов утилиты Collections.

+0

Возможный дубликат [Как получить индекс и максимальное значение массива за один снимок?] (Http://stackoverflow.com/questions/30730861/how-to-get-the-index-and-max- value-of-a-array-in-one-shot) – CKing

ответ

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 

Как @TagirValeev упоминает в his answer, вы можете избежать бокс с помощью IntStream#reduce вместо Stream#min, но за счет затеняя намерения:

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

Да, это хороший +1. Я был сосредоточен на деле 'zipWithIndex', но это может быть идиомом для решения этой проблемы. Я бы просто использовал 'orElse (-1)' вместо этого. –

2

Вы можете сделать это следующим образом:

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

Если список случайный список доступа, get постоянная работа времени. API не имеет стандартного кортежа, поэтому я использовал SimpleEntry из класса AbstractMap в качестве замены.

So IntStream.range генерирует поток индексов из списка, из которого вы сопоставляете каждый индекс с его соответствующим значением. Затем вы получаете минимальный элемент, предоставляя компаратор для значений (те, что указаны в списке). Оттуда вы наберете Optional<SimpleEntry<Integer, Integer>> на Optional<Integer>, из которого вы получите индекс (или -1, если необязательный пуст).

В качестве альтернативы, я бы, вероятно, использовал простой цикл, чтобы получить индекс минимального значения, так как ваша комбинация min/indexOf делает 2 перевала по списку.

Вы также можете быть заинтересованы, чтобы проверить Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

+0

Спасибо @Alexis, кажется, сложнее, чем простое использование «Коллекции» или «Реализация Scala». – Mubin

1

Вот два возможных решения, используя мою библиотеку StreamEx:

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

Или:

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

Второе решение внутренне очень близка к предложенной @AlexisC. Первый, пожалуй, самый быстрый, поскольку он не использует бокс (internally это операция уменьшения).

Без использования стороннего кода @ Ответ Миши выглядит лучше всего для меня.

2

Поскольку это для учебных целей, давайте попробуем найти решение, которое не просто использует поток, а фактически работает над потоком нашего списка. Мы также не хотим принимать произвольный доступ.

Итак, есть два способа получить нетривиальный результат из потока: collect и reduce.Here этого решение, которое использует коллектор:

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

Написания коллекторов создает раздражающее количество коды, но она легко может быть обобщена для поддержки любой сравнимой величины. Кроме того, вызов сборщика выглядит очень идиоматических:

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

Если мы изменим accept и combine методы всегда возвращают новый Minimum экземпляр (т. Е, если мы делаем Minimum неизменны), мы можем также использовать reduce:

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

Я ощущаю большой потенциал для распараллеливания в этом.

+0

Довольно интересное решение. Сначала я думал, что это не сработает для параллельных потоков, но, похоже, это будет. Обратите внимание, что вы можете использовать 'Collector.of (Minimum :: new, Minimum :: accept, Minimum :: comb, Minimum :: getIndex)' вместо определения анонимного класса. –

+1

Этот подход имеет то преимущество, что он не требует, чтобы список был произвольным. Обратите внимание, что метод 'comb' должен корректно обрабатывать случай, когда' other' пуст. '' Collector' javadoc специально требует, чтобы для параллельного дружелюбия как «ограничение личности». Он ничего не говорит о том, как обрабатывать случай «этого» пустым, а «другое» не пустым, но разумно также обращаться с этим. – Misha

+0

@ Миша, да, у него есть некоторые незначительные проблемы, но мне нравится идея. Btw работает очень быстро, на некоторых тестах он даже превосходит мой «IntStreamEx.minBy». Я [совершил ошибку] ​​(https://github.com/amaembo/streamex/commit/86703a8b787e4769766c99112db5ba42d75ac3c4) исправленную версию, возможно, включит ее в мою библиотеку. –