Flat-Mapping Collector for property of a Class using groupingBy - java

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())));

Related

Rewrite SortedMap with streams Java

I have this code:
SortedMap<String, Double> starsPerActivity = new TreeMap<>();
for(Product p : products.values()) {
for(Rating r : ratings) {
if(r.getProductName() == p.getName()) {
starsPerActivity.put(p.getActivityName(), this.getStarsOfProduct(p.getName()));
}
}
}
return starsPerActivity;
And I want to rewrite this piece of code with streams.
I tried, but I don't know how.
The method starsPerActivity() returns a map that associates a name of the activity to the average number of stars for the products belonging to that activity, with the activity names sorted alphabetically. Activities whose products have not been rated should not appear in the result.
You can use the third Collectors.toMap overload:
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory)
Which allows you to define what map implementation you'd like to use:
SortedMap<String, Double> starsPerActivity = products.values().stream()
.filter(p -> ratings.stream()
.anyMatch(r -> r.getProductName().equals(p.getName())))
.collect(Collectors.toMap(
Product::getActivityName,
p -> getStarsOfProduct(p.getName()),
Double::max, TreeMap::new
));
Also I noticed that you used r.getProductName() == p.getName() in your if statement, which is probably applied to Strings. This is discouraged, see: How do I compare strings in Java
Note that this implementation picks the rating with the highest value. You can change this behaviour by replacing Double::max with the logic you like. If you want the same behaviour you currently have then you could use this, which is close enough: (a, b) -> b which simply picks the latest put value.
SortedMap<String, Double> starsPerActivity = new TreeMap<>();
products.values().forEach(p -> ratings.stream().filter(r -> r.getProductName().equals(p.getName()))
.forEachOrdered(r -> starsPerActivity.put(p.getActivityName(), this.getStarsOfProduct(p.getName()))));

Fastest way to convert a List<Type> to List<OtherType>

Imagine we have the following code
private List<String> convertScreenTypeToString(List<ScreenType> screenTypeList){
List<String> result = new ArrayList<>();
for(ScreenType screenType : screenTypeList){
result.add(screenType.getLabel());
}
return result;
}
But, we got different Type (ScreenType, HomeType, UserType) and I dont want to repeat the same method 3 times more and I cant use inheritance because they are a providen model. (Arch design stuff).
Also,
.... TypeToScreen(List<Object> whatever){}
It is not a proper solution.
And furthermore:
private class Convert<T>{ .....TypeToScreen(List<T> whatecer){}}
Inside the parent class is OK but i am searching for some advanced ways
Streams will let you map elements of a list.
List<String> labels =
screenTypes.stream()
.map(ScreenType::getLabel)
.collect(Collectors.toList());
There's no guarantees as to what sort of List that is, so you might want to wrap in new ArrayList<>() or similar.
It's be convenient if there was a method like this on List. You can write a convenience method for this very common case.
public static <T, R> List<R> map(
List<T> source, Function<? super T,​ ? extends R> mapping
) {
return
screenTypes.stream()
.map(mapping)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
Here the three-argument form of Stream.collect removes the need for an intermediate List. #Ousmane D. in the comments offers an alternative last line.
.collect(Collectors.toCollection(ArrayList::new));
Alternatively, you could write it out without streams. This is faster and easier to read, if either of those things matter to you.
public static <T, R> List<R> map(
List<T> source, Function<? super T,​ ? extends R> mapping
) {
List<R> result = new ArrayList<>(source.size());
for (T t : source) {
result.add(mapping.apply(t));
}
return result;
}

Java streams with generics

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));
}

Type-safely create instance of Function to be passed to Comparator.comparing()

Assume I have a method with the following signature:
<T, U extends Comparable<? super U>> Comparator<T> method(Map<String, Function<? super T, ? extends U>> comparatorFunctionMap)
The method accepts a map of functions (with string keys) and creates a Comparator<T> as a result (it isn't important how). Map values are instances of Function<? super T, ? extends U>, so that they can be directly passed to Comparator.comparing().
How do I populate this map in a type-safe way? Say I have a class Person with attributes name and age (and getters for them).
When I do the following:
Map<String, Function<? super Person, ? extends Comparable>> map1 = new HashMap<>();
map1.put("name", Person::getName);
method(map1);
I get a warning on lines 1 and 3. If I try this instead, for example:
Map<String, Function<? super Person, ? extends Comparable<?>>> map2 = new HashMap<>();
map2.put("name", Person::getName);
method(map2);
The third line is a compile error.
Is there a way to do this type-safely?
If you want to be able to add both Person::getName and Person::getAge in the map, you won't be able to use the method signature you propose because there is no U that is a supertype of both String, Integer and Comparable.
Essentially, your map is a Map<String, Function<T, Comparable<?>> since the comparables are not related to each other type-wise.
I don't think you can get around it without using raw types, which could look like below. Note that you still have type safety (you must pass a Comparable to the map).
static void m() {
Map<String, Function<Person, Comparable<?>>> map = new HashMap<>();
map.put("name", Person::getName);
map.put("age", Person::getAge);
Comparator<Person> c = method(map);
}
#SuppressWarnings(value = {"unchecked", "rawtypes"})
static <T> Comparator<T> method(String name, Map<String, Function<T, Comparable<?>>> comparatorFunctionMap) {
Function f = (Function) comparatorFunctionMap.get("age");
Comparator<T> c = Comparator.comparing(f);
return c;
}
The only (I think) limitation of this is that technically, one could pass a weird class say Weird implements Comparable<String> that would probably cause a runtime error.

How to overload a method by method with parameters list that contains parameters of the exact same type but parametrized with other types

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);

Categories

Resources