How to write java streams for the nested loops - java

How do I use the java streams for the below scenario with nested loops...
package test;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
#Setter
#Getter
class Car {
String carName;
int wheelNo;
WheelDetails wheelDetails;
Car(String carName, int wheelNo) {
this.carName = carName;
this.wheelNo = wheelNo;
}
}
#Getter
#Setter
class WheelDetails {
int wheelNo;
String wheelColour;
int Size;
WheelDetails(int wheelNo, String wheelColour, int Size) {
this.wheelNo = wheelNo;
this.wheelColour = wheelColour;
this.Size = Size;
}
}
public class testMain {
public static void main(String[] args) {
List<Car> cars = new ArrayList<Car>();
cars.add(new Car("CarOne", 1));
cars.add(new Car("CarTwo", 2));
List<WheelDetails> wheelDetailsList = new ArrayList<WheelDetails>();
wheelDetailsList.add(new WheelDetails(1, "Black", 16));
wheelDetailsList.add(new WheelDetails(2, "Grey", 17));
for (Car car : cars) {
for (WheelDetails wheelDetails : wheelDetailsList) {
if (car.getWheelNo() == wheelDetails.getWheelNo()) {
car.setWheelDetails(wheelDetails);
}
}
}
}
}
not able to find the options to do the above in the streams it will be great helpful
have gone trough the other areas but could not find it... any link or solution will be helpful......

Try this:
cars.forEach(car -> wheelDetailsList.stream()
.filter(wheelDetails -> wheelDetails.wheelNo == car.wheelNo).forEach(car::setWheelDetails));

The following solution modifies elements in the list of cars:
cars.forEach(car -> car.setWheelDetails(
wheelDetailsList.stream()
.filter(wd -> car.getWheelNo() == wd.getWheelNo())
.findFirst()
.orElse(null)
));
Another faster solution is based on a temporary map <Integer, WheelDetails>:
Map<Integer, WheelDetails> wheelMap = wheelDetailsList
.stream()
.collect(Collectors.toMap(
WheelDetails::getWheelNo, // key
wd -> wd, // value
(wd1, wd2) -> wd1, // (optional) merge function to resolve conflicts
LinkedHashMap::new // keep insertion order
));
cars.forEach(car -> car.setWheelDetails(wheelMap.get(car.getWheelNo())));

Related

How to add a set of values from an ArrayList to another ArrayList using Lambda expression

I have to create a result list by adding objects from one array list to another. Here is my code that works.
private List<MyObject> getObjectList(List<OtherObject> objects) {
List<MyObject> resultList = new ArrayList<>();
for (int i = 0; i < objects.size(); i++) {
MyObject object = new MyObject()
object.setId(objects.get(i).getId());
object.setText(objects.get(i).getTextValue());
object.setUserId(objects.get(i).getUserName());
object.setCreatedDate(objects.get(i).getCreateDateTime());
resultList.add(object);
}
return resultList;
}
How can I achieve this by using lambda expression?
Here's how you can do it with a method reference:
private List<MyObject> getObjectList(List<OtherObject> objects) {
return objects.stream()
.map(this::map)
.collect(Collectors.toList());
}
private MyObject map(OtherObject otherObject) {
MyObject object = new MyObject();
object.setId(otherObject.getId());
object.setText(otherObject.getTextValue());
object.setUserId(otherObject.getUserName());
object.setCreatedDate(otherObject.getCreateDateTime());
return object;
}
Here's how you can do it using streams and lambda expression:
private List<MyObject> getObjectList(List<OtherObject> objects) {
return objects.stream()
.map(obj -> {
MyObject object = new MyObject();
object.setId(obj.getId());
object.setText(obj.getTextValue());
object.setUserId(obj.getUserName());
object.setCreatedDate(obj.getCreateDateTime());
return object;
}).collect(Collectors.toList());
}
Here is an example:
private List<MyObject> getObjectList(List<OtherObject> objects) {
List<MyObject> resultList = new ArrayList<>();
objects.forEach(obj -> {
MyObject object = new MyObject();
object.setId(obj.getId());
object.setText(obj.getTextValue());
object.setUserId(obj.getUserName());
object.setCreatedDate(obj.getCreateDateTime());
resultList.add(object);
});
return resultList;
}
Using Record
Equivalent of your OtherObject
import java.time.LocalDateTime;
public record OtherRec(int id,
String textValue,
String userName,
LocalDateTime createDateTime) {
}
Equivalent of your MyObject
import java.time.LocalDateTime;
public record MyRecord(int id,
String text,
String userId,
LocalDateTime createDate) {
}
Copying list of OtherRec to MyRecord
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
public class CopyList {
public static void main(String[] args) {
List<OtherRec> source = List.of(new OtherRec(1, "First", "One", LocalDateTime.of(2020, 10, 5, 12, 0)),
new OtherRec(2, "Second", "Two", LocalDateTime.of(2020, 10, 5, 13, 0)));
List<MyRecord> target = source.stream()
.map(or -> new MyRecord(or.id(), or.textValue(), or.userName(), or.createDateTime()))
.collect(Collectors.toList());
System.out.println(target);
}
}

