I have class AB:
class AB{
private final String a;
private final String b;
public AB(String a, String b) {
this.a = a;
this.b = b;
}
public String getB() {
return b;
}
public String getA() {
return a;
}
#Override
public boolean equals(Object o) {...}
#Override
public int hashCode() {...}
}
Now, I create map Map<AB,Float> mapWithData to count how many times in server response we have in stats.log a serviceName and b servicePort.
We sum it in map servicesNamePort.merge(new AB(...,...), 1.0f, Float::sum)
How can I create map from Map<AB,Float> mapWithData where key is b and value is map of a as key, and float as value it means Map<String **(b)**, Map<String**(a)**,Float>>
I try to use Collectors.groupingBy but I have this answer:
Map<String, List<Map.Entry<AB, Float>>> collect = mapWithData.entrySet()
.stream()
.collect(Collectors.groupingBy(abEntry-> abEntry.getKey().getB()));
Thanks for any help
The point you’ve missed, is that you can specify another Collector as second argument to the groupingBy collector to specify how to collect the groups:
Map<String, Map<String, Double>> collect = mapWithData.entrySet()
.stream()
.collect(Collectors.groupingBy(e -> e.getKey().getB(),
Collectors.groupingBy(e -> e.getKey().getA(),
Collectors.summingDouble(e -> e.getValue()))));
This is the closest to you question, you can get, as there is no direct support for Float.
But since this number is supposed to be a count, I’d rather use Integer or Long anyway.
If your mapWithData was declared as Map<AB,Long>, you could use
Map<String, Map<String, Long>> collect = mapWithData.entrySet()
.stream()
.collect(Collectors.groupingBy(e -> e.getKey().getB(),
Collectors.groupingBy(e -> e.getKey().getA(),
Collectors.summingLong(e -> e.getValue()))));
If it really has to be a Float result, you can use
Map<String, Map<String, Float>> collect = mapWithData.entrySet()
.stream()
.collect(Collectors.groupingBy(e -> e.getKey().getB(),
Collectors.groupingBy(e -> e.getKey().getA(),
Collectors.collectingAndThen(
Collectors.summingDouble(e->e.getValue()), Double::floatValue))));
Related
How to convert List to Map - Key of Map should be a combination of multiple keys
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class Student {
private long id;
private String firstName;
private String lastName;
private String street;
private String city;
public static void main(String[] args) {
List<Student> students = Arrays.asList(
Student.builder().id(1).firstName("John").lastName("Doe").build(),
Student.builder().id(1).firstName("Jane").lastName("Doe").build(),
Student.builder().id(1).firstName("Mike").lastName("Doe").build(),
Student.builder().id(1).firstName("Jack").lastName("Doe").build()
);
LinkedHashMap<Long, String> collect = students.stream()
.collect(Collectors.toMap(
Student::getId, Student::getFirstName, (x, y) -> x + ", " + y, LinkedHashMap::new));
System.out.println(collect);
// Answer I am expecting is Ex: {1johnDoe=[id=1,firstName=John, lastName=Doe]}
}
}
Using Java 17
I have implemented the below code using some of the latest java features apart from Java 8.
Records in java 14 : As of JDK 14, we can replace our data classes with records. Records are immutable classes that require only the type and name of fields. We do not need to create constructor, getters, setters, override toString() methods, override hashcode and equals methods.
List.of() in java 9: It is a static method that returns the immutable list of elements passed as arguments. Here in the below scenario, we will get the list of four student objects.
public class Test {
public static void main(String[] args) {
record Student(long id, String firstName, String lastName,String street, String city){}
Student s1 = new Student(1,"F1","L1","S1","C1");
Student s2 = new Student(2,"F2","L2","S2","C2");
Student s3 = new Student(3,"F3","L3","S3","C3");
Student s4 = new Student(4,"F4","L4","S4","C4");
Map<String,Student> output =
List.of(s1,s2,s3,s4).stream().collect(Collectors.toMap(x -> x.id() + x.firstName(),
Function.identity(), (k, v) -> k, LinkedHashMap::new));
System.out.println(output);
}
}
Output:
{1F1=Student[id=1, firstName=F1, lastName=L1, street=S1, city=C1], 2F2=Student[id=2, firstName=F2, lastName=L2, street=S2, city=C2], 3F3=Student[id=3, firstName=F3, lastName=L3, street=S3, city=C3], 4F4=Student[id=4, firstName=F4, lastName=L4, street=S4, city=C4]}
You can do as follows
LinkedHashMap<String, Student> collect = students.stream()
.collect(
Collectors.toMap(
student->(String.join("", Long.toString(student.getId()),student.getFirstName(),student.getLastName())),
student->student,
(v1,v2)->v1,
LinkedHashMap::new));
collect.forEach((k,v)->System.out.println(k+"="+v));
I think, you want key as String and value as Student into map
I am trying to learn map function in Stream
public class EmployeeInformationTest {
public static void main(String args[]) {
List<Employee> employees = Arrays.asList(
new Employee("Jai"),
new Employee("Adithya"),
new Employee("Raja"));
List<String> names = employees.stream()
.map(s -> s.getEmployeeName()) // Lambda Expression
.collect(Collectors.toList());
System.out.println(names);
}
}
we have above code and somehow it is giving us List of String from List of Employee. Say, we have other class Person in which we have field as name
public class Person {
private String name;
}
so is it feasible via map or some other function in stream so that I can get the List of Person rather than List of String in above code
sure thing, just change the map function to:
.map(s -> new Person(s.getEmployeeName()))
or if there is no such constructor:
.map(s -> {
Person p = new Person();
p.setName(s.getEmployeeName());
return p;
})
I have two classes - Student and StudentDetails.
My goal is to get data in the form of Map <studentName,Map<subjectName, subjectNo>>.
When using streams, not able to fetch data of parent class(Student).
public class Student {
private String studentName;
private Map<String, Long> studentDetails;
public Map<String, Long> getStudentDetails() {
return studentDetails;
}
public String getStudentName() {
return studentName;
}
}
class StudentDetails {
private String subjectName;
private String subjectNo;
public String getFSubjectName() {
return subjectName;
}
public String getSubjectNo() {
return subjectNo;
}
}
public class Main {
public static void main(String[] args) {
Collection<Student> student = null;
Map<String, Map<String, Long>> sts = student.stream()
.map(st -> st.getStudentDetails().values())
.flatMap(st -> st.stream())
.collect(Collectors.groupingBy(Student::getStudentName,
Collectors.toMap(StudentDetails :: getFatherName, StudentDetails:: getRollNo )));
}
}
Error : The type Student does not define getStudentName(T) that is applicable here.
You "lost" your Student while doing:
.map(st -> st.getStudentDetails().values())
.flatMap(st -> st.stream()) // this is a Stream<StudentDetails> now
Thus if you need it just map it:
.flatMap(st -> st.getStudentDetails()
.values()
.stream()
.map(sd -> new SimpleEntry<>(st, sd)))
.collect(Collectors.groupingBy(
en -> en.getKey().getStudentName(),
Collectors.toMap(
en -> en.getValue().getFartherName(),
en -> en.getValue().getRollNo()
)
))
This can be done without Streams too; you could try to look at Map::compute
The below java function is assignable (f(Order) to f(Object))
Function<Order, Order> orderProcessor = (Order order) -> {
System.out.println("Processing Order:"
return order;
};
Function f = orderProcessor;
The question is how do i cast this function back to Function < Order,Order> ?
or better I would like to cast this function back to Function < SomeInterface,SomeInterface>
I am storing these functions in a List< Function> but ideally i would like to store them in a List < SomeInterface,SomeInterface>.
Is it possible ?
you can define a List takes Functions with 2 generic parameters ? extends SomeInterface. then no need casting at all, for example:
List<Function<? extends SomeInterface, ? extends SomeInterface>> functions =
new ArrayList<>();
Function<Order, Order> orderProcessor = (Order order) -> {
System.out.println("Processing Order:" + order);
return order;
};
functions.add(orderProcessor);
IF the result type is same as parameter type, you also can using a UnaryOperator instead, for example:
List<UnaryOperator<? extends SomeInterface>> functions = new ArrayList<>();
UnaryOperator<Order> orderProcessor = (Order order) -> {
System.out.println("Processing Order:" + order);
return order;
};
functions.add(orderProcessor);
THEN you can't accept any SomeInterface at all, since the Function<? extends SomeInterface> can take any other subtypes not only for the Order class.
IF you want your Functions can accept all of its subtypes, you must declare it as write mode ? super SomeInterface, and then you must change the UnaryOperator<Order> definition, for example:
List<UnaryOperator<? super SomeInterface>> functions = new ArrayList<>();
UnaryOperator<SomeInterface> orderProcessor = (SomeInterface item) -> {
if(item instanceof Order){
System.out.println("Processing Order:" + order);
}
return item;
};
functions.add(orderProcessor);
here is a solution wrap a Map in Processors then you don't need any casting or instanceof statement in UnaryOperator body, for example:
Processors processors = new Processors();
UnaryOperator<Order> orderProcessor = (order) -> {
// no need instanceof & casting expression here
System.out.println("Processing Order:" + order);
return order;
};
processors.add(Order.class, orderProcessor);
processors.listing(Order.class).forEach(it -> it.apply(new Order()));
class Processors {
private final Map<Class<?>, List<UnaryOperator<?>>> registry = new HashMap<>();
public <T extends SomeInterface> void add(Class<T> type,
UnaryOperator<T> processor) {
registry.computeIfAbsent(type, aClass -> new ArrayList<>()).add(processor);
}
#SuppressWarnings("unchecked")
public <T extends SomeInterface>
Stream<UnaryOperator<T>> listing(Class<T> type){
return (Stream<UnaryOperator<T>>) lookup(type).stream();
}
private List<?> lookup(Class<?> type) {
if (!SomeInterface.class.isAssignableFrom(type))
return Collections.emptyList();
if (!registry.containsKey(type))
return registry.get(type.getSuperclass());
return registry.get(type);
}
}
I have a list of objects with class like this:
class TestResult{
int userId;
Set<String> subjects;
int score;
}
I want to produce a summary of score for each userId in each subject
Map<String,Map<Integer, Integer>>
The outer map has subject as key. The inner map has userId as key, sum of scores as the value.
(Please note: in the following, I refere to your (currently unnamed) class as MixedScores)
Let's a define a method sumScores which takes a Collection<MixedScores> and returns a Map<String, Map<Integer, Integer>> composed as you asked for.
You could use a local class SingleScore to help in splitting a single MixedScores instance in multiple instances, then group them by subject, by userId, and finally summing up all scores found.
In code:
Map<String, Map<Integer, Integer>> sumScores(Collection<MixedScores> scores) {
class SingleScore {
int userId;
String subject;
int score;
SingleScore(MixedScores mixedScores, String subject) {
this.userId = mixedScores.userId;
this.subject = subject;
this.score = mixedScores.score;
}
String getSubject() {
return subject;
}
int getUserId() {
return userId;
}
int getScore() {
return score;
}
}
return scores.stream()
.flatMap(x -> x.subjects.stream().map(subject -> new SingleScore(x, subject)))
.collect(groupingBy(SingleScore::getSubject,
groupingBy(SingleScore::getUserId, summingInt(SingleScore::getScore))));
}
Of course, there are some imports:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
Does this work for you?
Another way without creating a new class, suggested by #Flown
scores.stream()
.flatMap(x -> x.subjects.stream()
.map(subject -> new AbstractMap.SimpleEntry<String, Entry<Integer, Integer>>(subject,
new AbstractMap.SimpleEntry<Integer, Integer>(x.userId, x.score))))
.collect(Collectors.groupingBy(Entry<String, Entry<Integer, Integer>>::getKey,
Collectors.groupingBy(p -> p.getValue().getKey(),
Collectors.summingInt((p -> p.getValue().getValue())))));