是否有一种简洁的方法可以在访问流中的索引的同时迭代流?
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();
有没有更简洁的方法?
此外,似乎拉链已经移动或被移除......
intRange()
是什么?直到现在还没有在 Java 8 中遇到过这种方法。
IntStream.rangeClosed(x, y)
。
List<String> allCities = map.values().stream().flatMap(list -> list.stream()).collect(Collectors.toList());
的挑战 4 看起来更好 (IMO)
zip
以及各种称为 BiStream
或 MapStream
的实验性二值流。主要问题是,要有效地做到这一点,Java 确实需要一个结构类型的对(或元组)类型。缺少一个,很容易创建一个通用的 Pair 或 Tuple 类——已经做过很多次了——但它们都擦除为相同的类型。
最干净的方法是从索引流开始:
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”。
当您习惯于 for 循环时,一种看起来更熟悉的替代方法是使用可变对象维护一个临时计数器,例如 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
来融合过滤器和映射操作,但这很麻烦并且没有任何优势。我已经根据 Holger 的评论更新了示例。
IntStream.range(0, names.length).filter(i->names[i].length()<=i).mapToObj(i->names[i])
怎么样?它在没有拳击的情况下也能工作……
flatMap
?
flatMap
,因为它将过滤和映射操作融合到一个操作中,但这确实没有任何优势。我将编辑示例。
Stream.of( names ).filter( n -> n.length() <= 1).collect( Collectors.toList() );
更少的拆箱和更少的内存分配;因为我们不再创建范围流。
从番石榴 21 开始,您可以使用
Streams.mapWithIndex()
示例(来自 official doc):
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));
}
}
StreamSupport.stream()
和自定义迭代器每 N 个索引“插入”一个元素。
除了 protonpack,jOOλ's Seq 还提供了这个功能(以及像 cyclops-react 这样构建在它之上的扩展库,我是这个库的作者)。
Seq.seq(Stream.of(names)).zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
Seq 还仅支持 Seq.of(names) 并将在幕后构建一个 JDK Stream。
简单反应等价物看起来类似
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
simple-react 版本更适合异步/并发处理。
为了完整起见,这里是涉及我的 StreamEx 库的解决方案:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
在这里,我们创建了一个 EntryStream<Integer, String>
,它扩展了 Stream<Entry<Integer, String>>
并添加了一些特定的操作,例如 filterKeyValue
或 values
。还使用了 toList()
快捷方式。
.forEach(entry -> {})
有快捷方式吗?
.forKeyValue((key, value) -> {})
。
当 Stream 由列表或数组创建时(并且您知道大小),我在这里找到了解决方案。但是如果 Stream 的大小未知怎么办?在这种情况下,试试这个变体:
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
不同于任何 Collection
。 Stream
只是将数据从一个地方传送到另一个地方的管道,如 documentation 中所述:
没有存储。流不是存储元素的数据结构;相反,它们通过计算操作的管道从源(可以是数据结构、生成器、IO 通道等)携带值。
当然,正如您在问题中暗示的那样,您始终可以将 Stream<V>
转换为 Collection<V>
,例如 List<V>
,您可以在其中访问索引。
使用 https://github.com/poetix/protonpack,您可以执行该压缩:
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 有 zipWithIndex
和 forEachWithIndex
可用于多种类型。下面是一组使用 zipWithIndex
针对 JDK 类型和 Eclipse Collections 类型的挑战的解决方案。
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);
如果您将 lambda 更改为上面的匿名内部类,那么所有这些代码示例也将在 Java 5 - 7 中运行。
注意:我是 Eclipse Collections 的提交者
如果您尝试基于谓词获取索引,请尝试以下操作:
如果您只关心第一个索引:
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);
以防您在找不到值时返回它。
IntStream.range(0, list.size()).forEach(i -> list.get(i).setResult(resultArray[i]));
您可以使用 IntStream.iterate()
获取索引:
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());
一种可能的方法是索引流中的每个元素:
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));
在流中使用匿名类并没有很好地使用,但非常有用。
如果您需要 forEach 中的索引,那么这提供了一种方法。
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()));
});
}
您不需要 map
必然
这是最接近 LINQ 示例的 lambda:
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 matching 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);
输出:山姆、帕梅拉、戴夫、帕斯卡、埃里克
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);
List
。
正如 jean-baptiste-yunès 所说,如果您的流基于 java List,那么使用 AtomicInteger 及其 incrementAndGet 方法是解决问题的一个很好的方法,只要您返回的整数确实对应于原始 List 中的索引不要使用并行流。
这是标准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);
不定期副业成功案例分享
parallel
或sequential
被接受。