Java 8'de indeksleri olan bir akış üzerinde yineleme yapmanın kısa bir yolu var mı?


382

Akıştaki dizine erişirken bir akış üzerinde yineleme yapmanın özlü bir yolu var mı?

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

orada verilen LINQ örneğine kıyasla oldukça hayal kırıklığı yaratıyor gibi görünüyor

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

Daha özlü bir yol var mı?

Ayrıca zip ya taşınmış ya da kaldırılmış gibi görünüyor ...


2
Nedir intRange()? Şimdiye kadar Java 8'de bu yöntemin karşısına çıkmadım.
Rohit Jain

@RohitJain Muhtemelen bir IntStream.rangeClosed(x, y).
assylias

2
Bir yan yorum olarak, meydan okuma 4 ile daha iyi görünüyor (IMO)List<String> allCities = map.values().stream().flatMap(list -> list.stream()).collect(Collectors.toList());
assylias

3
Evet, veya olarak zipadlandırılan deneysel iki değerli akışlarla birlikte kaldırıldı . Asıl sorun, bunu etkili bir şekilde yapmak için Java'nın gerçekten yapısal olarak yazılmış bir çift (veya demet) tipine ihtiyacı olmasıdır. Bir tanesinden yoksun, genel bir Pair veya Tuple sınıfı oluşturmak kolaydır - birçok kez yapılır - ancak hepsi aynı türden silinir. BiStreamMapStream
Stuart Marks

3
Oh, genel bir Pair veya Tuple sınıfıyla ilgili bir başka sorun da tüm ilkellerin kutuya alınması gerekmesidir.
Stuart Marks

Yanıtlar:


435

En temiz yol bir endeks akışından başlamaktır:

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

Ortaya çıkan liste sadece "Erik" i içerir.


Döngüler için alıştığınız zaman daha tanıdık görünen bir alternatif, değiştirilebilir bir nesne kullanarak geçici bir sayaç bulundurmaktır, örneğin 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());

Not o ürün necesarily "sırayla" işleme tabi tutulmaz olarak kırılabilir bir paralel akışta ikincisi yöntemi kullanarak .


28
Atomiklerin bu şekilde kullanılması paralel akışlarla sorunludur. İlk olarak, öğelerin işlenme sırası, ilk dizide öğelerin meydana gelme sırası ile aynı olmaz. Bu nedenle, atomik kullanılarak atanan "indeks" muhtemelen gerçek dizi indeksiyle eşleşmez. İkincisi, atomlar iş parçacığı için güvenliyken, atomiği güncelleyen ve paralellik miktarını azaltan birden fazla iş parçacığı arasında çekişme yaşayabilirsiniz.
Stuart Marks

1
@Assylias'ınkine benzer bir çözüm geliştirdim. Bahsedilen paralel akım @StuartMarks ile sorunlu atlamak için, önce belirli bir paralel akış sıralı yapmak, eşleme yapmak ve paralel durumu geri yüklemek. public static <T> Stream<Tuple2<Integer, T>> zipWithIndex(Stream<T> stream) { final AtomicInteger index = new AtomicInteger(); final Function<T, Tuple2<Integer, T>> zipper = e -> Tuples.of(index.getAndIncrement(), e); if (stream.isParallel()) { return stream.sequential().map(zipper).parallel(); } else { return stream.map(zipper); } }
Daniel Dietrich

4
@DanielDietrich Soruyu çözdüğünü düşünüyorsanız, bir yorumdan ziyade bir cevap olarak göndermelisiniz (ve kod da daha okunabilir olacaktır!).
assylias

3
@DanielDietrich Üzgünüm, eğer bu kodu doğru okuyorsam işe yaramaz. Bir boru hattının paralel ve ardışık olarak çalışan farklı segmentleri olamaz. Terminal işlemi başladığında yalnızca sonuncusu parallelveya sequentialonurlandırılır.
Stuart Marks

4
Adalet uğruna, @ Stuart'ın cevabından “en temiz yol” çalındı.
Vadzim

70

Java 8 akış API'sı, akış öğesinin dizinini alma özelliklerinin yanı sıra akışları bir araya getirme özelliğinden yoksundur. Bu, talihsiz bir durumdur, çünkü bazı uygulamaları (LINQ zorlukları gibi) aksi halde olduğundan daha zor hale getirir.

Bununla birlikte, genellikle geçici çözümler vardır. Genellikle bu, akışı bir tamsayı aralığıyla "sürerek" ve orijinal öğelerin genellikle bir dizide veya dizin tarafından erişilebilen bir koleksiyonda olmasından yararlanılarak yapılabilir. Örneğin, 2. Mücadele sorunu şu şekilde çözülebilir:

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