Group and sort by multiple attribute using stream: Java 8

I have List of MainEntity
public class MainEntity {
private String keyword;
private double cost;
private String company;
}
and I have CompanyEntity
public class CompanyEntity {
private double cost;
private String company;
}
I am trying to transform my list into Map<String,List<CompanyEntity>> where key will be keyword and List<CompanyEntity> will have average of all the costs and sorted too. I am trying to do it in stream and Java 8.
For a particular keyword as input I am doing this.
List<MainEntity> entityList = keyWordMap.get(entity.getKeyword());
entityList.add(entity);
keyWordMap.put(entity.getKeyword(), entityList);
Map<String, Double> average = (keyWordMap.get(keyword)).stream()
.collect(groupingBy(MainEntity::getCompany,
Collectors.averagingDouble(MainEntity::getCtr)));
result.put(keyword, new ArrayList<>());
for (Map.Entry<String, Double> entity : average.entrySet()) {
result.get(keyword).add(new CompanyEntity(entity.getKey(), entity.getValue()));
}
But I trying to create a map for all keywords. Is is possible or iterating whole list again makes sense?
Currently keyowordMap is of type Map<String,MainEntity> which I did by iterating list of MainEntity, but I want Map<String,List<MainEntity>>.
First, make a keyWordMap
Map<String, List<MainEntity>> keyWordMap =
mainEntityList
.stream()
.collect(Collectors.groupingBy(MainEntity::getKeyword));
Then iterate the map, for each keyword, you can directly get the list of CompanyEntity sort by average value and using map() to transform the data and collect as List, then put in result
Map<String,List<CompanyEntity>> result = ....
for (Map.Entry<String, List<MainEntity> entry : keyWordMap.entrySet()) {
List<CompanyEntity> list = entry.getValue().stream()
.collect(groupingBy(MainEntity::getCompany,
Collectors.averagingDouble(MainEntity::getCtr)))
.entrySet()
.stream()
.sorted(Comparator.comparing(e -> e.getValue()))
.map(e -> new CompanyEntity(e.getKey(), e.getValue()))
.collect(Collectors.toList());
result.put(entry.getKey(), list);
}
Or you want to do this in one-shot
Map<String,List<CompanyEntity>> mapData =
mainEntityList
.stream()
.collect(Collectors.groupingBy(MainEntity::getKeyWord,
Collectors.groupingBy(MainEntity::getCtr,
Collectors.averagingDouble(MainEntity::getCtr))))
.entrySet()
.stream()
.collect(Collectors.toMap(m -> m.getKey(),
m -> m.entrySet()
.stream()
.sorted(Comparator.comparing(e -> e.getValue()))
.map(e -> new CompanyEntity(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
The other answer completely changed its answer after initially misunderstanding the question and in good StackOverflow spirits it attracted the first upvote so is now accepted and highest upvoted. But this has a few more steps in the code showing what's happening:
This should get you the result:
import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Value;
public class CompanyEntityStackOverflowQuestion {
public static void main(String[] args) throws IOException {
//setup test data
MainEntity one = new MainEntity("key1", 10D, "company1");
MainEntity two = new MainEntity("key2", 5D, "company2");
MainEntity three = new MainEntity("key1", 7D, "company3");
MainEntity four = new MainEntity("key2", 3D, "company4");
List<MainEntity> mainEntityList = List.of(one, two, three, four);
//group list by keyword
Map<String, List<MainEntity>> mainEntityByKeyword = mainEntityList.stream()
.collect(Collectors.groupingBy(MainEntity::getKeyword));
//map to companyEntity object
Stream<SimpleEntry<String, List<CompanyEntity>>> mapped = mainEntityByKeyword.entrySet().stream()
.map(entry -> new SimpleEntry<>(entry.getKey(), entry.getValue().stream().map(
getCompanyListFunction()).collect(Collectors.toList())));
//sort and calculate average
Stream<SimpleEntry<String, CompanyEntityListWithStats>> mappedToListWithStats = mapped
.map(entry -> new SimpleEntry<>(entry.getKey(),
new CompanyEntityListWithStats(entry.getValue().stream().mapToDouble(company -> company.cost).average().orElse(0D), //or use Collectors.averagingDouble(company -> company.cost))
sortList(entry.getValue()))));
//collect back to map
Map<String, CompanyEntityListWithStats> collect = mappedToListWithStats
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
//show result
System.out.println(collect);
}
//sort by cost
private static List<CompanyEntity> sortList(List<CompanyEntity> list) {
list.sort(Comparator.comparing(company -> company.cost));
return list;
}
//map MainEntity to CompanyEntity
private static Function<MainEntity, CompanyEntity> getCompanyListFunction() {
return mainEntity -> new CompanyEntity(mainEntity.cost, mainEntity.company);
}
#Value
public static class MainEntity {
public String keyword;
public double cost;
public String company;
}
#Value
public static class CompanyEntity {
public double cost;
public String company;
}
#Value
public static class CompanyEntityListWithStats {
public double average;
public List<CompanyEntity> companyList;
}
}
Output: {key1=CompanyEntityStackOverflowQuestion.CompanyEntityListWithStats(average=8.5, companyList=[CompanyEntityStackOverflowQuestion.CompanyEntity(cost=7.0, company=company3), CompanyEntityStackOverflowQuestion.CompanyEntity(cost=10.0, company=company1)]), key2=CompanyEntityStackOverflowQuestion.CompanyEntityListWithStats(average=4.0, companyList=[CompanyEntityStackOverflowQuestion.CompanyEntity(cost=3.0, company=company4), CompanyEntityStackOverflowQuestion.CompanyEntity(cost=5.0, company=company2)])}
You may be able to skip some steps, this is just quickly typed out. You can of course inline stuff to make it look a lot shorter/cleaner, but this format shows what's happening.

How to Stream and get the required format type Of Object

I have a EmpData Object
import java.util.ArrayList;
import java.util.List;
public class EmpData {
private List<Areas> areasList = new ArrayList<Areas>();
public List<Areas> getAreasList() {
return areasList;
}
public void setAreasList(List<Areas> areasList) {
this.areasList = areasList;
}
}
and a Area Object
public class Areas {
private String areaName;
public String getAreaName() {
return areaName;
}
public void setAreaName(String areaName) {
this.areaName = areaName;
}
}
I am trying to loop through the whole empDataList whose AreasList contains "VENG" ,
then get that particular EmpData
This is my Test Client
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ClientTest {
public static void main(String[] args) {
List<EmpData> empDataList = new ArrayList<>();
List<Areas> areasList = new ArrayList<Areas>();
Areas area1 = new Areas();
area1.setAreaName("VENG");
areasList.add(area1);
EmpData empData1 = new EmpData();
empData1.setAreasList(areasList);
empDataList.add(empData1);
List<EmpData> allData = empDataList.stream()
.flatMap(e -> e.getAreasList().stream()
.filter(a -> a.getAreaName().equals("VENG")))
.collect(Collectors.toList());
}
}
Type mismatch: cannot convert from List<Areas> to List<EmpData>
You need filter, not flatMap. For each EmpData element, apply a filter that streams over the areas List to locate the required area name:
List<EmpData> allData =
empDataList.stream()
.filter(e->e.getAreasList().stream().anyMatch(a->a.getAreaName().equals("VENG")))
.collect(Collectors.toList());
If you want a single EmptData instance, change the terminal operation:
Optional<EmpData> emp =
empDataList.stream()
.filter(e->e.getAreasList().stream().anyMatch(a->a.getAreaName().equals("VENG")))
.findFirst();

Java Search List by different criteria

i have several list classes that need to be searched by different criteria (single values, multiple values, etc...). These classes have, at the moment, different methods depending on the search criteria. Since i hate to write the same code again and again i am looking to avoid this... but i don't want to reinvent the wheel.
So, i am considering creating a custom class that implements a generic search.
The code looks like this:
import java.util.ArrayList;
import java.util.Iterator;
public class ListCustomComparable<T> {
private ArrayList<T> listItems;
public ListCustomComparable() {
}
/**
* #return the listItems
*/
public ArrayList<T> getListItems() {
return listItems;
}
/**
* #param listItems the listItems to set
*/
public void setListItems(ArrayList<T> listItems) {
this.listItems = listItems;
}
public ArrayList<T> searchByComparable(Comparable<T> comparator){
ArrayList<T> listRes= new ArrayList<T>();
for (T item: listItems ){
if(comparator.equals(item))
listRes.add(item);
}
return listRes;
}
}
So, i pretend that every method that wants to do a specific search must implement the comparable interface. This methods, obviously, will need their own code but will rely on the generic class.
Please, what do you think about it? i am reinventing the wheel because it is already done? Or is it OK?
One additional restriction. It should work with 1.6.
i am reinventing the wheel because it is already done? Or is it OK?
Yes, you are re-inventing the wheel.
What you are suggesting pretty much exactly matches Predicates.
Here's an example:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Example {
static class Person{
int age;
String name;
Person(int age, String name){
this.age = age;
this.name = name;
}
}
public static void main(String[] args){
List<Person> persons = new ArrayList<>();
persons.add(new Person(20,"John"));
persons.add(new Person(25,"Alice"));
persons.add(new Person(30,"Peter"));
persons.add(new Person(25,"Stefan"));
List<Person> results = persons.stream()
.filter(p -> p.age <= 25 && p.name.equals("Stefan"))
.collect(Collectors.toList());
for(Person p : results)
System.out.println(p.name);
}
}
Predicate is a generic interface that specifies a method that returns a boolean value if something matches or not given that value.
Examples of how predicates can be defined:
Predicate<String> filter_1 = str -> "value".equals(str);
Predicate<String> filter_2 = "value"::equals;
Predicate<String> filter_3 = new Predicate<String>() {
#Override
public boolean test(String s) {
return "value".equals(s);
}
};
Predicate<String> minLengthFilter = str -> str != null && str.length() > 5;
Predicate<String> maxLengthFilter = str -> str != null && str.length() < 8;
Predicate<String> combined = minLengthFilter.and(maxLengthFilter);
In case you have to work with a Java version before 1.8, you could use for example guava2 which also has it's own Predicate3 system.
You could then for example filter it using the Iterables4 class and then finally collect it back in to a list using5.
You could of course find an alternative or make a own function to combine the filter+collect to list methods.
You can also combine Predicates using their Predicates class6, as well as being able to use them to construct certain simple predicates.
Here's a full example using Guava.
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
public class GuavaExample {
static class Person{
int age;
String name;
boolean isFemale;
Person(int age, String name, boolean isFemale){
this.age = age;
this.name = name;
this.isFemale = isFemale;
}
}
public static <T> List<T> applyFilter(List<? extends T> list, Predicate<? super T> ... filters){
// default case: no filters.
if (filters == null || filters.length == 0)
return new ArrayList<T>(list);
Iterable<? extends T> it = Iterables.filter(list,Predicates.and(filters));
return Lists.newArrayList(it);
}
public static <T> List<T> applyFilter(List<? extends T> list, Predicate<? super T> filter){
Iterable<? extends T> it = Iterables.filter(list,filter);
return Lists.newArrayList(it);
}
public static void main(String[] args) {
List<Person> result;
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(8, "Little John",false));
persons.add(new Person(10, "Alice Jnr.",true));
persons.add(new Person(20,"John",false));
persons.add(new Person(25,"Alice",true));
persons.add(new Person(30,"Sarah",true));
persons.add(new Person(25,"Stefan",false));
Predicate<Person> isAdult = new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return person.age >= 18;
}
};
Predicate<Person> isFemale = new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return person.isFemale;
}
};
result = applyFilter(persons,isAdult);
System.out.println("Adults: ");
for(Person p : result)
System.out.println(p.name);
result = applyFilter(persons,Predicates.not(isAdult));
System.out.println("Children: ");
for(Person p : result)
System.out.println(p.name);
// Generic varargs will yield a warning, unfortionally...
result = applyFilter(persons, isAdult, isFemale);
System.out.println("Adult females: ");
for(Person p : result)
System.out.println(p.name);
}
}
You could perhaps define the Predicates using functions inside the classes you wish to sort and combine them with other classes that can be used to check if an value matches a certain objective.
e.g.
static class Person {
int age;
// ... code ...
public static Predicate<Person> ageFilter(final Range<Integer> range) {
return new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return range.contains(person.age);
}
};
}
}
Which can then be re-used for various filters:
Predicate<Person> isAdult = Person.ageFilter(Range.atLeast(18)); // Yields 18 and older.
Predicate<Person> isToddler = Person.ageFilter(Range.open(1,3)); // Yields ages 1-3, including 1 and 3 exact.
Predicate<Person> isMiddleAge = Person.ageFilter(Range.openClosed(45,65)); // Yields ages 45-65, 45 included, 65 excluded.

