2013-08-31 8 views
237

Есть ли сжатый способ перебора потока, имея доступ к индексу в потоке?Есть ли сжатый способ перебора потока с индексами в Java 8?

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; 

List<String> nameList; 
Stream<Integer> indices = intRange(1, names.length).boxed(); 
nameList = zip(indices, stream(names), SimpleEntry::new) 
     .filter(e -> e.getValue().length() <= e.getKey()) 
     .map(Entry::getValue) 
     .collect(toList()); 

, который кажется довольно разочаровывающим по сравнению с примером, LINQ приведенные там

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; 
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList(); 

Есть ли более краткий путь?

Далее кажется почтовым имеют либо перемещены или удалены ...

+2

Что такое 'intRange()'? Не дошли до этого метода на Java 8 до сих пор. –

+0

@RohitJain Вероятно, 'IntStream.rangeClosed (x, y)'. – assylias

+2

В качестве побочного комментария задача 4 выглядит лучше (IMO) с «List allCities = map.values ​​(). Stream(). FlatMap (list -> list.stream()). Collect (Collectors.toList()); ' – assylias

ответ

275

Чистейшая способ начать из потока индексов:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; 
IntStream.range(0, names.length) 
     .filter(i -> names[i].length() <= i) 
     .mapToObj(i -> names[i]) 
     .collect(Collectors.toList()); 

Результирующий список содержит «Эрик» только.


Одна альтернатива, которая выглядит более знакомым, когда вы привыкли для петли будет поддерживать специальный счетчик, используя изменяемый объект, например AtomicInteger:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; 
AtomicInteger index = new AtomicInteger(); 
List<String> list = Arrays.stream(names) 
          .filter(n -> n.length() <= index.incrementAndGet()) 
          .collect(Collectors.toList()); 

Обратите внимание, что с использованием последний метод на параллельном потоке мог бы сломаться, поскольку элементы не обязательно обрабатывались «по порядку».

+17

Использование атоматики таким образом проблематично с параллельными потоками. Во-первых, порядок * обработки * элементов не обязательно будет таким же, как порядок, в котором элементы появляются в исходном массиве. Таким образом, «индекс», назначенный с использованием атома, вероятно, не будет соответствовать фактическому индексу массива. Во-вторых, в то время как атомы являются потокобезопасными, вы можете столкнуться с разногласиями между несколькими потоками, обновляющими атомарную систему, ухудшая количество параллелизма. –

+1

Я разработал решение, подобное тому, что было описано на @assylias. Чтобы обойти проблематику с параллельным потоком @StuartMarks, я сначала сделал данный параллельный поток последовательным, выполнил отображение и восстановил параллельное состояние. 'общественности статической поток > zipWithIndex (поток поток) { \t Окончательный индекс AtomicInteger = новый AtomicInteger(); \t final Функция > zipper = e -> Tuples.of (index.getAndIncrement(), e); \t if (stream.isParallel()) { \t \t return stream.sequential(). Map (молния) .parallel(); \t} else { \t \t return stream.map (молния); \t} } ' –

+4

@ DanielDietrich Если вы считаете, что он решает вопрос, вы должны разместить его как ответ, а не комментарий (и код будет более читабельным тоже!). – assylias

4

Существует не способ перебрать Stream в то время как имеющий доступ к индексу, поскольку Stream в отличии от любого Collection. A Stream представляет собой просто трубопровод для переноса данных из одного места в другое, как указано в documentation:

Нет склада. Поток не является структурой данных, в которой хранятся элементы; вместо этого они переносят значения из источника (который может быть структурой данных, генератором, каналом ввода-вывода и т. д.) через конвейер вычислительных операций.

Конечно, как вы, кажется, намекая на ваш вопрос, вы всегда можете конвертировать ваши Stream<V> в Collection<V>, такие как List<V>, в котором вы будете иметь доступ к индексам.

53

