2016-02-27 6 views
3

В процессе, чтобы получить мои руки в потоках Java 8, следующее упражнение остановило меня.Java-потоки с состоянием - тривиальное упражнение

С учетом IntStream.range(0, 6). Производят струнный поток ниже:

"0, 1" 
"1, 2" 
"2, 3" 
"3, 4" 
"4, 5" 

Я думал использовать Collectors.collectAndThen, чтобы передать его в старый добрый список или массив и петля для построения списка строк следующим образом:

List<String> strgs = new ArrayList<>(); 
String prev = String.valueOf(nums[0]); 
for (int i = 1; i < nums.length; i++) { 
    strgs.add(prev+", "+String.valueOf(nums[i])); 
    prev = String.valueOf(nums[i]); 
} 

Но он не использует силу потоков. Я чувствовал, что Венкат Субраманиам говорит: «Я чувствовал, что принимаю душ после». Я хочу знать, как применять функциональные методы, поэтому я могу пропустить принятие душа после кодирования!

Кроме того, я хотел бы избегать библиотек, таких как StreamEx или JavaRx, я хочу придерживаться простых API Java 8.

Редактировать: @Tunaki, спасибо, что указал на неясную формулировку в моем вопросе. Это пара, состоящая из двух последовательных элементов Потока. Более бетон, поток как [1, 3, 5, 7, 9, ...] будет

"1, 3" 
"3, 5" 
"5, 7" 
... 

Edit 2

После салютуя ответы на все вопросы, хотя мой вопрос является дубликатом на другой вопрос, как отметил Tunaki. Я хочу расширить обсуждение сообщества для ответа, внесенного богемским языком. Хотя его ответ был недоволен некоторыми, он вызывает серьезную проблему, которая уменьшает действие с побочными эффектами. Моя просьба к сообществу - предоставить разумный счетчик действительной методики. Таким образом, я хочу, чтобы повторно использовать Bohemian ответ следующим образом:

Учитывая вход: НУМС = новый INT [] {1,3,5,7,9}

Пожалуйста, обратите внимание сниппет ниже:

List<CharSequence> stringList = new ArrayList<>(); 
IntBinaryOperator reductionWithSideEffect = (int left, int right) -> { 
     stringList.add(new StringBuilder().append(left).append(", ").append(right)); 
     return right; 
}; 
Arrays.stream(nums) 
     .reduce(reductionWithSideEffect); 
System.out.println(String.join(", ", stringList)); 
+4

Взгляните на этот вопрос: http://stackoverflow.com/q/20470010/1743880. Проще говоря, это не то, для чего был разработан Stream API. – Tunaki

+4

Откуда взялось это упражнение? –

+0

@Stuart Marks, это моя адаптация упражнения старых массивов, с помощью которого я хотел оспорить понимание Streams. Я очень приветствую ваш ответ в вопросе, связанном с комментарием Тунаки. Это близко к ручьям младшего, как я. – Sam

ответ

6

На мой взгляд, самый чистый способ решить эту проблему - написать собственный разделитель и создать поток над ним. Это не очень сложно, если вам не нужна абсолютно максимальная производительность и не заботится о параллельной обработке (параллельный поток будет работать, но неэффективно). Что-то, как это будет работать:

