I have a generic function which accepts Collection<? extends T> ts.
I'm also passing:
Function<? extends T, ? extends K> classifier which maps each item T to a key K (possible to have duplicates)
Function<? extends T, Integer> evaluator which gives an integer value for the item.
The function itself has a built-in calculation ("int to int") for every produced Integer (could be something like squaring for our example)
Finally, I'd like to sum all of the values for each key.
So the end result is: Map<K, Integer>.
For example,
Let's say we have the list ["a","a", "bb"] and we use Function.identity to classify, String::length to evaluate and squaring as the built-in function. Then the returned map will be: {"a": 2, "b": 4}
How can I do that? (I guess that preferably using Collectors.groupingBy)
Here's one way to do it:
public static <T,K> Map<K,Integer> mapper (
Collection<T> ts,
Function<T, K> classifier,
Function<T, Integer> evaluator,
Function<Integer,Integer> calculator)
{
return
ts.stream()
.collect(Collectors.groupingBy(classifier,
Collectors.summingInt(t->evaluator.andThen(calculator).apply(t))));
}
The output for:
System.out.println (mapper(Arrays.asList("a","a","bb"),Function.identity(),String::length,i->i*i));
is
{bb=4, a=2}
Or another approach:
private static <K, T> Map<K, Integer> map(Collection<? extends T> ts,
Function<? super T, ? extends K> classifier,
Function<? super T, Integer> evaluator,
Function<Integer, Integer> andThen) {
return ts.stream()
.collect(Collectors.groupingBy(
classifier,
Collectors.mapping(evaluator.andThen(andThen),
Collectors.reducing(0, Integer::sum))
));
}
And use it with:
public static void main(String[] args) {
System.out.println(map(
Arrays.asList("a", "a", "bb"),
Function.identity(),
String::length,
x -> x * x));
}
Related
I want to provide the user defined method to merge the map in java 8? The method you create should accept two maps and “merging” behavior.
public <T> Map<? super T, ? super T> mergeMaps( Map<? super T, ? super
T> map1, Map<? super T, ? super T> map2 ) {
// merging code here
}
but I want something like this
public <T> Map<? super T, ? super T> mergeMaps( Map<? super T, ? super T>
map1, Map<? super T, ? super T> map2 , MergeTwoMaps<T, U> mergeTwoMaps)
{
// merging code here
}
Something like this. Java generic merge
Map<String, Integer> map1 = new HashMap<>();
Map<String, Integer> map2 = new HashMap<>();
map1.put(“key1”, 20);
map1.put(“key2”, 30);
map2.put(“key3”, 40);
map2.put(“key1”, 50);
mergeMaps(map1,map2,mergeBehaviour)
Output should be
map.get("key1")--> 70
Map<String, String> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>();
map1.put(“key1”, "Hello");
map1.put(“key2”, "Hi");
map2.put(“key3”, "Ok");
map2.put(“key1”, "world");
mergeMaps(map1,map2,mergeBehaviour)
Output should be
map.get("key1")--> Helloworld
You should rethink your desired method signature. To cite the guidelines:
Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
In other words, having wildcards in the return types forces the caller to spread wildcards into every code using the returned result.
The method should rather look like:
public static <K, V> Map<K, V> mergeMaps(
Map<? extends K, ? extends V> map1, Map<? extends K, ? extends V> map2,
BinaryOperator<V> mergeFunction) {
Map<K, V> result = new HashMap<>(map1);
map2.forEach((k,v) -> result.merge(k, v, mergeFunction));
return result;
}
It allows the caller to pick arbitrary key and value types, as long as the input argument are compatible, which is always the case if the input maps’ key and value types are the same as the output map’s or subtypes of it. Of course, this signature also allows the use case where key and value types are the same, but it does not require it.
The implementation of the merge operation itself is trivial.
You can raise the method’s flexibility even more by using
public static <K, V> Map<K, V> mergeMaps(
Map<? extends K, ? extends V> map1, Map<? extends K, ? extends V> map2,
BiFunction<? super V, ? super V, ? extends V> mergeFunction) {
Map<K, V> result = new HashMap<>(map1);
map2.forEach((k,v) -> result.merge(k, v, mergeFunction));
return result;
}
though this is rarely needed, because it’s unnecessary when passing method references or lambda expressions. This only helps when reusing already existing BiFunction instances.
I have this class.
class Assignment {
private Integer index;
private List<Quantity> quantities;
}
Then, I have a list of objects from that class.
List<Assigment> assignments = new ArrayList<>();
Is there a way to create a Map that contains the index from Assignment and the List<Quantity> as values?
This is what I have tried so far.
assignments.stream().collect(groupingBy(Assignment::getIndex));
But this gives me a Map<Integer, List<Assignment>> and I want a Map<Integer, List<Quantity>>.
I have tried using forEach method - and it workes - but I'm sure there must be a way to do it in one liner - or at least using only collect and groupingBy methods
It looks like there is no flat-mapping collector that you can use as a down-stream for groupingBy in Java8, but it has been proposed and accepted for Java9: https://bugs.openjdk.java.net/browse/JDK-8071600
public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
return Collector.of(downstream.supplier(),
(r, t) -> mapper.apply(t).sequential().forEach(u -> downstreamAccumulator.accept(r, u)),
downstream.combiner(),
downstream.finisher(),
downstream.characteristics().stream().toArray(Collector.Characteristics[]::new));
}
If you use that one, and also add a quantities method to Assignment that returns a Stream<Quantity>, you can use this code:
Map<Integer, List<Quantity>> result = assignments.stream()
.collect(groupingBy(Assignment::getIndex,
flatMapping(Assignment::quantities, toList())));
In Function.class from Java8, we have:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
Compose accepts:
Function<? super V, ? extends T> before
Rather than:
Function<V, ? extends T> before
Is there any plausible situation in which the fact that "V" is lower bounded matters?
The ? super allows the returned Function's input type (V) to be different from the arguments input type.
For example, this compiles with the ? super version but not the alternate one.
Function<Object, String> before = Object::toString;
Function<String, Integer> after = Integer::parseInt;
Function<Integer, Integer> composed = after.compose(before);
I have a methods:
public List<Integer> convertBy(Function<String, List<String>> flines, Function<List<String>, String> join, Function<String, List<Integer>> collectInts) {
return collectInts.apply(join.apply(flines.apply((String) value)));
}//first method
public Integer convertBy(Function<List<String>, String> join, Function<String, List<Integer>> collectInts, Function<List<Integer>, Integer> sum) {
return sum.apply(collectInts.apply(join.apply((List<String>) value)));
}//second method
Despite their parameteres are parametrized with different types I cannot overload the first method. I might use different interface, other than Function<T,R> but don't know which one would suffice as I went through list of them and couldn't find one https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html.
Parameters in those functions are:
flines - reads the file from given path (String) and returns list of lines in that file (List<String>)
join - concatenates element of given List<String> and returns a String
collectInts - parses the given String and returns List of integers found in that String.
sum - adds elements from List<Integers> and returns the sum
Questions:
Can I overload the first mehod by the second one?
What other existing functional interface I might use besides function? I think none, as the types of argument and result always differ.
If you want to create a method which applies multiple functions and is not interested in the intermediate values, you can make it a generic method. The code in your question is strange as it assumes that value can be a String and a List<String> at the same time.
But comparing with your other question, there’s a different picture. While the varargs method there can’t work that way, you can easily provide overloaded methods for the actual use cases:
public class InputConverter<T> {
private T value;
public InputConverter(T value) {
this.value = value;
}
public <R> R convertBy(Function<? super T, ? extends R> f) {
return f.apply(value);
}
public <T1,R> R convertBy(
Function<? super T, ? extends T1> f1, Function<? super T1, ? extends R> f2) {
return f2.apply(f1.apply(value));
}
public <T1,T2,R> R convertBy(
Function<? super T, ? extends T1> f1, Function<? super T1, ? extends T2> f2,
Function<? super T2, ? extends R> f3) {
return f3.apply(f2.apply(f1.apply(value)));
}
public <T1,T2,T3,R> R convertBy(
Function<? super T, ? extends T1> f1, Function<? super T1, ? extends T2> f2,
Function<? super T2, ? extends T3> f3, Function<? super T3, ? extends R> f4) {
return f4.apply(f3.apply(f2.apply(f1.apply(value))));
}
}
Assuming that you fixed your interface types and created functions as described in this answer, you can use it like
InputConverter<String> fileConv=new InputConverter<>("LamComFile.txt");
List<String> lines = fileConv.convertBy(flines);
String text = fileConv.convertBy(flines, join);
List<Integer> ints = fileConv.convertBy(flines, join, collectInts);
Integer sumints = fileConv.convertBy(flines, join, collectInts, sum);
In this answer I attempted to create a static utility method to make a List into a Map:
public static <K, T> Map<K, T> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
It works just fine. However, I found that the method cannot be used in all the same contexts as the list.stream().collect(...) expression. The method isn't as flexible.
List<Student> students = Arrays.asList();
Map<Long, Student> studentsById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Student> studentsById2 = toMapBy(students, Student::getId);
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Person> peopleById2 = toMapBy(students, Student::getId); // compile error!
In this example, Student is a subtype of Person and has a getId method that returns a Long.
The last statement fails with incompatible types: inference variable T has incompatible bounds ... (JDK 1.8.0_25). Is there a way to define the type parameters so that the static method will work in the same contexts as the expression it contains?
You could add a type parameter for the values of the map so they can be different from T:
public static <K, V, T extends V> Map<K, V> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
Your last line calls the method toMapBy in which the compiler infers the type Student for T. So it obviously returns a List<Long, Student>.
But generics aren't covariant!
That means, you cannot assign a List<Long, Student> to a variable of type List<Long, Person>, because they are not in a subtype relationship.
The solution is to use the subtype form:
Map<Long, ? extends Person> peopleById2 = toMapBy(students, Student::getId); // no compiler error
With this bit:
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Notice that you do not provide arguments to Function.identity(). The compiler is free to infer it as Function.<Person>identity() to resolve the difference imposed by the return value assignment.
This should be good enough for your purpose:
public static <K, T> Map<K, T> toMapBy(
List<? extends T> list, // <- note
Function<? super T, ? extends K> mapper
) {
...
}
Now the elements of the List can be a subtype of the Map values. Or you can define a third parameter like #Alex has suggested.