Existe uma maneira concisa de iterar em um stream com índices no Java 8?

Existe uma maneira concisa de iterar em um stream enquanto se tem access ao índice no stream?

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; List nameList; Stream 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()); 

o que parece bastante decepcionante comparado ao exemplo LINQ dado lá

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

Existe uma maneira mais concisa?

Além disso, parece que o zip foi movido ou removido …

A maneira mais limpa é começar a partir de um stream de índices:

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

A lista resultante contém apenas “Erik”.


Uma alternativa que parece mais familiar quando você está acostumado a for loops seria manter um contador ad hoc usando um object mutável, por exemplo, um AtomicInteger :

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

Observe que o uso do último método em um stream paralelo poderia ser interrompido, pois os itens não seriam necessariamente processados ​​"por ordem" .

A API de streams do Java 8 não possui os resources para obter o índice de um elemento de stream, bem como a capacidade de compactar streams juntos. Isso é lamentável, pois faz com que certos aplicativos (como os desafios do LINQ) sejam mais difíceis do que seriam de outra forma.

Muitas vezes existem soluções alternativas, no entanto. Geralmente, isso pode ser feito “conduzindo” o stream com um intervalo inteiro e aproveitando o fato de que os elementos originais geralmente estão em uma matriz ou em uma coleção acessível por índice. Por exemplo, o problema do Desafio 2 pode ser resolvido desta maneira:

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

Como mencionei acima, isso aproveita o fato de que a fonte de dados (a matriz de nomes) é diretamente indexável. Se não fosse, essa técnica não funcionaria.

Admito que isso não satisfaz a intenção do Desafio 2. No entanto, ele resolve o problema de maneira razoavelmente eficaz.

EDITAR

Meu exemplo de código anterior usava flatMap para fundir as operações de filtro e mapa, mas isso era complicado e não dava vantagem. Eu atualizei o exemplo de acordo com o comentário de Holger.

Desde goiaba 21, você pode usar

 Streams.mapWithIndex() 

Exemplo (do documento oficial ):

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

Eu usei a seguinte solução no meu projeto. Eu acho que é melhor do que usar objects mutáveis ​​ou intervalos inteiros.

 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  Stream 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  Stream> zipWithIndex(Stream< ? extends T> stream) { return iterate(new Iterator>() { private final Iterator< ? extends T> streamIterator = stream.iterator(); private int index = 0; @Override public boolean hasNext() { return streamIterator.hasNext(); } @Override public Map.Entry 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  Stream mapWithIndex(Stream< ? extends T> stream, BiFunction 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)); } } 

Além do protonpack, o Seq de jOOλ fornece esta funcionalidade (e por bibliotecas de extensão que constroem sobre ele como cyclops-react , eu sou o autor desta biblioteca).

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

O Seq também suporta apenas Seq.of (nomes) e construirá um JDK Stream sob as capas.

O equivalente de simples reagir seria similarmente

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

A versão de reação simples é mais personalizada para processamento asynchronous / simultâneo.

Apenas para completar, aqui está a solução que envolve a minha biblioteca StreamEx :

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

Aqui criamos um EntryStream que estende o Stream> e adiciona algumas operações específicas como filterKeyValue ou values . Também toList() atalho é usado.

Não há uma maneira de iterar em um Stream enquanto se tem access ao índice porque um Stream é diferente de qualquer Collection . Um Stream é apenas um pipeline para transportar dados de um lugar para outro, conforme declarado na documentação :

Nenhum armazenamento. Um stream não é uma estrutura de dados que armazena elementos; em vez disso, eles carregam valores de uma fonte (que pode ser uma estrutura de dados, um gerador, um canal de E / S, etc.) por meio de um pipeline de operações computacionais.

Naturalmente, como você parece estar insinuando em sua pergunta, você sempre pode converter seu Stream em uma Collection , como uma List , na qual você terá access aos índices.