API-интерфейс потоков Java 8 не имеет особенностей получения индекса элемента потока, а также возможности связывания потоков вместе. Это печально, поскольку это делает некоторые приложения (например, проблемы LINQ) более сложными, чем в противном случае.

Однако часто возникают обходные пути. Обычно это может быть сделано путем «вождения» потока с целым диапазоном и использования того факта, что исходные элементы часто находятся в массиве или в коллекции, доступной по индексу. Например, задача Задача 2 может быть решена таким образом:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; 

List<String> nameList = 
    IntStream.range(0, names.length) 
     .filter(i -> names[i].length() <= i) 
     .mapToObj(i -> names[i]) 
     .collect(toList()); 

Как уже говорилось выше, это занимает преимущество тот факт, что источник данных (массив имен) непосредственно индексируемая. Если бы это не так, этот метод не сработал.

Я признаю, что это не удовлетворяет цели Challenge 2. Тем не менее, решение этой проблемы достаточно эффективно.

РЕДАКТИРОВАТЬ

Мой предыдущий пример кода используется flatMap плавить фильтра и карты операций, но это было громоздким и не предусмотрено никаких преимуществ. Я обновил пример в комментарии от Holger.

+7

Как насчет 'IntStream.range (0, names.length) .filter (i-> names [i] .length() <= i) .mapToObj (i-> names [i])'? Он работает без бокса ... – Holger

+1

Хм, да, почему я думал, что мне нужно использовать 'flatMap'? –

+2

Наконец, пересматривая это ... Я, вероятно, использовал 'flatMap', потому что он вроде как предохранит операцию фильтрации и сопоставления в одну операцию, но это действительно не дает никакого преимущества. Я отредактирую пример. –

19

В моем проекте я использовал следующее решение. Я думаю, что это лучше, чем использование изменяемых объектов или целых диапазонов.

import java.util.*; 
import java.util.function.*; 
import java.util.stream.Collector; 
import java.util.stream.Collector.Characteristics; 
import java.util.stream.Stream; 
import java.util.stream.StreamSupport; 
import static java.util.Objects.requireNonNull; 


public class CollectionUtils { 
    private CollectionUtils() { } 

    /** 
    * Converts an {@link java.util.Iterator} to {@link java.util.stream.Stream}. 
    */ 
    public static <T> Stream<T> iterate(Iterator<? extends T> iterator) { 
     int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE; 
     return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false); 
    } 

    /** 
    * Zips the specified stream with its indices. 
    */ 
    public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) { 
     return iterate(new Iterator<Map.Entry<Integer, T>>() { 
      private final Iterator<? extends T> streamIterator = stream.iterator(); 
      private int index = 0; 

      @Override 
      public boolean hasNext() { 
       return streamIterator.hasNext(); 
      } 

      @Override 
      public Map.Entry<Integer, T> next() { 
       return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next()); 
      } 
     }); 
    } 

    /** 
    * Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream. 
    * The first argument of the function is the element index and the second one - the element value. 
    */ 
    public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) { 
     return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue())); 
    } 

    public static void main(String[] args) { 
     String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; 

     System.out.println("Test zipWithIndex"); 
     zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry)); 

     System.out.println(); 
     System.out.println("Test mapWithIndex"); 
     mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s)); 
    } 
} 
+0

+1 - удалось реализовать функцию, которая «вставляет» элемент каждые N индексов с использованием 'StreamSupport.stream()' и пользовательского итератора. – ach

3

С https://github.com/poetix/protonpack и может сделать это почтовый индекс:

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; 

List<String> nameList; 
Stream<Integer> indices = IntStream.range(0, names.length).boxed(); 

nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new) 
     .filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());     

System.out.println(nameList); 
12

В дополнение к protonpack, jOOλ's Seq предоставляет эту функциональность (и библиотеками расширений, которые строят на нем, как cyclops-react, я являюсь автором этого библиотека).

Seq.seq(Stream.of(names)).zipWithIndex() 
         .filter(namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) 
         .toList(); 

Seq также поддерживает только Seq.of (имена), и будет строить JDK поток под одеялом.

