Собирайте пары из потока
У меня есть поток таких объектов:
"0", "1", "2", "3", "4", "5",
Как я могу преобразовать его в поток пар:
{ new Pair("0", "1"), new Pair("2", "3"), new Pair("4", "5")}.
Размер потока неизвестен. Я читаю данные из файла, который может быть большим. У меня есть только итератор для сбора, и я преобразую этот итератор в поток, используя spliterator. Я знаю, что здесь есть ответ для обработки соседних пар с StreamEx: Сбор последовательных пар из потока Можно ли это сделать в Java или StreamEx? Спасибо
5 ответов
Если вы не хотите собирать элементы
Название вопроса гласит: собирать пары из потока, поэтому я предполагаю, что вы действительно хотите собрать их, но вы прокомментировали:
Ваше решение работает, проблема в том, что он загружает данные из файла в PairList, а затем я могу использовать поток из этой коллекции для обработки пар. Я не могу этого сделать, потому что данные могут быть слишком большими для хранения в памяти.
так вот способ сделать это без сбора элементов.
Относительно просто преобразовать Iterator >
, а затем преобразовать поток в поток пар.
/**
* Returns an iterator over pairs of elements returned by the iterator.
*
* @param iterator the base iterator
* @return the paired iterator
*/
public static <T> Iterator<List<T>> paired(Iterator<T> iterator) {
return new Iterator<List<T>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public List<T> next() {
T first = iterator.next();
if (iterator.hasNext()) {
return Arrays.asList(first, iterator.next());
} else {
return Arrays.asList(first);
}
}
};
}
/**
* Returns an stream of pairs of elements from a stream.
*
* @param stream the base stream
* @return the pair stream
*/
public static <T> Stream<List<T>> paired(Stream<T> stream) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(paired(stream.iterator()), Spliterator.ORDERED),
false);
}
@Test
public void iteratorAndStreamsExample() {
List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f");
Iterator<List<String>> pairs = paired(strings.iterator());
while (pairs.hasNext()) {
System.out.println(pairs.next());
// [a, b]
// [c, d]
// [e, f]
}
paired(Stream.of(1, 2, 3, 4, 5, 6, 7, 8)).forEach(System.out::println);
// [1, 2]
// [3, 4]
// [5, 6]
// [7, 8]
}
Если вы хотите собрать элементы...
Я бы сделал это, собрав в список и используя AbstractList для представления элементов в виде пар.
Во-первых, PairList. Это простая оболочка AbstractList вокруг любого списка, имеющего четное количество элементов. (Это может быть легко адаптировано для обработки списков нечетной длины, если задано желаемое поведение.)
/**
* A view on a list of its elements as pairs.
*
* @param <T> the element type
*/
static class PairList<T> extends AbstractList<List<T>> {
private final List<T> elements;
/**
* Creates a new pair list.
*
* @param elements the elements
*
* @throws NullPointerException if elements is null
* @throws IllegalArgumentException if the length of elements is not even
*/
public PairList(List<T> elements) {
Objects.requireNonNull(elements, "elements must not be null");
this.elements = new ArrayList<>(elements);
if (this.elements.size() % 2 != 0) {
throw new IllegalArgumentException("number of elements must have even size");
}
}
@Override
public List<T> get(int index) {
return Arrays.asList(elements.get(index), elements.get(index + 1));
}
@Override
public int size() {
return elements.size() / 2;
}
}
Тогда мы можем определить коллектор, который нам нужен. Это по сути сокращение для collectingAndThen(toList(), PairList::new)
:
/**
* Returns a collector that collects to a pair list.
*
* @return the collector
*/
public static <E> Collector<E, ?, PairList<E>> toPairList() {
return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
}
Обратите внимание, что может быть целесообразно определить конструктор PairList, который не копирует список с защитой, поскольку мы знаем, что список поддержки создается заново (как в этом случае). Это сейчас не очень важно. Но как только мы это сделаем, этот метод будет collectingAndThen(toCollection(ArrayList::new), PairList::newNonDefensivelyCopiedPairList)
,
И теперь мы можем использовать это:
/**
* Creates a pair list with collectingAndThen, toList(), and PairList::new
*/
@Test
public void example() {
List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
.collect(toPairList());
System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
.collect(toPairList());
System.out.println(stringPairs); // [[a, b], [b, c]]
}
Вот полный исходный файл с работающим примером (как тест JUnit):
package ex;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
public class PairCollectors {
/**
* A view on a list of its elements as pairs.
*
* @param <T> the element type
*/
static class PairList<T> extends AbstractList<List<T>> {
private final List<T> elements;
/**
* Creates a new pair list.
*
* @param elements the elements
*
* @throws NullPointerException if elements is null
* @throws IllegalArgumentException if the length of elements is not even
*/
public PairList(List<T> elements) {
Objects.requireNonNull(elements, "elements must not be null");
this.elements = new ArrayList<>(elements);
if (this.elements.size() % 2 != 0) {
throw new IllegalArgumentException("number of elements must have even size");
}
}
@Override
public List<T> get(int index) {
return Arrays.asList(elements.get(index), elements.get(index + 1));
}
@Override
public int size() {
return elements.size() / 2;
}
}
/**
* Returns a collector that collects to a pair list.
*
* @return the collector
*/
public static <E> Collector<E, ?, PairList<E>> toPairList() {
return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
}
/**
* Creates a pair list with collectingAndThen, toList(), and PairList::new
*/
@Test
public void example() {
List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
.collect(toPairList());
System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
.collect(toPairList());
System.out.println(stringPairs); // [[a, b], [b, c]]
}
}
Это не естественная посадка, но вы можете сделать
List input = ...
List<Pair> pairs = IntStream.range(0, input.size() / 2)
.map(i -> i * 2)
.mapToObj(i -> new Pair(input.get(i), input.get(i + 1)))
.collect(Collectors.toList());
Чтобы создать пары, когда вы идете в потоке, вам нужны лямбды с сохранением состояния, которых обычно следует избегать, но это можно сделать. Примечание: это будет работать, только если поток однопоточный. т.е. не параллельно.
Stream<?> stream =
assert !stream.isParallel();
Object[] last = { null };
List<Pair> pairs = stream.map(a -> {
if (last[0] == null) {
last[0] = a;
return null;
} else {
Object t = last[0];
last[0] = null;
return new Pair(t, a);
}
}).filter(p -> p != null)
.collect(Collectors.toList());
assert last[0] == null; // to check for an even number input.
Предполагая, что есть Pair
с left
, right
и получатели и конструктор:
static class Paired<T> extends AbstractSpliterator<Pair<T>> {
private List<T> list = new ArrayList<>(2);
private final Iterator<T> iter;
public Paired(Iterator<T> iter) {
super(Long.MAX_VALUE, 0);
this.iter = iter;
}
@Override
public boolean tryAdvance(Consumer<? super Pair<T>> consumer) {
getBothIfPossible(iter);
if (list.size() == 2) {
consumer.accept(new Pair<>(list.remove(0), list.remove(0)));
return true;
}
return false;
}
private void getBothIfPossible(Iterator<T> iter) {
while (iter.hasNext() && list.size() < 2) {
list.add(iter.next());
}
}
}
Использование будет:
Iterator<Integer> iterator = List.of(1, 2, 3, 4, 5).iterator();
Paired<Integer> p = new Paired<>(iterator);
StreamSupport.stream(p, false)
.forEach(pair -> System.out.println(pair.getLeft() + " " + pair.getRight()));
Я знаю, что опаздываю на вечеринку, но все ответы кажутся действительно сложными или содержат много GC-заголовков / недолговечных объектов (что не так уж важно для современных JVM), но почему бы не сделать это просто как это?
public class PairCollaterTest extends TestCase {
static class PairCollater<T> implements Function<T, Stream<Pair<T, T>>> {
T prev;
@Override
public Stream<Pair<T, T>> apply(T curr) {
if (prev == null) {
prev = curr;
return Stream.empty();
}
try {
return Stream.of(Pair.of(prev, curr));
} finally {
prev = null;
}
}
}
public void testPairCollater() {
Stream.of("0", "1", "2", "3", "4", "5").sequential().flatMap(new PairCollater<>()).forEach(System.out::println);
}
}
Печать:
(0,1)
(2,3)
(4,5)
Просто замени IntStream.range(1, 101)
с вашим потоком (вам не нужно знать размер вашего потока) -
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class TestClass {
public static void main(String[] args) {
final Pair pair = new Pair();
final List<Pair> pairList = new ArrayList<>();
IntStream.range(1, 101)
.map(i -> {
if (pair.a == null) {
pair.a = i;
return 0;
} else {
pair.b = i;
return 1;
}
})
.filter(i -> i == 1)
.forEach(i -> {
pairList.add(new Pair(pair));
pair.reset();
});
pairList.stream().forEach(p -> System.out.print(p + " "));
}
static class Pair {
public Object a;
public Object b;
public Pair() {
}
public Pair(Pair orig) {
this.a = orig.a;
this.b = orig.b;
}
void reset() {
a = null;
b = null;
}
@Override
public String toString() {
return "{" + a + "," + b + '}';
}
}
}