Existe-t-il un moyen concis 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 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 le Zip ait été déplacé ou supprimé.
Le moyen le 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" uniquement.
Une alternative qui vous semble plus familière lorsque vous êtes habitué aux boucles for serait de conserver un compteur ad hoc utilisant un objet modifiable, 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());
Notez quel'utilisation de cette dernière méthode sur un flux parallèle pourrait être interrompue, car les éléments ne seraient pas nécessairement traités "dans l'ordre".
L'API des flux Java 8 ne dispose pas des fonctionnalités d'obtention de l'index d'un élément de flux, ni de la possibilité de compresser des flux simultanément. C'est regrettable, car cela rend certaines applications (comme les défis LINQ) plus difficiles qu'elles ne le seraient autrement.
Cependant, il existe souvent des solutions de contournement. Pour ce faire, vous pouvez généralement "piloter" le flux avec une plage entière et tirer parti du fait que les éléments d'origine se trouvent souvent dans un tableau ou dans une collection accessible par index. Par exemple, le problème du défi 2 peut être résolu de la manière suivante:
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é plus haut, cela tire parti du fait que la source de données (le tableau de noms) est directement indexable. Si ce n'était pas le cas, cette technique ne fonctionnerait pas.
J'admets que cela ne répond pas à l'objectif du Défi 2. Néanmoins, cela résout le problème de manière raisonnablement efficace.
MODIFIER
Mon exemple de code précédent utilisait flatMap
pour fusionner les opérations de filtrage et de mappage, mais cette opération était lourde et ne présentait aucun avantage. J'ai mis à jour l'exemple en fonction du commentaire de Holger.
Depuis la 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 des plages entières.
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 le construisent 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 ne prend également en charge que Seq.of (noms) et créera un flux JDK sous les couvertures.
L'équivalent simple-réagir ressemblerait de la même façon
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
La version simple-react est plus adaptée au traitement asynchrone/simultané.
Juste pour compléter, voici la solution impliquant ma StreamEx library:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
Nous créons ici un EntryStream<Integer, String>
qui étend Stream<Entry<Integer, String>>
et ajoute des opérations spécifiques comme filterKeyValue
ou values
. Aussi toList()
raccourci est utilisé.
J'ai trouvé les solutions ici lorsque le flux est créé de liste ou tableau (et vous connaissez la taille) Mais que se passe-t-il 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);
}
};
}
}
Usage:
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());
});
}
Il n’existe pas de moyen de parcourir une Stream
tout en ayant accès à l’index car une Stream
ne ressemble à aucune Collection
. Un Stream
est simplement un pipeline permettant de 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 provenant d'une source (une structure de données, un générateur, un canal IO, etc.) via un pipeline d'opérations de calcul.
Bien sûr, comme vous semblez l'indiquer dans votre question, vous pouvez toujours convertir votre Stream<V>
en un Collection<V>
, tel qu'un List<V>
, dans lequel vous aurez accès aux index.
Avec https://github.com/poetix/protonpack U peut faire cela 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 vous n’êtes pas dérangé par l’utilisation d’une bibliothèque tierce, Collections Eclipse a zipWithIndex
et forEachWithIndex
disponible pour différents types. Voici un ensemble de solutions à ce défi pour les types JDK et les collections Eclipse à l'aide de 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 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 modifiez les lambdas en classes internes anonymes ci-dessus, tous ces exemples de code fonctionneront également en Java 5 - 7.
Note: Je suis un partisan des 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
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), ?)
En effet, Streams
sont paresseux et nous n'avons aucune idée des éléments suivants dans le flux.
Voici le code par 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 essayez d’obtenir un index 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());
Ajoutez .orElse(-1);
si vous souhaitez renvoyer une valeur si elle ne la trouve pas.
Cette question ( Stream Way pour obtenir l'index du premier élément correspondant à boolean ) a marqué la question actuelle comme un doublon, je ne peux donc pas y 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;
}
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
String completeString
= IntStream.range(0,namesArray.length)
.mapToObj(i -> namesArray[i]) // Converting each array element into Object
.map(String::valueOf) // Converting object to String again
.collect(Collectors.joining(",")); // getting a Concat String of all values
System.out.println(completeString);
SORTIE: Sam, Pamela, Dave, Pascal, Erik
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0,namesArray.length)
.mapToObj(i -> namesArray[i]) // Converting each array element into Object
.map(String::valueOf) // Converting object to String again
.forEach(s -> {
//You can do various operation on each element here
System.out.println(s);
}); // getting a Concat String of all
Vous pouvez utiliser IntStream.iterate()
pour obtenir l'index:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i < names.length, i -> i + 1)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
Ceci ne fonctionne que pour Java 9 à partir de Java 8, vous pouvez utiliser ceci:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i + 1)
.limit(names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
Vous pouvez créer une classe interne statique pour encapsuler l'indexeur comme je le devais 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();
}