Com https://github.com/poetix/protonpack u pode fazer esse zip:

 String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; List nameList; Stream 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); 

Se você não se importar em usar uma biblioteca de terceiros, o Eclipse Collections tem zipWithIndex e forEachWithIndex disponíveis para uso em vários tipos. Aqui está um conjunto de soluções para esse desafio para tipos JDK e tipos de Coleções Eclipse usando zipWithIndex .

 String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; ImmutableList expected = Lists.immutable.with("Erik"); Predicate> predicate = pair -> pair.getOne().length() < = pair.getTwo() + 1; // JDK Types List strings1 = ArrayIterate.zipWithIndex(names) .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, strings1); List list = Arrays.asList(names); List strings2 = ListAdapter.adapt(list) .zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, strings2); // Eclipse Collections types MutableList mutableNames = Lists.mutable.with(names); MutableList strings3 = mutableNames.zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, strings3); ImmutableList immutableNames = Lists.immutable.with(names); ImmutableList strings4 = immutableNames.zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, strings4); MutableList strings5 = mutableNames.asLazy() .zipWithIndex() .collectIf(predicate, Pair::getOne, Lists.mutable.empty()); Assert.assertEquals(expected, strings5); 

Aqui está uma solução usando forEachWithIndex .

 MutableList mutableNames = Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik"); ImmutableList expected = Lists.immutable.with("Erik"); List actual = Lists.mutable.empty(); mutableNames.forEachWithIndex((name, index) -> { if (name.length() < = index + 1) actual.add(name); }); Assert.assertEquals(expected, actual); 

Se você alterar os lambdas para classs internas anônimas acima, todos esses exemplos de código funcionarão também no Java 5 - 7.

Nota: Eu sou um committer para collections do Eclipse

Com uma lista você pode tentar

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

Saída:

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

Eu encontrei as soluções aqui quando o stream é criado de lista ou matriz (e você sabe o tamanho). Mas e se o Stream estiver com tamanho desconhecido? Neste caso, tente esta variante:

 public class WithIndex { 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  Function> indexed() { return new Function>() { int index = 0; @Override public WithIndex apply(T t) { return new WithIndex<>(index++, t); } }; } } 

Uso:

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

Aqui está o código de AbacusUtil

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

Divulgação: Eu sou o desenvolvedor do AbacusUtil.

Se acontecer de você usar o Vavr (anteriormente conhecido como Javaslang), você pode aproveitar o método dedicado:

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

Se imprimirmos o conteúdo, veremos algo interessante:

 Stream((A, 0), ?) 

Isso ocorre porque os Streams são preguiçosos e não temos a menor ideia sobre os próximos itens no stream.

Você pode criar uma class interna estática para encapsular o indexador como eu precisava fazer no exemplo abaixo:

 static class Indexer { int i = 0; } public static String getRegex() { EnumSet 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(); } 

Esta questão ( Stream Way para obter o índice do primeiro elemento correspondente booleano ) marcou a questão atual como uma duplicata, então eu não posso responder lá; Eu estou respondendo aqui.

Aqui está uma solução genérica para obter o índice correspondente que não requer uma biblioteca externa.

Se você tem uma lista.

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

E chame assim:

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

E se estiver usando uma coleção, tente esta.

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

Uma maneira possível é indexar cada elemento no stream:

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

Usando uma class anônima ao longo de um stream não é bem usado, sendo muito útil.

Se você está tentando obter um índice baseado em um predicado, tente isto:

Se você se importa apenas com o primeiro índice:

 OptionalInt index = IntStream.range(0, list.size()) .filter(i -> list.get(i) == 3) .findFirst(); 

Ou se você quiser encontrar vários índices:

 IntStream.range(0, list.size()) .filter(i -> list.get(i) == 3) .collect(Collectors.toList()); 

Adicione .orElse(-1); caso você queira devolver um valor se não o encontrar.