diff --git a/src/main/java/one/util/streamex/EntryStream.java b/src/main/java/one/util/streamex/EntryStream.java index 7f4e408a..2640f9d1 100644 --- a/src/main/java/one/util/streamex/EntryStream.java +++ b/src/main/java/one/util/streamex/EntryStream.java @@ -41,6 +41,7 @@ import java.util.function.BinaryOperator; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collector; @@ -2284,4 +2285,85 @@ public static EntryStream generate(Supplier keySupplie Stream.generate(() -> new SimpleImmutableEntry<>(keySupplier.get(), valueSupplier.get())), StreamContext.SEQUENTIAL); } + + /** + * Returns an {@code EntryStream} consisting of the elements of this stream + * whose keys are not equal to any of supplied keys. + * + *

+ * This is an intermediate operation. + * May return itself if no keys were supplied. + * + *

+ * Current implementation scans the supplied keys linearly for every stream element. + * If you have many keys, consider using more efficient alternative instead. + * + *

+ * Future implementations may take advantage on using {@code hashCode()} or + * {@code compareTo} for {@code Comparable} objects to improve the performance. + * + *

+ * If the {@code keys} array is changed between calling this method and finishing the stream traversal, + * then the result of the stream traversal is undefined: changes may or may not be taken into account. + * + * @param keys the keys to remove from the stream. + * @return the new stream + * @since 0.7.4 + * @see #withoutValues(Object...) + * @see StreamEx#without(Object...) + */ + public EntryStream withoutKeys(K... keys) { + if (keys.length == 0) + return this; + if (keys.length == 1) + return filter(entry -> !entry.getKey().equals(keys[0])); + return filter(entry -> { + for (K key : keys) { + if (entry.getKey().equals(key)) + return false; + } + return true; + }); + } + + /** + * Returns an {@code EntryStream} consisting of the elements of this stream + * whose values are not equal to any of the supplied values. + * + *

+ * This is an intermediate operation. + * May return itself if no values were supplied. + * + *

+ * Current implementation scans the supplied values linearly for every stream element. + * If you have many values, consider using more efficient alternative instead. + * + *

+ * Future implementations may take advantage on using {@code hashCode()} or + * {@code compareTo} for {@code Comparable} objects to improve the performance. + * + *

+ * If the {@code values} array is changed between calling this method and finishing the stream traversal, + * then the result of the stream traversal is undefined: changes may or may not be taken into account. + * + * @param values the values to remove from the stream. + * @return the new stream + * @since 0.7.4 + * @see #withoutKeys(Object...) + * @see StreamEx#without(Object...) + */ + public EntryStream withoutValues(V... values) { + if (values.length == 0) + return this; + if (values.length == 1) + return filter(entry -> !entry.getValue().equals(values[0])); + return filter(entry -> { + for (V value : values) { + if (entry.getValue().equals(value)) + return false; + } + return true; + }); + } + } diff --git a/src/test/java/one/util/streamex/api/EntryStreamTest.java b/src/test/java/one/util/streamex/api/EntryStreamTest.java index 2b3b18c5..9ee88dcd 100644 --- a/src/test/java/one/util/streamex/api/EntryStreamTest.java +++ b/src/test/java/one/util/streamex/api/EntryStreamTest.java @@ -814,4 +814,68 @@ public void testPrefixValues() { Map map = EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4).prefixKeys(String::concat).toMap(); assertEquals(EntryStream.of("a", 1, "ab", 2, "abc", 3, "abcd", 4).toMap(), map); } + + @Test + public void testWithoutKeys() { + assertEquals("Stream is not changed", + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutKeys().withoutKeys().withoutKeys().toMap()); + + assertEquals(EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutKeys().withoutKeys("A").withoutKeys("B", "C","D").toMap()); + + entryStream(() -> EntryStream.of(1, "a", 1, "b", 2, "c", 3, "d", 1, "e", 1, "f", 1, "g"), + s -> checkAsString("2->c;3->d", s.get().withoutKeys(1))); + + assertEquals(EntryStream.of("b", 2, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutKeys("a").withoutKeys("c").toMap()); + + assertEquals(EntryStream.of("a", 1, "B", 4).toMap(), + EntryStream.of("a", 1, "A", 2, "b", 3, "B", 4) + .withoutKeys("A").withoutKeys("b").toMap()); + + entryStream(() -> EntryStream.generate(() -> "a", () -> 1).limit(10), + s -> checkAsString("", s.get().withoutKeys("a"))); + + entryStream(() -> EntryStream.of(1, "a", 1, "b", 2, "c", 3, "d", 4, "e", 4, "f", 1, "g"), + s -> checkAsString("2->c;3->d", s.get().withoutKeys(1, 4))); + assertEquals(EntryStream.of("b", 2, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutKeys("a", "c").toMap()); + } + + @Test + public void testWithoutValues() { + assertEquals("Stream is not changed", + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutValues().withoutValues().withoutValues().toMap()); + + assertEquals(EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutValues().withoutValues(6).withoutValues(7, 8).toMap()); + + entryStream(() -> EntryStream.of(1, "a", 1, "b", 2, "b", 3, "c", 4, "c", 5, "c", 6, "d"), + s -> checkAsString("1->a;1->b;2->b;6->d", s.get().withoutValues("c"))); + + assertEquals(EntryStream.of("c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutValues(1).withoutValues(2).toMap()); + + entryStream(() -> EntryStream.generate(() -> "a", () -> 1).limit(10), + s -> checkAsString("", s.get().withoutValues(1))); + + entryStream(() -> EntryStream.of(1, "a", 1, "b", 2, "b", 3, "c", 4, "c", 5, "c", 6, "d"), + s -> checkAsString("1->a;6->d", s.get().withoutValues("b", "c"))); + entryStream(() -> EntryStream.of(1, "a", 1, "A", 2, "b", 3, "B", 4, "c", 5, "C"), + s -> checkAsString("1->A;2->b;4->c;5->C", s.get().withoutValues("a", "B"))); + + assertEquals(EntryStream.of("c", 3, "d", 4).toMap(), + EntryStream.of("a", 1, "b", 2, "c", 3, "d", 4) + .withoutValues(1, 2).toMap()); + + } } diff --git a/wiki/CHANGES.md b/wiki/CHANGES.md index 3c22ca50..53098f38 100644 --- a/wiki/CHANGES.md +++ b/wiki/CHANGES.md @@ -4,6 +4,7 @@ Check also [MIGRATION.md](MIGRATION.md) for possible compatibility problems. ### 0.7.4 * [#091] Changed: API tests moved to the separate package. +* [#185] Added: `EntryStream.withoutKeys` and `EntryStream.withoutValues`. ### 0.7.3 * [#028] Added: `StreamEx.toCollectionAndThen`. @@ -13,14 +14,14 @@ Check also [MIGRATION.md](MIGRATION.md) for possible compatibility problems. * [#219] Changed: MoreCollectors now reject eagerly null parameters where possible; `MoreCollectors.last` throws NPE if last stream element is null. * [#221] Fixed: `rangeClosed(x, x, step)` returned empty stream instead of stream of `x` if step absolute value is bigger than one. * [#226] Added: `EntryStream.pairMap` (pairMap pulled up to AbstractStreamEx). -* [#229] Fixed: Some non-canonical nans were sorted incorrectly with `Double.reverseSorted()`. +* [#229] Fixed: some non-canonical nans were sorted incorrectly with `Double.reverseSorted()`. ### 0.7.2 -* Fixed: accidental use of Java 9 API in CrossSpliterator +* Fixed: accidental use of Java 9 API in CrossSpliterator. ### 0.7.1 -* [#202] Fixed: `StreamEx/EntryStream.ofTree` stack consumption is now limited -* Multi-release Jar is used to provide Java 9+ specializations +* [#202] Fixed: `StreamEx/EntryStream.ofTree` stack consumption is now limited. +* Multi-release Jar is used to provide Java 9+ specializations. ### 0.7.0 * [#193] Removed optimizations which rely on internal implementation details of Stream API (unwrap IteratorSpliterator; diff --git a/wiki/CHEATSHEET.md b/wiki/CHEATSHEET.md index 0da9d21c..26d62549 100644 --- a/wiki/CHEATSHEET.md +++ b/wiki/CHEATSHEET.md @@ -83,7 +83,9 @@ Stream of doubles from the `DoubleBuffer` | `DoubleStreamEx.of(DoubleBuffer)` What I want | How to get it --- | --- Remove nulls | `StreamEx/EntryStream.nonNull()` -Remove entries which keys or values are null | `EntryStream.nonNullKeys()/nonNullValues()` +Remove entries whose keys or values are null | `EntryStream.nonNullKeys()/nonNullValues()` +Remove entries whose keys are equal to any of the supplied keys | `EntryStream.withoutKeys()` +Remove entries whose values are equal to any of the supplied values | `EntryStream.withoutValues()` Remove elements by predicate | `any.remove()` Remove given elements | `StreamEx/IntStreamEx/LongStreamEx.without()` Remove by value extracted by supplied mapper function | `StreamEx.removeBy()`