Er det en kortfattet måte å iterere over en bekk med indekser i Java 8?

stemmer
280

Er det en kortfattet måte å iterere over en bekk samtidig ha tilgang til indeksen i strømmen?

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

som synes heller skuffende sammenlignet med LINQ eksempel gis det

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

Er det en mer konsis måte?

Videre ser det ut zip enten har flyttet eller fjernet ...

Publisert på 31/08/2013 klokken 17:31
kilden bruker
På andre språk...                            


20 svar

stemmer
4

Det er ikke en måte å iterere over en Streamsamtidig ha tilgang til indeksen fordi en Streamer ulik alle Collection. A Streamer bare en rørledning for transport av data fra ett sted til et annet, som det fremgår av dokumentasjonen :

Ingen lagring. En strøm ikke er en datastruktur som lagrer elementer; i stedet, bærer de verdier fra en kilde (som kan være en datastruktur, en generator, en IO kanal, etc) gjennom en rørledning av beregningsoperasjoner.

Selvfølgelig, som du synes å være antydet at i spørsmålet ditt, du kan alltid konvertere Stream<V>til en Collection<V>, for eksempel en List<V>, der du vil ha tilgang til indeksene.

Svarte 31/08/2013 kl. 17:37
kilden bruker

stemmer
318

Den reneste måte er å starte fra en strøm av indekser:

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

Den resulterende listen inneholder "Erik" bare.


Et alternativ som ser mer kjent når du er vant til for looper vil være å opprettholde en ad hoc-disken ved hjelp av en foranderlig gjenstand, for eksempel en 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());

Merk at ved bruk av den sistnevnte metode på en parallell strøm kunne bryte deler som ikke ville necesarily bli behandlet "i orden" .

Svarte 31/08/2013 kl. 17:38
kilden bruker

stemmer
59

Java 8 strømmer API mangler funksjonene får indeks av en strøm element, så vel som evne til å zip strømmer sammen. Dette er uheldig, fordi det gjør enkelte programmer (som LINQ utfordringer) vanskeligere enn de ellers ville være.

Det er ofte midlertidige løsninger, imidlertid. Vanligvis kan dette gjøres ved "kjøring" av datastrømmen med et heltall rekkevidde, og drar nytte av det faktum at de opprinnelige elementer er ofte i en matrise eller i en samling tilgjengelig med indeks. For eksempel kan Challenge 2 problemet løses på denne måten:

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

Som nevnt ovenfor, tar denne fordel av det faktum at datakilden (den navnene array) er direkte indexable. Hvis det ikke var, ville denne teknikken ikke fungerer.

Jeg skal innrømme at dette ikke oppfyller intensjonen i Challenge 2. Likevel det løser problemet rimelig effektivt.

REDIGERE

Min forrige kode eksempel brukes flatMaptil sikring filter og kart operasjoner, men dette var tungvint og ga ingen nytte. Jeg har oppdatert eksempelet per kommentaren fra Holger.

Svarte 01/09/2013 kl. 21:31
kilden bruker

stemmer
22

Jeg har brukt følgende løsning i mitt prosjekt. Jeg tror det er bedre enn å bruke foranderlig gjenstander eller heltall områder.

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));
    }
}
Svarte 14/04/2014 kl. 00:58
kilden bruker

stemmer
3

Med https://github.com/poetix/protonpack u kan gjøre det 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);
Svarte 16/01/2015 kl. 15:33
kilden bruker

stemmer
13

I tillegg til protonpack, jOOλ er Seq gir denne funksjonaliteten (og i forlengelsen bibliotekene som bygger på det som Cyclops reagerer , jeg er forfatter av dette biblioteket).

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

Seq støtter også bare Seq.of (navn) og vil bygge en JDK Stream under dyna.

Den enkle reagerer tilsvarende ville likeledes se ut

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

Den enkle reagerer versjonen er mer tilpasset for asynkron / samtidige behandling.

Svarte 29/03/2015 kl. 19:25
kilden bruker

stemmer
12

Bare for fullstendighet her er løsningen som involverer min StreamEx bibliotek:

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