Yukarıda bahsettiğim gibi, bu veri kaynağının (names dizisi) doğrudan dizine eklenebilir olmasından yararlanır. Eğer olmasaydı, bu teknik işe yaramazdı.

Bunun 2. Mücadelenin niyetini karşılamadığını kabul edeceğim. Bununla birlikte, sorunu makul bir şekilde etkili bir şekilde çözüyor.

DÜZENLE

Önceki kod flatMapörneğim, filtre ve harita işlemlerini birleştirmek için kullanıldı , ancak bu hantaldı ve hiçbir avantaj sağladı. Holger yorumuna göre örneği güncelledim.


7
Nasıl IntStream.range(0, names.length).filter(i->names[i].length()<=i).mapToObj(i->names[i])? Boks olmadan çalışıyor…
Holger

1
Hım, evet, neden kullanmam gerektiğini düşündüm flatMap?
Stuart Marks

2
Sonunda bunu tekrar ziyaret ediyorum ... Muhtemelen kullandım flatMapçünkü bir filtreleme ve haritalama işlemini tek bir işleme birleştiriyor, ama bu gerçekten hiçbir avantaj sağlamaz. Örneği düzenleyeceğim.
Stuart Marks

Stream.of (Dizi) bir dizi için bir akış arabirimi oluşturur. Etkili bir şekilde Stream.of( names ).filter( n -> n.length() <= 1).collect( Collectors.toList() );daha az kutudan çıkarma ve daha az bellek ayırma; çünkü artık bir dizi akışı oluşturmuyoruz.
Eyez Kod

44

Guava 21'den beri kullanabilirsiniz

Streams.mapWithIndex()

Örnek ( resmi dokümandan ):

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

3
Ayrıca, Guava halkı henüzEachWithIndex (bir işlev yerine bir tüketici almak) için uygulamadı, ancak bu atanmış bir sorun: github.com/google/guava/issues/2913 .
John Glassmyer

25

Projemde aşağıdaki çözümü kullandım. Değişken nesneler veya tamsayı aralıkları kullanmaktan daha iyi olduğunu düşünüyorum.

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

+1 - her N dizinini kullanarak bir öğeyi "ekleyen" bir işlev StreamSupport.stream()ve özel bir yineleyici uygulayabildi .
ach

13

Protonpack'e ek olarak, jOOλ's Seq bu işlevselliği sağlar (ve bunun üzerine cyclops-tepki gibi inşa edilen uzatma kütüphaneleri ile bu kütüphanenin yazarıyım ).

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

Seq aynı zamanda sadece Seq.of (adlar) 'ı destekler ve kapakların altında bir JDK Akışı oluşturur.

Basit tepki eşdeğeri benzer şekilde

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

Basit tepki versiyonu asenkron / eşzamanlı işleme için daha uygundur.


John bugün kütüphanenizi gördüm, hem şaşırdım hem de kafam karıştı.
GOXR3PLUS

12

Sadece tamlık için StreamEx kütüphanemi içeren çözüm :

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

Burada veya gibi bazı belirli işlemleri EntryStream<Integer, String>genişleten Stream<Entry<Integer, String>>ve ekleyen bir tane oluştururuz . Ayrıca kısayol kullanılır.filterKeyValuevaluestoList()


harika iş; için bir kısayol var .forEach(entry -> {}) mı?
Steve Oh

2
@SteveOh doğru soruyu anlarsam, evet, yazabilirsin .forKeyValue((key, value) -> {}).
Tagir Valeev

8

Akış liste veya dizi oluşturulduğunda (ve boyutu biliyorsunuz) burada çözümler buldum. Peki ya Akış bilinmeyen boyuttaysa? Bu durumda bu varyantı deneyin:

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

Kullanımı:

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

6

Bir Liste ile deneyebilirsiniz

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

Çıktı:

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

1
Aslında güzel bir fikir ama dizeleri :: indexOf biraz pahalı olabilir. Benim önerim bunun yerine: .collect (HashMap :: new, (h, s) -> h.put (h.size (), s), (h, s) -> {}) . Dizini oluşturmak için size () yöntemini kullanabilirsiniz.
gil.fernandes

@ gil.fernandes Öneri için teşekkürler. Düzenlemeleri yapacağım.
V0idst4r

3

İndekse Streamerişirken yinelemenin bir yolu yoktur, çünkü a Streamherhangi birinden farklıdır Collection. A Stream, yalnızca belgelerde belirtildiği gibi bir yerden bir yere veri taşımak için bir boru hattıdır :

Depolama alanı yok. Akış, öğeleri depolayan bir veri yapısı değildir; bunun yerine, bir hesaplama işlemlerinden oluşan bir boru hattı aracılığıyla bir kaynaktan değerleri (bir veri yapısı, bir jeneratör, bir IO kanalı, vb.) taşırlar.