public static <T, R> Stream<R> pairMap(BaseStream<T, ?> source, 
     BiFunction<? super T, ? super T, ? extends R> mapper) { 
    Spliterator<T> spltr = source.spliterator(); 
    long sourceSize = spltr.estimateSize(); 
    Spliterator<R> result = new Spliterators.AbstractSpliterator<R>(
      sourceSize > 0 && sourceSize < Long.MAX_VALUE ? sourceSize - 1 : sourceSize, 
      spltr.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { 
     T prev; 
     boolean started; 

     @Override 
     public boolean tryAdvance(Consumer<? super R> action) { 
      if (!started) { 
       if (!spltr.tryAdvance(t -> prev = t)) 
        return false; 
       started = true; 
      } 
      return spltr.tryAdvance(t -> action.accept(mapper.apply(prev, prev = t))); 
     } 
    }; 
    return StreamSupport.stream(result, source.isParallel()).onClose(source::close); 
} 

Здесь mapper это функция, которая создает элемент нового потока на основе пары соседних элементов входного потока.

Пример использования:

pairMap(IntStream.range(0, 6), (a, b) -> a + ", " + b).forEach(System.out::println); 

Выход:

0, 1 
1, 2 
2, 3 
3, 4 
4, 5 
+0

Спасибо за ваш ответ. Это один из немногих раз, когда я вижу, что Spliterators используются в кратком коде. Это отличный пример для начинающих spliterator. – Sam

+0

Вы можете реализовать 'tryAdvance' как один оператор,' return (start || (start = spltr.tryAdvance (t -> prev = t))) && spltr.tryAdvance (t -> action.accept (mapper.apply (prev, prev = t))); 'но, я думаю, слишком много людей считают это слишком трудным для чтения ... – Holger

+0

@ Хольджер, конечно, слишком много, включая меня. –

0

Как Tunaki упоминает в комментариях поток API не является идеальным для этого случая использования. Лучшее, что я мог сделать, это написать функцию отображения состояния, чтобы помнить, каким был последний встреченный элемент.

Примечание. Как отмечает Тагир Валеев в комментариях, это решение прерывается, если результирующий поток возвращается в параллельный режим вызывающим абонентом с использованием BaseStream.parallel().

public class StreamExercise { 
    public static void main(String[] args) { 
     f(IntStream.range(0, 6)) 
      .forEach(s -> System.out.println(s)); 

     System.out.println(); 

     f(IntStream.of(1,3,5,7,9)) 
      .forEach(s -> System.out.println(s)); 
    } 

    private static Stream<String> f(IntStream input) { 
     IntFunction<String> mapper = new IntFunction<String>() { 
      private int last = -1; 

      @Override 
      public String apply(int value) { 
       String result = last + ", " + value; 
       last = value; 
       return result; 
      } 

     }; 

     return input.sequential().mapToObj(mapper).skip(1); 
    } 
} 

Выход:

0, 1 
1, 2 
2, 3 
3, 4 
4, 5 

1, 3 
3, 5 
5, 7 
7, 9 
+0

Спасибо, Кевин за ответ, он просто встает на свои места с моим набором знаний. Я только надеюсь, что какой-то потоковый Гуру вернет нам законность использования функции отображения состояния, как в вашем ответе. Обсуждение в вопросе http://stackoverflow.com/questions/20470010/collect-successive-pairs-from-a-stream также вызывает такую ​​же озабоченность в ответе, указанном @assylias – Sam

+0

@Sam: обратная связь от меня (если Я могу считаться гуру Streams): использование функции отображения с использованием состояния плохо. Как правило: если Stream ведет себя неправильно в параллельном режиме, тогда решение грязно. Обратите внимание, что '.sequential()' здесь не помогает, так как я могу снова включить поток в параллель: 'f (IntStream.of (1,3,5,7,9)). Parallel(). ForEachOrdered (System. из :: Println) '. –

+0

@ Тагир Валеев ах, я не думал об этом. Кажется, мне еще многое нужно узнать о потоках! –

-1

Я думаю, что самый короткий код, чтобы получить работу является:

IntStream.range(0, 6).reduce((a, b) -> {System.out.println(a + ", " + b); return b;}); 

Поскольку выше (рабочий) код вызывает споры, это то это простое, читаемое решение, которое использует API приемлемо:

class PairPrinter implements IntConsumer { 
    private boolean set; 
    private int previous; 

    public void accept(int i) { 
     if (set) { 
      System.out.println(previous + ", " + i); 
     } 
     set = true; 
     previous = i; 
    } 
} 

PairPrinter printer = new PairPrinter(); 
IntStream.range(0, 6).forEach(printer); 

Которая (также) дает желаемый результат.

+0

Да, но это не работает для 'Stream' like '[1, 3, 5, 7, 9, ...]' или '[0, 2, 4, 6, 8, ...] ' – FaNaJ

+1

@FaNaJ Я просто понял, что общее решение еще проще - см. Отредактированный ответ – Bohemian

+3

Это злоупотребление API. В то время как '(a, b) -> b' является правильной функцией для' сокращения', свободного от побочных эффектов и ассоциативного, утверждение печати не является. Очевидно, что он будет работать не в параллельном режиме, а даже при последовательной работе, это просто работает как побочный эффект того, как он реализован. – Holger

0

Простой и легкий:

public static void main(String[] args) { 
    generatePairs(Arrays.asList(1, 3, 5, 7, 9)).forEach(System.out::println); 
} 

public static <E> List<Pair<E>> generatePairs(List<E> src) { 
    if (src.size() < 2) { 
     throw new IllegalArgumentException("src.size must be greater than 1"); 
    } 
    return IntStream.range(0, src.size() - 1) 
      .mapToObj(i -> new Pair<>(src.get(i), src.get(i + 1))) 
      .collect(Collectors.toList()); 
} 

и Pair класс:

public final class Pair<E> { 

    private E left; 
    private E right; 

    public Pair() { 
    } 

    public Pair(E left, E right) { 
     this.left = left; 
     this.right = right; 
    } 

    public E getLeft() { 
     return left; 
    } 

    public void setLeft(E left) { 
     this.left = left; 
    } 

    public E getRight() { 
     return right; 
    } 

    public void setRight(E right) { 
     this.right = right; 
    } 

    @Override 
    public String toString() { 
     return "{" + left + 
       ", " + right + 
       '}'; 
    } 

} 

EDIT:

Вот полный пример:

import java.util.Arrays; 
import java.util.Comparator; 
import java.util.List; 
import java.util.Objects; 
import java.util.stream.Collectors; 
import java.util.stream.DoubleStream; 
import java.util.stream.IntStream; 
import java.util.stream.LongStream; 

public final class Pair<L, R> implements Comparable<Pair<L, R>> { 

    private final Comparator<? super L> comparator; 

    private L left; 
    private R right; 

    public Pair() { 
     comparator = null; 
    } 

    public Pair(Comparator<? super L> comparator) { 
     this.comparator = comparator; 
    } 

    public Pair(L left, R right) { 
     this(left, right, null); 
    } 

    public Pair(L left, R right, Comparator<? super L> comparator) { 
     this.left = left; 
     this.right = right; 
     this.comparator = comparator; 
    } 

    public L getLeft() { 
     return left; 
    } 

    public void setLeft(L left) { 
     this.left = left; 
    } 

    public R getRight() { 
     return right; 
    } 

    public void setRight(R right) { 
     this.right = right; 
    } 

    @Override 
    public int hashCode() { 
     return Objects.hash(left, right); 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == this) { 
      return true; 
     } 
     if (obj == null || !(obj instanceof Pair)) { 
      return false; 
     } 
     Pair that = (Pair) obj; 
     return Objects.equals(left, that.left) && Objects.equals(right, that.right); 
    } 

    @Override 
    public String toString() { 
     return "{" + left + 
       ", " + right + 
       '}'; 
    } 

    @Override 
    @SuppressWarnings("unchecked") 
    public int compareTo(Pair<L, R> o) { 
     return comparator == null ? ((Comparable<? super L>) left).compareTo(o.left) : comparator.compare(left, o.left); 
    } 

    public static abstract class Stream { 

     private Stream() { 
     } 

     public final java.util.stream.Stream<Pair<Integer, Integer>> of(IntStream src) { 
      return of(src.boxed()); 
     } 

     public final java.util.stream.Stream<Pair<Long, Long>> of(LongStream src) { 
      return of(src.boxed()); 
     } 

     public final java.util.stream.Stream<Pair<Double, Double>> of(DoubleStream src) { 
      return of(src.boxed()); 
     } 

     public final <E> java.util.stream.Stream<Pair<E, E>> of(java.util.stream.Stream<E> src) { 
      return of(src.collect(Collectors.toList())); 
     } 

     @SuppressWarnings("all") 
     public abstract <E> java.util.stream.Stream<Pair<E, E>> of(E... src); 

     public abstract <E> java.util.stream.Stream<Pair<E, E>> of(List<E> src); 

     protected static void checkSize(int size) { 
      if (size < 1) { 
       throw new IllegalArgumentException("Empty source."); 
      } 
     } 

     protected static <E> E getOrNull(E[] array, int index) { 
      return index < array.length ? array[index] : null; 
     } 

     protected static <E> E getOrNull(List<E> list, int index) { 
      return index < list.size() ? list.get(index) : null; 
     } 

     public static Stream chained() { 
      return new ChainedPairStream(); 
     } 

     public static Stream distinct() { 
      return new DistinctPairStream(); 
     } 

    } 

    private static final class ChainedPairStream extends Stream { 

     @SafeVarargs 
     public final <E> java.util.stream.Stream<Pair<E, E>> of(E... src) { 
      int length = src.length; 
      checkSize(length); 
      return IntStream.range(0, Math.max(1, length - 1)).mapToObj(i -> new Pair<>(src[i], getOrNull(src, i + 1))); 
     } 

     public final <E> java.util.stream.Stream<Pair<E, E>> of(List<E> src) { 
      int size = src.size(); 
      checkSize(size); 
      return IntStream.range(0, Math.max(1, size - 1)).mapToObj(i -> new Pair<>(src.get(i), getOrNull(src, i + 1))); 
     } 

    } 

    private static final class DistinctPairStream extends Stream { 

     @SafeVarargs 
     public final <E> java.util.stream.Stream<Pair<E, E>> of(E... src) { 
      int length = src.length; 
      checkSize(length); 
      return IntStream.iterate(0, i -> i + 2) 
        .limit((long) Math.ceil(length/2.0)) 
        .mapToObj(i -> new Pair<>(src[i], getOrNull(src, i + 1))); 
     } 

     public final <E> java.util.stream.Stream<Pair<E, E>> of(List<E> src) { 
      int size = src.size(); 
      checkSize(size); 
      return IntStream.iterate(0, i -> i + 2) 
        .limit((long) Math.ceil(size/2.0)) 
        .mapToObj(i -> new Pair<>(src.get(i), getOrNull(src, i + 1))); 
     } 

    } 

    public static void main(String[] args) { 
     Pair.Stream.distinct().of(1, 2, 3, 4, 5, 6).forEach(System.out::println); 

     Pair.Stream.chained().of(Arrays.asList(1, 3, 5, 7, 9)).forEach(System.out::println); 

     Pair.Stream.chained().of(Arrays.stream(new int[]{0, 2, 4, 6, 8})).forEach(System.out::println); 
     Pair.Stream.distinct().of(Arrays.stream(new int[]{0, 2, 4, 6, 8})).forEach(System.out::println); 

     Pair.Stream.distinct().of(IntStream.range(0, 6)).forEach(System.out::println); 
    } 

} 
+0

Спасибо за ответ. Это похоже на честную адаптацию @StuartMarks в вопросе http://stackoverflow.com/questions/20470010/collect-successive-pairs-from-a-stream. Надеюсь, мы сможем увидеть некоторые ответы на ваш ответ от сообщества. – Sam

+0

@Sam, обратная связь: для этого решения требуется источник с произвольным доступом. Для потокового ввода он сначала выгружает его в промежуточную «Список», излишне теряющую память. Если это приемлемое ограничение, то решение в порядке. По крайней мере, это не нарушает спецификации. –