grouping objects java 8

I have something like the below :
public class MyClass {
private Long stackId
private Long questionId
}
A collection of say 100, where the stackid could be duplicate with different questionIds. Its a one to many relationship between stackId and questionId
Is there a streamy, java 8 way to convert to the below strcuture :
public class MyOtherClass {
private Long stackId
private Collection<Long> questionIds
}
Which would be a collection of 25, with each instance having a nested collection of 4 questionIds.
Input :
[{1,100},{1,101},{1,102},{1,103},{2,200},{2,201},{2,202},{1,203}]
Output
[{1, [100,101,102,103]},{2,[200,201,202,203]}]
The straight-forward way with the Stream API involves 2 Stream pipelines:
The first one creates a temporary Map<Long, List<Long>> of stackId to questionIds. This is done with the groupingBy(classifier, downstream) collectors where we classify per the stackId and values having the same stackId are mapped to their questionId (with mapping) and collected into a list with toList().
The second one converts each entry of that map into a MyOtherClass instance and collects that into a list.
Assuming you have a constructor MyOtherClass(Long stackId, Collection<Long> questionIds), a sample code would be:
Map<Long, List<Long>> map =
list.stream()
.collect(Collectors.groupingBy(
MyClass::getStackId,
Collectors.mapping(MyClass::getQuestionId, Collectors.toList())
));
List<MyOtherClass> result =
map.entrySet()
.stream()
.map(e -> new MyOtherClass(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Using StreamEx library, you could do that in a single Stream pipeline. This library offers a pairing and first collectors. This enables to pair two collectors and perform a finisher operation on the two collected results:
The first one only keeps the first stackId of the grouped elements (they will all be the same, by construction)
The second one mapping each element into their questionId and collecting into a list.
The finisher operation just returns a new instance of MyOtherClass.
Sample code:
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static one.util.streamex.MoreCollectors.first;
import static one.util.streamex.MoreCollectors.pairing;
// ...
Collection<MyOtherClass> result =
StreamEx.of(list)
.groupingBy(
MyClass::getStackId,
pairing(
collectingAndThen(mapping(MyClass::getStackId, first()), Optional::get),
mapping(MyClass::getQuestionId, toList()),
MyOtherClass::new
)
).values();
List<MyClass> inputs = Arrays.asList(
new MyClass(1L, 100L),
new MyClass(1L, 101L),
new MyClass(1L, 102L),
new MyClass(1L, 103L),
new MyClass(2L, 200L),
new MyClass(2L, 201L),
new MyClass(2L, 202L),
new MyClass(2L, 203L)
);
Map<Long, List<Long>> result = inputs
.stream()
.collect(
Collectors.groupingBy(MyClass::getStackId,
Collectors.mapping(
MyClass::getQuestionId,
Collectors.toList()
)
)
);
You can use the java8 groupingBy collector. Like this:
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RandomTest {
class MyClass {
private Long stackId;
private Long questionId;
public MyClass(Long stackId, Long questionId) {
this.stackId = stackId;
this.questionId = questionId;
}
public Long getStackId() {
return stackId;
}
public Long getQuestionId() {
return questionId;
}
}
public class MyOtherClass {
private Long stackId;
private Set<Long> questionIds;
public MyOtherClass(Long stackId, Set<Long> questionIds) {
this.stackId = stackId;
this.questionIds = questionIds;
}
public Long getStackId() {
return stackId;
}
public Set<Long> getQuestionIds() {
return questionIds;
}
}
#Test
public void test() {
List<MyClass> classes = new ArrayList<>();
List<MyOtherClass> otherClasses = new ArrayList<>();
//populate the classes list
for (int j = 1; j <= 25; j++) {
for (int i = 0; i < 4; i++) {
classes.add(new MyClass(0L + j, (100L*j) + i));
}
}
//populate the otherClasses List
classes.stream().collect(Collectors
.groupingBy(MyClass::getStackId, Collectors.mapping(MyClass::getQuestionId, Collectors.toSet())))
.entrySet().stream().forEach(
longSetEntry -> otherClasses.add(new MyOtherClass(longSetEntry.getKey(), longSetEntry.getValue())));
//print the otherClasses list
otherClasses.forEach(myOtherClass -> {
System.out.print(myOtherClass.getStackId() + ": [");
myOtherClass.getQuestionIds().forEach(questionId-> System.out.print(questionId + ","));
System.out.println("]");
});
}
}

Categories