Tabii ki, gibi her zaman dönüştürebilirsiniz, sorunuzu ima gibi görünen Stream<V>bir karşı Collection<V>böyle bir olarak, List<V>hangi dizinleri erişebilir.


2
Bu diğer dillerde / araçlarda mevcuttur. Bu sadece harita fonksiyonuna geçen artan bir değerdir
Lee Campbell

Belgelere bağlantınız kesildi.
Usman Mutawakil

3

Https://github.com/poetix/protonpack ile bu zip'i yapabilirsiniz:

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

3

Bir üçüncü taraf kitaplığı kullanarak sakıncası yoksa, Eclipse Koleksiyonları sahiptir zipWithIndexve forEachWithIndexbirçok türleri arasında kullanıma hazır. İşte JDK türleri ve Eclipse Collections türleri için bu meydan okuma için bir dizi çözüm 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);

İşte forEachWithIndexbunun yerine kullanarak bir çözüm .

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

Lambdasları yukarıdaki anonim iç sınıflarla değiştirirseniz, bu kod örneklerinin tümü Java 5 - 7'de de çalışır.

Not: Eclipse Collections için bir komisyoncuyum


2

Vavr'ı (eski adıyla Javaslang olarak bilinir) kullanırsanız, özel yöntemi kullanabilirsiniz:

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

İçeriği yazdırırsak ilginç bir şey göreceğiz:

Stream((A, 0), ?)

Çünkü Streamstembel ve akıştaki sonraki öğeler hakkında hiçbir fikrimiz yok.


1

Bir yüklemi temel alan bir dizin almaya çalışıyorsanız, şunu deneyin:

Yalnızca ilk dizini önemsiyorsanız:

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

Veya birden çok dizin bulmak istiyorsanız:

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

.orElse(-1);Bir değeri bulamazsa döndürmek istemeniz durumunda ekleyin .


1

İşte AbacusUtil kodu

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

Açıklama : AbacusUtil'in geliştiricisiyim.


1

IntStream.iterate()Dizini almak için kullanabilirsiniz :

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

Bu yalnızca Java 8'de Java 9 için yukarı doğru çalışır:

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

0

Aşağıdaki örnekte yapmam gereken dizinleyiciyi kapsüllemek için statik bir iç sınıf oluşturabilirsiniz:

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

0

Bu soru ( Boole ile eşleşen ilk öğenin dizinini almanın Akış Yolu onu orada cevap veremez, böylece kopya olarak cari soru işaretlemiştir); Ben burada cevaplıyorum.

Harici bir kütüphane gerektirmeyen eşleşen dizini almak için genel bir çözüm.

Bir listeniz varsa.

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

Ve şöyle deyin:

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

Koleksiyon kullanıyorsanız, bunu deneyin.

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

0

Olası bir yol, akıştaki her öğeyi dizine eklemektir:

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

Bir akış boyunca anonim bir sınıf kullanmak, çok yararlı olurken iyi kullanılmaz.


0

map mutlaka
LINQ örneğine en yakın lambda olana ihtiyacınız yoktur :

int[] idx = new int[] { 0 };
Stream.of( names ).filter( name -> name.length() <= idx[0]++ ).collect( Collectors.toList() );

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

ÇIKTI: 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 

Listede Toplamak İçin:

String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
 List<String> namesList
                =  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.toList()); // collecting elements in List
        System.out.println(listWithIndex);

Yukarıdaki sorunun çözümünün, Listbir element içeren Erik olması beklenmektedir .
Kaplan

Listede de toplamak için bir örnek ekledim.
Arpan Saini

0

Jean-baptiste-yunès'in dediği gibi, akışınız bir java listesine dayanıyorsa, o zaman bir AtomicInteger ve onun incrementAndGet yöntemi soruna çok iyi bir çözümdür ve döndürülen tam sayı, orijinal Listedeki dizine karşılık geldiği sürece paralel akış kullanmayın.


0

ForEach dizinine ihtiyacınız varsa, bu bir yol sağlar.

  public class IndexedValue {

    private final int    index;
    private final Object value;

    public IndexedValue(final int index, final Object value) { 
        this.index = index;
        this.value = value;
    }

    public int getIndex() {
        return index;
    }

    public Object getValue() {
        return value;
    }
}

Sonra aşağıdaki gibi kullanın.

@Test
public void withIndex() {
    final List<String> list = Arrays.asList("a", "b");
    IntStream.range(0, list.size())
             .mapToObj(index -> new IndexedValue(index, list.get(index)))
             .forEach(indexValue -> {
                 System.out.println(String.format("%d, %s",
                                                  indexValue.getIndex(),
                                                  indexValue.getValue().toString()));
             });
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.