From 7ac69b3ad7e2be3398b9daee72954ffa0efb36c3 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 27 Nov 2018 14:21:26 +0000 Subject: [PATCH] JENA-1641: Iter.flatMap. --- .../org/apache/jena/atlas/iterator/Iter.java | 17 +++++ .../jena/atlas/iterator/IteratorFlatMap.java | 69 +++++++++++++++++ .../jena/atlas/iterator/IteratorFlatten.java | 76 +++++++++++++++++++ .../apache/jena/atlas/iterator/TestIter.java | 33 ++++++++ 4 files changed, 195 insertions(+) create mode 100644 jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatMap.java create mode 100644 jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatten.java diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java index 5e615ed8e9f..797f3d5e7bb 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java @@ -287,6 +287,19 @@ public static List map(List list, Function converte return toList(map(list.iterator(), converter)) ; } + /** + * Apply a function to every element of an iterator, to produce possibly multiple mapping each time. + * See {@link Stream#flatMap} + */ + public static Iterator flatMap(Iterator iter, Function> mapper) { + // Combined mapping and flattening + return new IteratorFlatMap<>(iter, mapper); + // For reference: an alternative splitting the mapping out: + // Iterator> pipeline = Iter.map(iter, mapper); + // Iterator outcome = new IteratorFlatten<>(pipeline); + // IteratorFlatten is only one line and one field less complicated than IteratorFlatMap + } + /** * Apply an action to everything in stream, yielding a stream of the * same items. @@ -777,6 +790,10 @@ public Iter map(Function converter) { return iter(map(iterator, converter)) ; } + /** FlatMap each element using given function of element to iterator of mapped elements.s */ + public Iter flatMap(Function> converter) { + return iter(flatMap(iterator, converter)) ; + } /** * Apply an action to everything in the stream, yielding a stream of the * original items. diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatMap.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatMap.java new file mode 100644 index 00000000000..f223cffbcef --- /dev/null +++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatMap.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.atlas.iterator; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Stream; + +/** flatMap iterator. + * See {@link Stream#flatMap} + */ +public class IteratorFlatMap implements Iterator { + private boolean finished = false; + private Iterator current = null; + private Iterator input; + final private Function> mapper; + + public IteratorFlatMap(Iterator iter, Function> mapper) { + this.input = iter; + this.mapper = mapper ; + } + + @Override + public boolean hasNext() { + if ( finished ) + return false; + // !finished and current == null : happens at the start. + if ( current != null && current.hasNext() ) + return true; + // Stage finished or this is the first call. + //current = null; + while ( input.hasNext() ) { + IN x = input.next(); + current = mapper.apply(x); + if ( current == null || ! current.hasNext() ) + continue; + // There is at least one item in the new current stage. + return true; + } + // Nothing more. + current = null; + finished = true; + return false; + } + + @Override + public OUT next() { + if ( !hasNext() ) + throw new NoSuchElementException(); + return current.next(); + } +} diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatten.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatten.java new file mode 100644 index 00000000000..531625271c1 --- /dev/null +++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorFlatten.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.atlas.iterator; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** Iterator that flattens an iterator of iterators ({@code Iterator>}). */ +public class IteratorFlatten implements Iterator { + private boolean finished = false; + private Iterator current = null; + final private Iterator> pipeline; + + public IteratorFlatten(Iterator> pipeline) { + this.pipeline = pipeline; + } + + @Override + public boolean hasNext() { + if ( finished ) + return false; + // !finished and current == null : happens at the start. + if ( current != null && current.hasNext() ) + return true; + // Stage finished or this is the first call. + while(pipeline.hasNext()) { + current = pipeline.next(); + if ( current == null || ! current.hasNext()) + continue; + // There is at least one item in the new current stage. + return true; + } + // Nothing more. + current = null; + finished = true; + return false; + } + + @Override + public X next() { + if ( !hasNext() ) + throw new NoSuchElementException(); + return current.next(); + } + +// /** Advance an iterator, skipping nulls. +// * Return null iff the iterator has ended. +// * @implNote +// * Unlike a filtering out null from an iterator (e.g. {@link Iter#filter(Iterator, Predicate)}), +// * this code does not create intermediate objects. +// */ +// private static X /*Iter.*/advance(Iterator iter) { +// while (iter.hasNext()) { +// X item = iter.next(); +// if ( item != null ) +// return item; +// } +// return null; +// } +} diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java index 13ba84b2b2d..c80324011f6 100644 --- a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java +++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue ; import java.util.* ; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.IntStream; @@ -195,6 +196,38 @@ public void map_01() { test(it, "xx", "yy", "zz"); } + @Test + public void flatmap_01() { + Iterator it = Iter.flatMap(data2.iterator(), item -> Arrays.asList(item+item, item).iterator()); + test(it, "xx", "x", "yy", "y", "zz", "z"); + } + + @Test + public void flatmap_02() { + List data = Arrays.asList(1,2,3); + Iterator it = Iter.flatMap(data.iterator(), x -> { + if ( x == 2 ) return Iter.nullIterator(); + return Arrays.asList(x*x).iterator(); + }); + test(it, 1, 9); + } + + @Test + public void flatmap_03() { + List data = Arrays.asList(1,2,3); + Function> mapper = x -> { + switch(x) { + case 1: return Iter.nullIterator(); + case 2: return Arrays.asList("two").iterator(); + case 3: return Iter.nullIterator(); + default: throw new IllegalArgumentException(); + } + }; + + Iter it = Iter.iter(data.iterator()).flatMap(mapper); + test(it, "two"); + } + private Predicate filter = item -> item.length() == 1; @Test