Her skaper vi en EntryStream<Integer, String>som strekker seg Stream<Entry<Integer, String>>og legger til noen spesifikke operasjoner som filterKeyValueeller values. Også toList()snarvei brukes.

Svarte 10/09/2015 kl. 01:07
kilden bruker

stemmer
0

Du kan opprette en statisk indre klasse å kapsle indekser som jeg trengte å gjøre i eksempelet nedenfor:

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();
}
Svarte 14/02/2016 kl. 00:14
kilden bruker

stemmer
3

Hvis du ikke har noe imot å bruke en tredjeparts bibliotek, Eclipse samlinger har zipWithIndexog forEachWithIndextilgjengelig for bruk på tvers av mange typer. Her er et sett av løsninger på denne utfordringen både for JDK typer og Eclipse samlinger typer hjelp 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);

Her er en løsning å bruke forEachWithIndexi stedet.

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

Hvis du endrer lambdaene til anonyme indre klasser over, da alle disse kode eksempler vil fungere i Java 5-7 også.

Merk: Jeg er en committer for Eclipse samlinger

Svarte 28/02/2016 kl. 04:36
kilden bruker

stemmer
1

Her er koden ved AbacusUtil

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

Disclosure: Jeg er utvikleren av AbacusUtil.

Svarte 02/12/2016 kl. 06:06
kilden bruker

stemmer
3

Med en liste kan du prøve

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

Produksjon:

0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth
Svarte 06/03/2017 kl. 00:46
kilden bruker

stemmer
24

Siden guava 21, kan du bruke

Streams.mapWithIndex()

Eksempel (fra offisielle doc ):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
Svarte 08/03/2017 kl. 12:22
kilden bruker

stemmer
0

Dette spørsmålet ( Stream måten å få Prisindeks for første element matchende boolsk ) har markert det aktuelle spørsmålet som et duplikat, så jeg kan ikke svare på det der; Jeg svarer det her.

Her er en generisk løsning for å få matching indeksen som ikke krever en ekstern bibliotek.

Hvis du har en 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);
}

Og kalle det slik:

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

Og hvis du bruker en samling, prøv denne.

   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;
    }
Svarte 21/05/2017 kl. 12:18
kilden bruker

stemmer
1

Hvis du tilfeldigvis bruke Vavr (tidligere kjent som Javaslang), kan du utnytte den dedikerte metoden:

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

Hvis vi skrive ut innholdet, vil vi se noe interessant:

Stream((A, 0), ?)

Dette er fordi Streamser late, og vi har ingen anelse om neste elementer i strømmen.

Svarte 25/07/2017 kl. 16:23
kilden bruker

stemmer
3

Jeg fant løsninger her når Stream er skapt av liste eller matrise (og du vet størrelsen). Men hva hvis Stream er med ukjent størrelse? I dette tilfellet kan du prøve denne varianten:

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

bruk:

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());
    });
}
Svarte 31/08/2017 kl. 07:01
kilden bruker

stemmer
0

En mulig måte er å indeksere hvert element på strømnings:

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

Ved hjelp av en anonym klasse langs en strøm er ikke godt benyttes mens den er svært nyttig.

Svarte 31/08/2017 kl. 20:22
kilden bruker

stemmer
0

Hvis du prøver å få en indeks basert på et predikat, prøv dette:

Hvis du bare bryr seg om den første indeks:

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

Eller hvis du ønsker å finne flere indekser:

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

Legg .orElse(-1);i tilfelle du ønsker å returnere en verdi hvis den ikke finner det.

Svarte 04/04/2018 kl. 18:59
kilden bruker

stemmer
0

Du kan bruke IntStream.iterate()for å få Indeks:

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

Dette fungerer bare for Java 9 oppover i Java 8 kan du bruke denne:

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());
Svarte 05/06/2019 kl. 22:57
kilden bruker

stemmer
0
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);

UTGANG: 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 
Svarte 29/08/2019 kl. 05:21
kilden bruker

stemmer
0

du trenger ikke en map nødvendigvis
som er nærmest lambda til LINQ eksempel:

int[] idx = new int[] { 0 };
Stream.of( names ).filter( name -> name.length() <= idx[0]++ ).collect( Collectors.toList() );
Svarte 26/10/2019 kl. 16:48
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more