reduce two group in java streaming api - java-8

i want to group the item element according to date such that , if item object's date is greater than present date then , it will fall into futuredate group and if date is less than present date then it will fall into pastdate
and then i want to reduce on these group so that i can get aggregate qty,price of these group, further aggregate value returned from pastdate group shold sit in oldPrice and oldQty of object
public class Item {
private String name;
private int qty;
private int oldQty;
private BigDecimal price;
private BigDecimal oldPrice;
Private Date date;
//constructors, getter/setters
}
Map<String, List<Item>> groupByP =
items.stream().collect(Collectors.groupingBy((row)->{
//logic to seperate items into two group based on date
}));
How to proceed after this

There are two ways to this, the first is first partition and later compute:
(this partitions, then return a Pair that holds the sum of all quantities and an average of the price)
Map<Boolean, List<Item>> partioned = items.stream()
.collect(Collectors.partitioningBy(item -> item.getDate().compareTo(now) > 0));
partioned.get(Boolean.FALSE).stream()
.map(item -> new AbstractMap.SimpleEntry<>(item.getOldQty(), item.getOldPrice()))
.reduce((entry1, entry2) -> {
int sum = entry1.getKey() + entry2.getKey();
BigDecimal averagePrice = entry1.getValue().add(entry2.getValue()).divide(BigDecimal.valueOf(2));
return new AbstractMap.SimpleEntry<>(sum, averagePrice);
}).get();
The second us writing a custom collector, but I doubt it will be more clear.

If you want to split items into to groups you need to use partitioningBy collector.
#AllArgsConstructor
#Getter
public static class Item {
private String name;
private int qty;
private int oldQty;
private BigDecimal price;
private BigDecimal oldPrice;
private Date date;
}
public static void main(String [] args) {
List<Item> items = Lists.newArrayList();
Map<Boolean, List<Item>> partitionedItems = items.stream()
.collect(Collectors.partitioningBy(item -> item.getDate().after(new Date())));
}
In this case you'll get
Map<Boolean, List<Item>>
Now you can:
map.get(true)
returns a list of items that match the condition
map.get(false)
returns a list of items that don't match the condition
Having those two lists you can make any reduction you want.

Related

How to convert List to Map - Key of Map should be a combination of multiple keys

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

Sort by a list by name and age descending

I am trying to sort a list by lastName and age descending using Java 8 - I can get to the point of sorting both firstName and age - but not by firstName and age descending. Please help.
#Getter
#Setter
#AllArgsConstructor
#ToString
class ABCE {
private String firstName;
private String lastName;
private int age;
}
// Sort by firstName and then by age descending
list = list.stream().sorted(Comparator.comparing(ABCE::getFirstName).thenComparing(ABCE::getAge))
.collect(Collectors.toList());
list.stream().forEach(System.out::println);
Did you want both name and age descending? Your reversed() reverses all the comparisons prior. If you just want to reverse based on ages, do it as follows. I simplified it somewhat by using a record with just a first name.
record ABCE(String getFirstName, int getAge) {
}
public static void main(String[] args) {
List<ABCE> list = List.of(new ABCE("A", 10),
new ABCE("B", 10), new ABCE("B", 12),
new ABCE("C", 10), new ABCE("C", 10));
// Sort by firstName ascending and then by age descending
list = list.stream()
.sorted(Comparator.comparing(ABCE::getFirstName)
.thenComparing(ABCE::getAge,
Comparator.reverseOrder()))
.collect(Collectors.toList());
list.stream().forEach(System.out::println);
}
Prints
ABCE[getFirstName=A, getAge=10]
ABCE[getFirstName=B, getAge=12]
ABCE[getFirstName=B, getAge=10]
ABCE[getFirstName=C, getAge=10]
ABCE[getFirstName=C, getAge=10]

Use Java 8 stream to convert enum to set

I have an enum looks like:
public enum Movies {
SCIFI_MOVIE("SCIFI_MOVIE", 1, "Scifi movie type"),
COMEDY_MOVIE("COMEDY_MOVIE", 2, "Comedy movie type");
private String type;
private int id;
private String name;
Movies(String type, int id, String name) {
this.type = type;
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
}
I know that I can use stream to create a Set of Movies enum with:
Set<Movie> Movie_SET = Arrays.stream(Movie.values()).collect(Collectors.toSet());
What if I want to create a Set of enum Movies id. Is there a way to do that with stream?
Yes, assuming you have a getter for your id, your Movies enum might look like this:
public enum Movies {
SCIFI_MOVIE("SCIFI_MOVIE", 1, "Scifi movie type"),
COMEDY_MOVIE("COMEDY_MOVIE", 2, "Comedy movie type");
private String type;
private int id;
private String name;
Movies(String type, int id, String name) {
this.type = type;
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
}
Then, you can get the set of ids by using Stream.map():
Set<Integer> movieIds = Arrays.stream(Movies.values()).map(Movies::getId)
.collect(Collectors.toSet());
BTW, an alternative way to create the set of all movies is to use EnumSet.allOf():
Set<Integer> movieIds = EnumSet.allOf(Movies.class).stream().map(Movies::getId)
.collect(Collectors.toSet());
You can use EnumSet implementation for this purpose e.g.
For obtaining the Set of Movies:
Set<Movies> movies = EnumSet.allOf(Movies.class);
For obtaining only movies ids:
Set<Integer> moviesIds = movies.stream().map(Movies::getId).collect(Collectors.toSet());
If you are able to get stream of Enum values then rest could easily be achieved.
You could use custom Collector impl (My favorite of all time), see example below of a custom collector:-
Set<Integer> movieIds = Arrays
.stream(Movies.values())
.collect(
HashSet::new,
(set, e) -> set.add(e.getId()),
HashSet::addAll
);
Or you could use map to fetch ids only and the collect to collect these ids to a Set as usual.
Set<Integer> movieIds = Arrays
.stream(Movies.values())
.map(Movies::getId)
.collect(Collectors.toSet());

How to pivot pages dynamically

I need to create a pivot project considering a list of three levels:
public class Value
{
private DateTime DayMonth;
private float value;
private float variation;
[...]
}
public class Element
{
private String description;
private List<Value> values;
private List<Element> elements;
[...]
}
public class User
{
private String name;
private List<Element> elements;
[...]
}
What I need is to make pivot pages for each of the diferent Value.DayMonth of each element. So, when I visualize the data, I can see the value e variation of each element, and if I want to see these indicators for another day of the month I just go to the next page.

Using Linq to select from a List in a List with Contains

I'm having syntax troubles.
public class Student
{
int StudentId;
string Name;
}
public class Course
{
int CourseId;
List<Student> Students;
}
int[] studentIds = { 5, 7, 12 };
List<Course> allCourses = myDataContext.Courses.ToList();
Using Lambda expressions or query expressions, how do I get a filtered list of all the Courses containing any of the Students in the array studentIds?
var result = from course in allCourses
where course.Students.Any(x => studentIds.Contains(x.StudentId))
select course;

Resources