Простой реагировать эквивалентное бы аналогично выглядеть

LazyFutureStream.of(names) 
       .zipWithIndex() 
       .filter(namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) 
       .toList(); 

Простой реагировать версия более с учетом для асинхронного/параллельной обработки.

10

Просто для полноты вот решение с моей StreamEx библиотеки:

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; 
EntryStream.of(names) 
    .filterKeyValue((idx, str) -> str.length() <= idx+1) 
    .values().toList(); 

Здесь мы создаем EntryStream<Integer, String>, который расширяет Stream<Entry<Integer, String>> и добавляет некоторые специфические операции, такие как filterKeyValue или values. Также используется toList() ярлык.

0

Вы можете создать статический внутренний класс для инкапсуляции индексатор, как мне нужно делать в примере ниже:

static class Indexer { 
    int i = 0; 
} 

public static String getRegex() { 
    EnumSet<MeasureUnit> range = EnumSet.allOf(MeasureUnit.class); 
    StringBuilder sb = new StringBuilder(); 
    Indexer indexer = new Indexer(); 
    range.stream().forEach(
      measureUnit -> { 
       sb.append(measureUnit.acronym); 
       if (indexer.i < range.size() - 1) 
        sb.append("|"); 

       indexer.i++; 
      } 
    ); 
    return sb.toString(); 
} 
3

Если вы не возражаете, используя сторонние библиотеки, Eclipse Collections имеет zipWithIndex и forEachWithIndex доступен для использования во многих типах. Вот набор решений этой проблемы как для типов JDK, так и для типов Eclipse Collections с использованием zipWithIndex.

String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; 
ImmutableList<String> expected = Lists.immutable.with("Erik"); 
Predicate<Pair<String, Integer>> predicate = 
    pair -> pair.getOne().length() <= pair.getTwo() + 1; 

// JDK Types 
List<String> strings1 = ArrayIterate.zipWithIndex(names) 
    .collectIf(predicate, Pair::getOne); 
Assert.assertEquals(expected, strings1); 

List<String> list = Arrays.asList(names); 
List<String> strings2 = ListAdapter.adapt(list) 
    .zipWithIndex() 
    .collectIf(predicate, Pair::getOne); 
Assert.assertEquals(expected, strings2); 

// Eclipse Collections types 
MutableList<String> mutableNames = Lists.mutable.with(names); 
MutableList<String> strings3 = mutableNames.zipWithIndex() 
    .collectIf(predicate, Pair::getOne); 
Assert.assertEquals(expected, strings3); 

ImmutableList<String> immutableNames = Lists.immutable.with(names); 
ImmutableList<String> strings4 = immutableNames.zipWithIndex() 
    .collectIf(predicate, Pair::getOne); 
Assert.assertEquals(expected, strings4); 

MutableList<String> strings5 = mutableNames.asLazy() 
    .zipWithIndex() 
    .collectIf(predicate, Pair::getOne, Lists.mutable.empty()); 
Assert.assertEquals(expected, strings5); 

Решение проблемы с использованием forEachWithIndex.

MutableList<String> mutableNames = 
    Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik"); 
ImmutableList<String> expected = Lists.immutable.with("Erik"); 

List<String> actual = Lists.mutable.empty(); 
mutableNames.forEachWithIndex((name, index) -> { 
     if (name.length() <= index + 1) 
      actual.add(name); 
    }); 
Assert.assertEquals(expected, actual); 

Если изменить лямбды на анонимные внутренние классы выше, то все эти примеры кода будет работать в Java 5 - 7, а также.

Примечание: Я коммиттер для Eclipse, Коллекции

1

Вот код на AbacusUtil

Stream.of(names).indexed() 
     .filter(e -> e.value().length() <= e.index()) 
     .map(Indexed::value).toList(); 

Раскрытие: Я разработчик AbacusUtil.

3

со списком вы можете попробовать

