Programing

Java 8에서 지수를 사용하여 스트림을 반복하는 간결한 방법이 있는가?

c10106 2022. 5. 16. 20:28
반응형

Java 8에서 지수를 사용하여 스트림을 반복하는 간결한 방법이 있는가?

스트림에서 인덱스에 액세스하면서 스트림을 반복하는 간결한 방법이 있는가?

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

LINQ의 예에 비하면 다소 실망스러운 것 같다.

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

좀 더 간결한 방법이 있을까?

게다가 지퍼가 움직였거나 제거된 것 같아...

가장 깨끗한 방법은 일련의 지수에서 시작하는 것이다.

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

결과 목록은 "Erik"만 포함한다.


루프에 익숙할 때 보다 친숙해 보이는 한 가지 대안은 예를 들어 변이 가능한 객체를 사용하여 임시 카운터를 유지하는 것이다.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());

병렬 스트림에서 후자의 방법을 사용하면 항목이 "순서에 따라" 처리되지 않으므로 파손될 수 있다는 점에 유의하십시오.

Java 8 스트림 API에는 스트림 요소의 인덱스를 얻는 기능뿐만 아니라 스트림을 지퍼링할 수 있는 기능이 부족하다.이는 유감스러운 일이며, 이는 (LINQ 과제와 같은) 특정 애플리케이션을 그렇지 않은 애플리케이션보다 더 어렵게 만들기 때문이다.

그러나 종종 해결책이 있다.보통 이것은 스트림을 정수 범위로 "주행"하고, 원래 요소가 종종 배열이나 색인으로 접근 가능한 집합에 있다는 점을 이용하여 수행할 수 있다.예를 들어 챌린지 2 문제는 다음과 같은 방법으로 해결할 수 있다.

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

위에서 언급했듯이, 이것은 데이터 소스(이름 배열)가 직접 인덱싱할 수 있다는 점을 이용한다.그렇지 않다면 이 기술은 효과가 없을 것이다.

이것이 챌린지 2의 의도를 충족시키지 못한다는 것을 인정하겠다.그럼에도 불구하고 그것은 문제를 합리적으로 해결한다.

편집

사용된 이전 코드 예제flatMap필터와 지도 작업을 융합하는 것은 번거로웠고 아무런 이점도 제공하지 않았다.나는 홀거의 코멘트에 따라 예시를 업데이트했다.

guava 21부터 사용가능

Streams.mapWithIndex()

예제(공식 문서):

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

나는 내 프로젝트에서 다음과 같은 솔루션을 사용해 왔다.나는 그것이 변이 가능한 물체나 정수 범위를 사용하는 것보다 낫다고 생각한다.

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

protonpack 외에도 jOOλ의 Seq는 이러한 기능(그리고 그것을 기반으로 한 확장형 라이브러리에 의해, 나는 이 라이브러리의 작가다).

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

Seq는 또한 Seq.of(이름)만을 지원하며 커버 아래 JDK 스트림을 구축한다.

간단한 리액트 등가물이 유사하게

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

단순 리액트 버전은 비동기/동시 프로세싱에 더 적합하다.

StreamEx 라이브러리와 관련된 솔루션은 다음과 같다.

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

여기서 확장된 것을 만든다.Stream<Entry<Integer, String>>그리고 또는 .와 같은 특정 작업을 추가하며 바로 가기도 사용된다.

스트림이 목록이나 배열로 만들어졌을 때 여기서 해결책을 찾았어. 그리고 너도 그 크기를 알잖아.스트림이 알 수 없는 크기라면?이 경우 다음 변형을 사용해 보십시오.

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

사용량:

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

목록으로 시도할 수 있음

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

출력:

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

Vavr(이전의 Javaslang으로 알려진)을 사용할 경우 다음과 같은 전용 방법을 활용할 수 있다.

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

만약 우리가 그 내용을 인쇄한다면, 우리는 흥미로운 것을 보게 될 것이다:

Stream((A, 0), ?)

왜하하면 되기 때문이다.Streams게을러서 우리는 개울의 다음 아이템에 대해 전혀 알지 못한다.

AbacusUtil별 코드

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

공개: 나는 AbacusUtil의 개발자다.

