Y a-t-il une façon concise d'itérer sur un flux avec des indices en Java 8?
y a-t-il une façon concise d'itérer sur un flux tout en ayant accès à l'index du flux?
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());
ce qui semble plutôt décevant par rapport à L'exemple de LINQ donné ici
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
y a-t-il un moyen plus concis?
de plus, il semble que la fermeture éclair ait été déplacée ou enlevée...
17 réponses
la manière la plus propre est de partir d'un flux d'indices:
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());
la liste résultante contient" Erik " seulement.
une alternative qui semble plus familière lorsque vous êtes habitué à des boucles serait de maintenir un compteur ad hoc en utilisant un objet mutable, par exemple un 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());
noter que utilisant cette dernière méthode sur un flux parallèle .
L'API Java 8 streams ne possède pas les fonctionnalités nécessaires pour obtenir l'index d'un élément stream, ni la capacité de compresser les flux ensemble. C'est regrettable, car cela rend certaines applications (comme les défis LINQ) plus difficiles qu'elles ne le seraient autrement.
Il ya souvent des solutions de contournement, cependant. Généralement, cela peut être fait en "pilotant" le flux avec une portée entière, et en profitant du fait que les éléments originaux sont souvent dans un tableau ou dans un collection accessible par index. Par exemple, le problème du Challenge 2 peut être résolu de cette façon:
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());
comme je l'ai mentionné ci-dessus, cela tire avantage du fait que la source de données (le tableau des noms) est directement indexable. Si ce n'était pas le cas, cette technique ne fonctionne pas.
j'admets que cela ne répond pas à l'intention du défi 2. Néanmoins, elle résout le problème de manière raisonnablement efficace.
EDIT
mon exemple de code précédent utilisait flatMap
pour fusionner les opérations de filtrage et de cartographie, mais cela était lourd et n'apportait aucun avantage. J'ai mis à jour l'exemple dans le commentaire de Holger.
depuis goyave 21, vous pouvez utiliser
Streams.mapWithIndex()
exemple (de doc officiel ):
Streams.mapWithIndex(
Stream.of("a", "b", "c"),
(str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
j'ai utilisé la solution suivante dans mon projet. Je pense que c'est mieux que d'utiliser des objets mutables ou entier plages.
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));
}
}
en plus de protonpack, Jooλ's Seq fournit cette fonctionnalité (et par extension les bibliothèques qui construisent sur elle comme cyclops-react , je suis l'auteur de cette bibliothèque).
Seq.seq(Stream.of(names)).zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
Seq supporte aussi Seq.of (names) et construira un flux JDK sous les couvertures.
l'équivalent à réaction simple ressemblerait également à
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
Le simple fait de réagir la version est plus adaptée au traitement asynchrone / simultané.
juste pour l'exhaustivité voici la solution impliquant mon StreamEx bibliothèque:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
ici nous créons un EntryStream<Integer, String>
qui étend Stream<Entry<Integer, String>>
et ajoute quelques opérations spécifiques comme filterKeyValue
ou values
. Aussi toList()
raccourci est utilisé.
il n'y a pas moyen d'itérer sur un Stream
tout en ayant accès à l'index parce qu'un Stream
est différent de tout Collection
. Un Stream
est simplement un pipeline pour transporter des données d'un endroit à un autre, comme indiqué dans la documentation :
pas de stockage. Un flux n'est pas une structure de données qui stocke des éléments; au lieu de cela, ils portent des valeurs d'une source (qui pourrait être une structure de données, un générateur, un IO canal, etc) à travers un pipeline de calcul des opérations.
bien sûr, comme vous semblez l'indiquer dans votre question, vous pouvez toujours convertir votre Stream<V>
à un Collection<V>
, comme un List<V>
, dans lequel vous aurez accès à l'index.
avec https://github.com/poetix/protonpack u peut le faire que zip:
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);
si cela ne vous dérange pas d'utiliser une bibliothèque tierce, Eclipse Collections a zipWithIndex
et forEachWithIndex
disponible pour une utilisation dans de nombreux types. Voici un ensemble de solutions à ce défi pour les types de Collections JDK et Eclipse en utilisant 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);
Voici une solution en utilisant forEachWithIndex
à la place.
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);
si vous changez le lambdas aux classes internes anonymes ci - dessus, alors tous ces exemples de code fonctionneront aussi en Java 5-7.
Note: je suis un committer pour les Collections Eclipse
avec une liste vous pouvez essayer
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));
});
sortie:
0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth
j'ai trouvé les solutions ici quand le flux est créé de liste ou de tableau (et vous savez la taille). Mais que faire si Stream est de taille inconnue? Dans ce cas, essayez cette variante:
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);
}
};
}
}
Utilisation:
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());
});
}
voici le code de AbacusUtil
Stream.of(names).indexed()
.filter(e -> e.value().length() <= e.index())
.map(Indexed::value).toList();
Divulgation: je suis le développeur de AbacusUtil.
si vous utilisez Vavr (anciennement Javaslang), vous pouvez utiliser la méthode dédiée:
Stream.of("A", "B", "C")
.zipWithIndex();
si nous imprimons le contenu, nous verrons quelque chose d'intéressant:
Stream((A, 0), ?)
c'est parce que Streams
sont paresseux et nous n'avons aucune idée des éléments suivants dans le flux.
vous pouvez créer une classe interne statique pour encapsuler l'indexeur comme je devais le faire dans l'exemple ci-dessous:
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();
}
à Cette question ( cours d'eau de Façon à obtenir l'indice du premier élément correspondant boolean ) a marqué la question actuelle comme un doublon, donc je ne peux pas répondre; je réponds ici.
Voici une solution générique pour obtenir l'index correspondant qui ne nécessite pas de bibliothèque externe.
Si vous avez une liste.
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);
}
et appelez ça comme ça:
int index = indexOf(myList, item->item.getId()==100);
et si vous utilisez une collection, essayez celle-ci.
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;
}
si vous essayez d'obtenir un indice basé sur un prédicat, essayez ceci:
si vous ne vous souciez que du premier index:
OptionalInt index = IntStream.range(0, list.size())
.filter(i -> list.get(i) == 3)
.findFirst();
ou si vous voulez trouver plusieurs index:
IntStream.range(0, list.size())
.filter(i -> list.get(i) == 3)
.collect(Collectors.toList());
ajouter .orElse(-1);
dans le cas où vous voulez retourner une valeur si elle ne le trouve pas.
une façon possible est d'indexer chaque élément sur le flux:
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));
utiliser une classe anonyme le long d'un ruisseau n'est pas bien utilisé tout en étant très utile.