[英]Processing a collection taking two adjacent elements at a time (use of stream)
[英]Java Stream: is there a way to iterate taking two elements a time instead of one?
假设我们有这个流
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j");
我想在地图中保存一对相邻的字符串,其中第一个字符串以“err”开头。
我想到的是这样的
Map<String, String> map = new HashMap<>();
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.reduce((acc, next) -> {
if (acc.startsWith("err"))
map.put(acc,next);
if (next.startsWith("err"))
return next;
else
return "";
});
但我对它并不完全满意,主要有两个原因
reduce
功能。 在 Stream API 中,每个函数都有其明确定义的用途: max
应该计算最大值, filter
应该根据条件进行过滤, reduce
应该产生增量累加值等等。 在这里我使用reduce
是因为(据我所知)它是唯一可以让你比较几个值的函数,你可以以某种方式返回类似于“当前值”和“下一个值”概念的东西。
有没有更直接的方法? 允许您在每次迭代中考虑多个值来迭代流的东西?
我正在考虑的是某种机制,在给定当前元素的情况下,允许您为每次迭代定义一个“元素窗口”以供考虑。
就像是
<R> Stream<R> mapMoreThanOne(
int elementsBeforeCurrent,
int elementsAfterCurrent,
Function<List<? super T>, ? extends R> mapper);
代替
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
这将是对当前 API 的强大“升级”。
我感谢人们提出解决方案的努力,但问题不在于算法本身。 通过将流、索引、临时变量放在一起来存储以前的值,有不同的方法可以实现我的目标......但我想知道 Stream API 中是否有一些方法是为处理当前元素以外的元素而设计的不打破“流范式”。 像这样的东西
List<String> list =
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.filterFunctionImWonderingIfExist(/*filters couples of elements*/)
.limit(2)
.collect(Collectors.toList());
鉴于答案,我认为没有“清晰而快速”的解决方案,除非使用 StreamEx 库
您可以为此任务构建自定义Collector
。
Map<String, String> map =
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(MappingErrors.collector());
和:
private static final class MappingErrors {
private Map<String, String> map = new HashMap<>();
private String first, second;
public void accept(String str) {
first = second;
second = str;
if (first != null && first.startsWith("err")) {
map.put(first, second);
}
}
public MappingErrors combine(MappingErrors other) {
throw new UnsupportedOperationException("Parallel Stream not supported");
}
public Map<String, String> finish() {
return map;
}
public static Collector<String, ?, Map<String, String>> collector() {
return Collector.of(MappingErrors::new, MappingErrors::accept, MappingErrors::combine, MappingErrors::finish);
}
}
在这个收集器中,保留了两个运行元素。 每次接受String
,它们都会更新,如果第一个以"err"
开头,则将两个元素添加到映射中。
另一种解决方案是使用StreamEx库,它提供了一个pairMap
方法,该方法将给定的函数应用于此流的每个相邻元素对。 在以下代码中,如果第一个元素以"err"
开头,则该操作返回一个 String 数组,该数组由该对的第一个和第二个元素组成,否则返回null
。 然后过滤掉null
元素,并将 Stream 收集到映射中。
Map<String, String> map =
StreamEx.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.pairMap((s1, s2) -> s1.startsWith("err") ? new String[] { s1, s2 } : null)
.nonNull()
.toMap(a -> a[0], a -> a[1]);
System.out.println(map);
您可以编写自定义收集器,或使用更简单的方法来流式传输列表的索引:
Map<String, String> result = IntStream.range(0, data.size() - 1)
.filter(i -> data.get(i).startsWith("err"))
.boxed()
.collect(toMap(data::get, i -> data.get(i+1)));
这假设您的数据位于随机访问友好列表中,或者您可以暂时将其转储到一个列表中。
如果您不能随机访问数据或将其加载到列表或数组中进行处理,您可以随时制作自定义pairing
收集器,以便您可以编写
Map<String, String> result = data.stream()
.collect(pairing(
(a, b) -> a.startsWith("err"),
AbstractMap.SimpleImmutableEntry::new,
toMap(Map.Entry::getKey, Map.Entry::getValue)
));
这是收集器的来源。 它是并行友好的,在其他情况下可能会派上用场:
public static <T, V, A, R> Collector<T, ?, R> pairing(BiPredicate<T, T> filter, BiFunction<T, T, V> map, Collector<? super V, A, R> downstream) {
class Pairing {
T left, right;
A middle = downstream.supplier().get();
boolean empty = true;
void add(T t) {
if (empty) {
left = t;
empty = false;
} else if (filter.test(right, t)) {
downstream.accumulator().accept(middle, map.apply(right, t));
}
right = t;
}
Pairing combine(Pairing other) {
if (!other.empty) {
this.add(other.left);
this.middle = downstream.combiner().apply(this.middle, other.middle);
this.right = other.right;
}
return this;
}
R finish() {
return downstream.finisher().apply(middle);
}
}
return Collector.of(Pairing::new, Pairing::add, Pairing::combine, Pairing::finish);
}
如果您的输入位于随机访问列表中,事情会更容易。 通过这种方式,您可以像这样使用旧的List.subList
方法:
List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e",
"f", "g", "h", "err3", "i", "j");
Map<String, String> map = IntStream.range(0, list.size()-1)
.mapToObj(i -> list.subList(i, i+2))
.filter(l -> l.get(0).startsWith("err"))
.collect(Collectors.toMap(l -> l.get(0), l -> l.get(1)));
同样的事情可以用已经提到的 StreamEx 库(由我编写)以更短的方式完成:
List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e",
"f", "g", "h", "err3", "i", "j");
Map<String, String> map = StreamEx.ofSubLists(list, 2, 1)
.mapToEntry(l -> l.get(0), l -> l.get(1))
.filterKeys(key -> key.startsWith("err"))
.toMap();
虽然如果你不想要第三方依赖,糟糕的 Stream API 解决方案看起来也不是很糟糕。
使用Collector.of
和List<List<String>>
作为收集对的结构的其他方法。 首先收集到List<List<String>>
:
List<List<String>> collect = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(
Collector.of(
LinkedList::new,
(a, b) -> {
if (b.startsWith("err"))
a.add(new ArrayList<>(List.of(b)));
else if (!a.isEmpty() && a.getLast().size() == 1)
a.getLast().add(b);
},
(a, b) -> { throw new UnsupportedOperationException(); }
)
);
然后它可以转换为映射
Map<String, String> toMap = collect.stream().filter(l -> l.size() == 2)
.collect(Collectors.toMap(
e -> e.get(0),
e -> e.get(1))
);
或者与Collectors.collectingAndThen
合而为一
Map<String, String> toMap = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(Collectors.collectingAndThen(
Collector.of(
LinkedList<List<String>>::new,
(a, b) -> {
if (b.startsWith("err"))
a.add(new ArrayList<>(List.of(b)));
else if (!a.isEmpty() && a.getLast().size() == 1)
a.getLast().add(b);
},
(a, b) -> { throw new UnsupportedOperationException(); }
), (x) -> x.stream().filter(l -> l.size() == 2)
.collect(Collectors.toMap(
e -> e.get(0),
e -> e.get(1))
)
));
这是一个使用现成收集器的简单衬里:
Stream<String> stream = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j");
Map<String, String> map = Arrays.stream(stream
.collect(Collectors.joining(",")).split(",(?=(([^,]*,){2})*[^,]*$)"))
.filter(s -> s.startsWith("err"))
.map(s -> s.split(","))
.collect(Collectors.toMap(a -> a[0], a -> a[1]));
这里的“技巧”是首先将所有术语连接到一个字符串中,然后将其拆分为成对的字符串,例如"a,b"
, "err1,c"
等。一旦你有了一个对流,处理是直截了当。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.