반복할 수 있는 방법이 없다.Stream다음과 같은 이유로 인덱스에 액세스할 수 있는 동안Stream다른 어떤 것과도 다르다.CollectionStream설명서에 명시된 바와 같이 한 장소에서 다른 장소로 데이터를 운반하는 파이프라인일 뿐이다.

창고가 없다.스트림은 요소를 저장하는 데이터 구조가 아니라, 대신, 소스(데이터 구조, 제너레이터, IO 채널 등)로부터 값을 계산 작업의 파이프라인을 통해 전달한다.

물론, 질문에서 암시하고 있는 것처럼 보이듯이, 당신은 언제나 자신의 생각을 바꿀 수 있었다.Stream<V>완전히Collection<V>등 , 등List<V> 인덱스에 액세스할 수 있는 위치.

https://github.com/poetix/protonpack으로 당신은 그 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);

타사 라이브러리를 사용하는 것이 괜찮다면 Eclipse Collections는 여러 가지 유형에서 사용할 수 있으며 사용할 수 있다.다음은 JDK 유형과 Eclipse Collections 유형 모두에 대한 이 과제에 대한 솔루션 모음이며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);

다다이즘을 forEachWithIndex대신에

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

위의 람다를 익명 내부 클래스로 변경하면 이러한 모든 코드 예제가 Java 5 - 7에서도 작동하게 된다.

참고: 나는 Eclipse Collections의 커밋자입니다.

인덱스를 가져오는 데 사용할 수 있는 기능:

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

이것은 Java 8에서 Java 9 이상에서만 사용할 수 있다.

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

술어를 기반으로 인덱스를 가져오려면 다음을 수행하십시오.

첫 번째 인덱스에만 관심이 있는 경우:

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

또는 여러 인덱스를 찾으려는 경우:

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

추가하다.orElse(-1);값을 찾을 수 없는 경우 값을 반환하십시오.

가능한 한 가지 방법은 흐름의 각 요소를 색인화하는 것이다.

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

하천을 따라 익명 클래스를 사용하는 것은 유용하지만 잘 사용되지 않는다.

각 항목에 인덱스가 필요한 경우 이 방법을 사용하십시오.

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

그럼 다음과 같이 쓰세요.

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

a는 필요 없다. map 필수로
LINQ 예제에 가장 가까운 람다:

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

아래 예제에서처럼 인덱서를 캡슐화하는 정적 내부 클래스를 만들 수 있다.

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

이 질문(Stream Way to get index of first element match boolean)은 현재 질문을 중복으로 표시하였으므로, 나는 거기에 대답할 수 없고, 여기서 대답한다.

외부 라이브러리가 필요 없는 일치 지수를 얻기 위한 일반적인 솔루션이 여기에 있다.

리스트가 있으면.

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

그리고 이렇게 부른다.

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

그리고 만약 컬렉션을 사용한다면, 이것을 사용해 보십시오.

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

출력 : 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 

목록에서 수집하려면:

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

Jean-baptiste-yunés가 말했듯이, 만약 당신의 스트림이 자바 리스트에 기초한다면, AtomicInteger와 그 incrementAndGet 방법을 사용하는 것은 문제에 대한 매우 좋은 해결책이며, 당신이 병렬 스트림을 사용하지 않는 한, 반환된 정수는 원래 리스트의 인덱스에 해당된다.

표준 Java에 대한 솔루션:

인라인 솔루션:

Arrays.stream("zero,one,two,three,four".split(","))
        .map(new Function<String, Map.Entry<Integer, String>>() {
            int index;

            @Override
            public Map.Entry<Integer, String> apply(String s) {
                return Map.entry(index++, s);
            }
        })
        .forEach(System.out::println);

유틸리티 방법을 사용하여 읽기 쉬운 솔루션:

static <T> Function<T, Map.Entry<Integer, T>> mapWithIntIndex() {
    return new Function<T, Map.Entry<Integer, T>>() {
        int index;

        @Override
        public Map.Entry<Integer, T> apply(T t) {
            return Map.entry(index++, t);
        }
    };
}

...
Arrays.stream("zero,one,two,three,four".split(","))
        .map(mapWithIntIndex())
        .forEach(System.out::println);

참조URL: https://stackoverflow.com/questions/18552005/is-there-a-concise-way-to-iterate-over-a-stream-with-indices-in-java-8

반응형