-
Notifications
You must be signed in to change notification settings - Fork 1.2k
FpKit now longer uses streams for performance reasons #3932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| import java.lang.reflect.Array; | ||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.Iterator; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
|
|
@@ -18,69 +19,90 @@ | |
| import java.util.OptionalInt; | ||
| import java.util.Set; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.function.BiFunction; | ||
| import java.util.function.BinaryOperator; | ||
| import java.util.function.Function; | ||
| import java.util.function.Predicate; | ||
| import java.util.function.Supplier; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static java.util.Collections.singletonList; | ||
| import static java.util.function.Function.identity; | ||
| import static java.util.stream.Collectors.mapping; | ||
|
|
||
| @Internal | ||
| public class FpKit { | ||
|
|
||
| // | ||
| // From a list of named things, get a map of them by name, merging them according to the merge function | ||
| public static <T> Map<String, T> getByName(List<T> namedObjects, Function<T, String> nameFn, BinaryOperator<T> mergeFunc) { | ||
| return namedObjects.stream().collect(Collectors.toMap( | ||
| nameFn, | ||
| identity(), | ||
| mergeFunc, | ||
| LinkedHashMap::new) | ||
| ); | ||
| return toMap(namedObjects, nameFn, mergeFunc); | ||
| } | ||
|
|
||
| // | ||
| // From a collection of keyed things, get a map of them by key, merging them according to the merge function | ||
| public static <T, NewKey> Map<NewKey, T> toMap(Collection<T> collection, Function<T, NewKey> keyFunction, BinaryOperator<T> mergeFunc) { | ||
| Map<NewKey, T> resultMap = new LinkedHashMap<>(); | ||
| for (T obj : collection) { | ||
| NewKey key = keyFunction.apply(obj); | ||
| if (resultMap.containsKey(key)) { | ||
| T existingValue = resultMap.get(key); | ||
| T mergedValue = mergeFunc.apply(existingValue, obj); | ||
| resultMap.put(key, mergedValue); | ||
| } else { | ||
| resultMap.put(key, obj); | ||
| } | ||
| } | ||
| return resultMap; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new common generic function - used in other places |
||
| } | ||
|
|
||
| // normal groupingBy but with LinkedHashMap | ||
| public static <T, NewKey> Map<NewKey, ImmutableList<T>> groupingBy(Collection<T> list, Function<T, NewKey> function) { | ||
| return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); | ||
| return filterAndGroupingBy(list, ALWAYS_TRUE, function); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| public static <T, NewKey> Map<NewKey, ImmutableList<T>> filterAndGroupingBy(Collection<T> list, | ||
| Predicate<? super T> predicate, | ||
| Function<T, NewKey> function) { | ||
| return list.stream().filter(predicate).collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); | ||
| } | ||
| // | ||
| // The cleanest version of this code would have two maps, one of immutable list builders and one | ||
| // of the built immutable lists. BUt we are trying to be performant and memory efficient so | ||
| // we treat it as a map of objects and cast like its Java 4x | ||
| // | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment above explains the whacky code. One less map allocation Alternative is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note two allocations - neater code - worse performance |
||
| Map<NewKey, Object> resutMap = new LinkedHashMap<>(); | ||
| for (T item : list) { | ||
| if (predicate.test(item)) { | ||
| NewKey key = function.apply(item); | ||
| // we have to use an immutable list builder as we built it out | ||
| ((ImmutableList.Builder<Object>) resutMap.computeIfAbsent(key, k -> ImmutableList.builder())) | ||
| .add(item); | ||
| } | ||
| } | ||
| if (resutMap.isEmpty()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you meant to call it a |
||
| return Collections.emptyMap(); | ||
| } | ||
| // Convert builders to ImmutableLists in place to avoid an extra allocation | ||
| // yes the code is yuck - but its more performant yuck! | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mind if I borrow this line for the release notes? |
||
| resutMap.replaceAll((key, builder) -> | ||
| ((ImmutableList.Builder<Object>) builder).build()); | ||
|
|
||
| public static <T, NewKey> Map<NewKey, ImmutableList<T>> groupingBy(Stream<T> stream, Function<T, NewKey> function) { | ||
| return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); | ||
| // make it the right shape - like as if generics were never invented | ||
| return (Map<NewKey, ImmutableList<T>>) (Map<?, ?>) resutMap; | ||
| } | ||
|
|
||
| public static <T, NewKey> Map<NewKey, T> groupingByUniqueKey(Collection<T> list, Function<T, NewKey> keyFunction) { | ||
| return list.stream().collect(Collectors.toMap( | ||
| keyFunction, | ||
| identity(), | ||
| throwingMerger(), | ||
| LinkedHashMap::new) | ||
| ); | ||
| public static <T, NewKey> Map<NewKey, T> toMapByUniqueKey(Collection<T> list, Function<T, NewKey> keyFunction) { | ||
| return toMap(list, keyFunction, throwingMerger()); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was renamed from |
||
|
|
||
| public static <T, NewKey> Map<NewKey, T> groupingByUniqueKey(Stream<T> stream, Function<T, NewKey> keyFunction) { | ||
| return stream.collect(Collectors.toMap( | ||
| keyFunction, | ||
| identity(), | ||
| throwingMerger(), | ||
| LinkedHashMap::new) | ||
| ); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. never used - so I removed it - one less stream thing to port |
||
|
|
||
| private static final Predicate<Object> ALWAYS_TRUE = o -> true; | ||
|
|
||
| private static final BinaryOperator<Object> THROWING_MERGER_SINGLETON = (u, v) -> { | ||
| throw new IllegalStateException(String.format("Duplicate key %s", u)); | ||
| }; | ||
|
|
||
|
|
||
| private static <T> BinaryOperator<T> throwingMerger() { | ||
| return (u, v) -> { | ||
| throw new IllegalStateException(String.format("Duplicate key %s", u)); | ||
| }; | ||
| //noinspection unchecked | ||
| return (BinaryOperator<T>) THROWING_MERGER_SINGLETON; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -240,11 +262,6 @@ public static <T> List<T> valuesToList(Map<?, T> map) { | |
| return new ArrayList<>(map.values()); | ||
| } | ||
|
|
||
| public static <K, V, U> List<U> mapEntries(Map<K, V> map, BiFunction<K, V, U> function) { | ||
| return map.entrySet().stream().map(entry -> function.apply(entry.getKey(), entry.getValue())).collect(Collectors.toList()); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. never used so I removed it - one less stream thing to port |
||
|
|
||
|
|
||
| public static <T> List<List<T>> transposeMatrix(List<? extends List<T>> matrix) { | ||
| int rowCount = matrix.size(); | ||
| int colCount = matrix.get(0).size(); | ||
|
|
@@ -261,21 +278,13 @@ public static <T> List<List<T>> transposeMatrix(List<? extends List<T>> matrix) | |
| return result; | ||
| } | ||
|
|
||
| public static <T> CompletableFuture<List<T>> flatList(CompletableFuture<List<List<T>>> cf) { | ||
| return cf.thenApply(FpKit::flatList); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. never used - no need to port it |
||
|
|
||
| public static <T> List<T> flatList(Collection<List<T>> listLists) { | ||
| return listLists.stream() | ||
| .flatMap(List::stream) | ||
| .collect(ImmutableList.toImmutableList()); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. never used - no need to port it |
||
|
|
||
| public static <T> Optional<T> findOne(Collection<T> list, Predicate<T> filter) { | ||
| return list | ||
| .stream() | ||
| .filter(filter) | ||
| .findFirst(); | ||
| for (T t : list) { | ||
| if (filter.test(t)) { | ||
| return Optional.of(t); | ||
| } | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| public static <T> T findOneOrNull(List<T> list, Predicate<T> filter) { | ||
|
|
@@ -292,10 +301,13 @@ public static <T> int findIndex(List<T> list, Predicate<T> filter) { | |
| } | ||
|
|
||
| public static <T> List<T> filterList(Collection<T> list, Predicate<T> filter) { | ||
| return list | ||
| .stream() | ||
| .filter(filter) | ||
| .collect(Collectors.toList()); | ||
| List<T> result = new ArrayList<>(); | ||
| for (T t : list) { | ||
| if (filter.test(t)) { | ||
| result.add(t); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| public static <T> Set<T> filterSet(Collection<T> input, Predicate<T> filter) { | ||
|
|
@@ -352,9 +364,10 @@ public static <T> Supplier<T> interThreadMemoize(Supplier<T> delegate) { | |
| /** | ||
| * Faster set intersection. | ||
| * | ||
| * @param <T> for two | ||
| * @param <T> for two | ||
| * @param set1 first set | ||
| * @param set2 second set | ||
| * | ||
| * @return intersection set | ||
| */ | ||
| public static <T> Set<T> intersection(Set<T> set1, Set<T> set2) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| package graphql.util | ||
|
|
||
|
|
||
| import com.google.common.collect.ImmutableList | ||
| import spock.lang.Specification | ||
|
|
||
| import java.util.function.Supplier | ||
|
|
@@ -98,6 +98,100 @@ class FpKitTest extends Specification { | |
| l == ["Parrot"] | ||
| } | ||
|
|
||
| class Person { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hey look - TESTS! We never had very many tests for FpKit So I I write them first - then changed the implementation TDD!!!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noice |
||
| String name | ||
| String city | ||
|
|
||
| Person(String name) { | ||
| this.name = name | ||
| } | ||
|
|
||
| Person(String name, String city) { | ||
| this.name = name | ||
| this.city = city | ||
| } | ||
|
|
||
| String getName() { | ||
| return name | ||
| } | ||
|
|
||
| String getCity() { | ||
| return city | ||
| } | ||
| } | ||
|
|
||
| def a = new Person("a", "New York") | ||
| def b = new Person("b", "New York") | ||
| def c1 = new Person("c", "Sydney") | ||
| def c2 = new Person("c", "London") | ||
|
|
||
| def "getByName tests"() { | ||
|
|
||
| when: | ||
| def map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() }) | ||
| then: | ||
| map == ["a": a, "b": b, c: c1] | ||
|
|
||
| when: | ||
| map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() }, { it1, it2 -> it2 }) | ||
| then: | ||
| map == ["a": a, "b": b, c: c2] | ||
| } | ||
|
|
||
| def "groupingBy tests"() { | ||
|
|
||
| when: | ||
| Map<String, ImmutableList<Person>> map = FpKit.groupingBy([a, b, c1, c2], { it -> it.getCity() }) | ||
| then: | ||
| map == ["New York": [a, b], "Sydney": [c1], "London": [c2]] | ||
|
|
||
| when: | ||
| map = FpKit.filterAndGroupingBy([a, b, c1, c2], { it -> it != c1 }, { it -> it.getCity() }) | ||
| then: | ||
| map == ["New York": [a, b], "London": [c2]] | ||
|
|
||
| } | ||
|
|
||
| def "toMapByUniqueKey works"() { | ||
|
|
||
| when: | ||
| Map<String, Person> map = FpKit.toMapByUniqueKey([a, b, c1], { it -> it.getName() }) | ||
| then: | ||
| map == ["a": a, "b": b, "c": c1] | ||
|
|
||
| when: | ||
| FpKit.toMapByUniqueKey([a, b, c1, c2], { it -> it.getName() }) | ||
| then: | ||
| def e = thrown(IllegalStateException.class) | ||
| e.message.contains("Duplicate key") | ||
| } | ||
|
|
||
| def "findOne test"() { | ||
| when: | ||
| def opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "c" }) | ||
| then: | ||
| opt.isPresent() | ||
| opt.get() == c1 | ||
|
|
||
| when: | ||
| opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "d" }) | ||
| then: | ||
| opt.isEmpty() | ||
|
|
||
| when: | ||
| opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "a" }) | ||
| then: | ||
| opt.isPresent() | ||
| opt.get() == a | ||
| } | ||
|
|
||
| def "filterList works"() { | ||
| when: | ||
| def list = FpKit.filterList([a, b, c1, c2], { it -> it.getName() == "c" }) | ||
| then: | ||
| list == [c1, c2] | ||
| } | ||
|
|
||
| def "set intersection works"() { | ||
| def set1 = ["A","B","C"] as Set | ||
| def set2 = ["A","C","D"] as Set | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it one less statement by looking up the value and save it one call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically not the right semantics in a general sense - sure there could be non