2015-09-09 6 views
5

Как извлечь два элемента из Stream по их позициям? Например, я пытаюсь извлечь элементы 0 и 1 (эти числа произвольны!) Из Stream<String>. Наивный подход заключается в следующем:Соберите определенные элементы из потока

List<String> strings = Arrays.asList("s0", "s1", "s2", "s3", "s4"); 
Consumer<String> c0 = s -> System.out.println("c0.accept(" + s + ")"); 
Consumer<String> c1 = s -> System.out.println("c1.accept(" + s + ")"); 
strings.stream().skip(0).peek(c0).skip(1).peek(c1).findAny(); 

Это производит следующий вывод:

c0.accept(s0) 
c0.accept(s1) 
c1.accept(s1) 

Я понимаю, что это потому, что s0 будет попадать в поток, сталкиваются skip(0), то peek(c0) (что дает первую строку) а затем skip(1), который пропустит этот элемент, а затем, по-видимому, продолжит следующий элемент с начала потока.

Я думал, что я мог бы использовать эти потребители, чтобы извлечь строки, но c0 будут переписаны вторым элементом:

String[] extracted = new String[2]; 
c0 = s -> extracted[0]; 
c1 = s -> extracted[1]; 

EDIT:

Эти характеристики потока:

  • Существует только поток, а не список или массив
  • Поток возможно бесконечное
  • поток может быть sequential
+0

Во-первых, 'skip (0)' ничего не делает, вы можете удалить его. Во-вторых, вам придется объяснить свой вариант использования немного больше, потому что вам лучше избавиться от индекса, используя исходный список, вместо использования потоков. – Andreas

+0

Вы действительно не можете извлекать по индексу из 'Stream', поскольку поток не является« List »- это произвольный _flow_ элементов. Вам нужно будет рассказать нам, что вы пытаетесь сделать, поскольку это очень похоже на [XY Problem] (https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). –

+0

@Andreas Как я уже сказал, числа являются произвольными, поэтому подумайте о 'skip (pos0)' вместо 'skip (0)' и 'skip (pos1)' для 'skip (1)'. «Исходный список» не существует (как упоминалось). – steffen

ответ

0

Вы могли бы написать что-то вроде следующего:

public static void main(String[] args) throws Exception { 
    List<String> strings = Arrays.asList("s0", "s1", "s2", "s3", "s4"); 
    System.out.println(getNthElement(strings.stream(), 0)); // prints "s0" 
    System.out.println(getNthElement(strings.stream(), 1)); // prints "s1" 
} 

private static <T> T getNthElement(Stream<T> stream, int n) { 
    return stream.skip(n).findFirst().get(); 
} 

Обратите внимание, что это вызовет исключение, если есть меньше, чем n элементы в потоке. Кроме того, это имеет смысл, только если поток не параллелен.

+1

Он также будет читать «Поток» несколько раз, что может быть проблемой, если «Поток» доступен только один раз. –

+0

@BoristheSpider Да, будет. Однако я не вижу другого способа сделать это. – Tunaki

+0

Я бы zip поток с индексом, а затем написать пользовательский коллекционер. Но я уверен, что OP хочет именно так, поэтому я не думаю, что стоит ответить на проблему XY. –

4

Учитывая ваше ограничение можно объединить limit() с пользовательским коллектору, как это:

public static <T, A, R> Collector<T, ?, R> collectByIndex(Set<Integer> wantedIndices, 
                  Collector<T, A, R> downstream) { 
    class Acc { 
     int pos; 
     A acc = downstream.supplier().get(); 
    } 
    return Collector.of(Acc::new, (acc, t) -> { 
     if(wantedIndices.contains(acc.pos++)) 
      downstream.accumulator().accept(acc.acc, t); 
    }, (a, b) -> {throw new UnsupportedOperationException();}, // combining not supported 
     acc -> downstream.finisher().apply(acc.acc)); 
} 

Здесь Set<Integer> wantedIndices является набор, содержащий индексы элементов хотел (не ограничивается 2). Использование:

Set<Integer> wantedIndices = new HashSet<>(Arrays.asList(1, 3)); 
Stream<String> input = Stream.of("s0", "s1", "s2", "s3", "s4"); 
List<String> result = input.limit(Collections.max(wantedIndices)+1) 
      .collect(collectByIndex(wantedIndices, Collectors.toList())); 
// [s1, s3] 
+0

Работает очень хорошо! – steffen

0

Это (не очень красиво, но легко и работает) решение:

List<String> strings = Arrays.asList("s0", "s1", "s2", "s3", "s4"); 
String[] extracted = new String[2]; 
Consumer<String> c0 = s -> extracted[0] = extracted[0] == null ? s : extracted[0]; 
Consumer<String> c1 = s -> extracted[1] = extracted[1] == null ? s : extracted[1]; 
strings.stream().skip(0).peek(c0).skip(1 - 0).peek(c1).findAny(); 
1

Это решение приходит из комментария Федерико Перальта Schaffner:

public String[] collect(Stream<String> stream, int... positions) { 
    String[] collect = new String[positions.length]; 
    Iterator<String> iterator = stream.iterator(); 
    int skipped = 0; 
    for (int pos = 0; pos < positions.length; pos++) { 
     while (skipped++ < positions[pos]) { 
      iterator.next(); 
     } 
     collect[pos] = iterator.next(); 
    } 
    return collect; 
} 

Этот является самой прямой и понятной идеей и отлично работает.

2

Вот такой подход, который я не видел в других ответах. Он использует изменение повсеместного Pair класса:

class Pair<T> { 
    final T first; 
    final T last; 
    Pair(T t1, T t2) { first = t1; last = t2; } 
    Pair(T t) { first = last = t; } 
    Pair<T> merge(Pair<T> other) { return new Pair<>(this.first, other.last); } 
} 

После того как вы это, вы можете легко получить первые и последние элементы потока.Учитывая бесконечный поток и желаемые показатели, вы можете использовать skip() и limit() урезать поток содержит только нужные элементы:

static <T> Pair<T> firstAndLast(Stream<T> stream, int firstIndex, int lastIndex) { 
    // ensure indexes >= 0 and firstIndex <= lastIndex 
    return stream.skip(firstIndex) 
       .limit(lastIndex - firstIndex + 1) 
       .map(Pair::new) 
       .reduce(Pair::merge) 
       .orElseThrow(() -> new IllegalArgumentException("nonexistent")); 
} 

Другие варианты будут включать встраивание строительство или объединить логику в операции потока вместо того, чтобы его на классе Pair. Рефакторинг по вкусу.

Вы бы использовать его как это:

Stream<String> str = Stream.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); 
    Pair<String> pair = firstAndLast(str, 4, 5); 
    System.out.println(pair.first + " " + pair.last); 

    e f 
1

Основным препятствием является одноразовым характер Stream с которой можно обойти:

static <T> List<T> get(Stream<? extends T> s, int... positions) { 
    Spliterator<? extends T> sp=s.spliterator(); 
    ArrayList<T> list=new ArrayList<>(positions.length); 
    int current=0; 
    for(int i: positions) { 
     if(i<current) throw new IllegalArgumentException("positions not ascending"); 
     Optional<? extends T> o 
      =StreamSupport.stream(sp, false).skip(i-current).findFirst(); 
     if(!o.isPresent()) break; 
     current=i+1; 
     list.add(o.get()); 
    } 
    return list; 
} 

Хотя я не уверен в том, Мне нравится ...

+0

Выглядит более громоздко, чем решение @steffen, и вы полагаетесь на неуказанный факт, что 'findFirst()' не перемещает spliterator дальше, чем необходимо ... –

+0

В каком из его решений вы ссылаетесь? И почему 'findFirst()' последовательного потока когда-либо продвигает spliterator больше, чем необходимо? Во всяком случае, я вообще не вижу случая для такого рода задач, так что это не имеет значения. Это больше интересный вопрос ... – Holger

+0

Не заметил, что @steffen отправил два ответа (почему SO разрешает это?). Я говорю об этом (http://stackoverflow.com/a/32484436/4856258). Это определенно не должно, это просто не указано. –

Смежные вопросы