List<String> strings = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Fourth", "Fifth")); // An example list of Strings 
strings.stream() // Turn the list into a Stream 
    .collect(HashMap::new, (h, o) -> h.put(h.size(), o), (h, o) -> {}) // Create a map of the index to the object 
     .forEach((i, o) -> { // Now we can use a BiConsumer forEach! 
      System.out.println(String.format("%d => %s", i, o)); 
     }); 

Выход:

0 => First 
1 => Second 
2 => Third 
3 => Fourth 
4 => Fifth 
+0

На самом деле хорошая идея, но * строки :: indexOf * могут быть немного дорогими. Мое предложение состоит в том, чтобы использовать вместо этого: * .collect (HashMap :: new, (h, s) -> h.put (h.size(), s), (h, s) -> {}) *. Вы можете просто использовать метод size() для создания индекса. –

+0

@ gil.fernandes Спасибо за предложение. Я сделаю изменения. – V0idst4r

8

С гуавы 21, вы можете использовать

Streams.mapWithIndex() 

Пример (из official doc):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"), 
    (str, index) -> str + ":" + index) 
) // will return Stream.of("a:0", "b:1", "c:2") 
0

Этот вопрос (Stream Way to get index of first element matching boolean) отметил текущий вопрос как дубликат, поэтому я не могу ответить на него; Я отвечаю на него здесь.

Вот общее решение для получения соответствующего индекса, который не требует внешней библиотеки.

Если у вас есть список.

public static <T> int indexOf(List<T> items, Predicate<T> matches) { 
     return IntStream.range(0, items.size()) 
       .filter(index -> matches.test(items.get(index))) 
       .findFirst().orElse(-1); 
} 

И называть это так:

int index = indexOf(myList, item->item.getId()==100); 

А если использовать коллекцию, попробуйте это.

public static <T> int indexOf(Collection<T> items, Predicate<T> matches) { 
     int index = -1; 
     Iterator<T> it = items.iterator(); 
     while (it.hasNext()) { 
      index++; 
      if (matches.test(it.next())) { 
       return index; 
      } 
     } 
     return -1; 
    } 
1

Если вы решили использовать Vavr (ранее известный как Javaslang), вы можете использовать специальный метод:

Stream.of("A", "B", "C") 
    .zipWithIndex(); 

Если распечатать содержимое, мы увидим что-то интересное:

Stream((A, 0), ?) 

Это потому, что Streams ленивы, и мы не имеем понятия о следующих предметах в потоке.

2

Я нашел решения здесь, когда Stream создан из списка или массива (и вы знаете размер). Но что, если Stream с неизвестным размером? В этом случае попробуйте этот вариант:

public class WithIndex<T> { 
    private int index; 
    private T value; 

    WithIndex(int index, T value) { 
     this.index = index; 
     this.value = value; 
    } 

    public int index() { 
     return index; 
    } 

    public T value() { 
     return value; 
    } 

    @Override 
    public String toString() { 
     return value + "(" + index + ")"; 
    } 

    public static <T> Function<T, WithIndex<T>> indexed() { 
     return new Function<T, WithIndex<T>>() { 
      int index = 0; 
      @Override 
      public WithIndex<T> apply(T t) { 
       return new WithIndex<>(index++, t); 
      } 
     }; 
    } 
} 

Использование:

public static void main(String[] args) { 
    Stream<String> stream = Stream.of("a", "b", "c", "d", "e"); 
    stream.map(WithIndex.indexed()).forEachOrdered(e -> { 
     System.out.println(e.index() + " -> " + e.value()); 
    }); 
} 
0

Один возможный способ индексировать каждый элемент на поток:

AtomicInteger index = new AtomicInteger(); 
Stream.of(names) 
    .map(e->new Object() { String n=e; public i=index.getAndIncrement(); }) 
    .filter(o->o.n.length()<=o.i) // or do whatever you want with pairs... 
    .forEach(o->System.out.println("idx:"+o.i+" nam:"+o.n)); 

Используя анонимный класс вдоль поток не очень хорошо используется, будучи очень полезным.