I am working with two java objets. One of these object just have string attributes and the other one have strings and list of the first object.
The goal for me is to replace the double loops for a hasmap to reduce the time complexity. In this loop I do a equality check to see if some strings matches.
``
public class Object1 {
String name;
String xyz;
List<Object2> listObject2;
}
public class Object2 {
String name;
String abc;
String def;
}
Now in my main file I have the following function:
public fillNestedObject() {
List<Object1> listObject1 = new ArrayList();
listObject1 = fetchObjects1FromApi();
List<Object2> listObject2 = new ArrayList();
listObject2 = fetchObjectsFromApi2();
for(Object1 object1 : listObject1){
List<Object2> tmpList = new ArrayList();
for(Object2 object2 : listObject2) {
if(object1.getName().equals(object2.getName())){
tmpList.add(object2)
}
}
object1.setListObject2(tmpList)
}
}
I'm pretty sure that to reduce the time complexity I can replace my double for loops by a hasmap (or 2?) but I am not sure how to do this because I want the equality to be true.
I read that I should use stream to convert into hasmaps on other questions but I am not sure how streams are gonna be used to achieve what I want.
First the Object2 stream is reduced to a map of the list of items with the same name.
Then retrieve the object 1 items from collect.
Map<String, List<Object2>> reduced2 = Collections.unmodifiableMap(
stream2.reduce(new HashMap<>(), (a, b) -> {
if (!a.containsKey(b.getName())) {
a.put(b.getName(), new ArrayList<>());
}
a.get(b.getName()).add(b);
return a;
}, (a, b) -> b));
stream1.peek(object1 -> object1.setListObject2(reduced2.get(object1.getName())))
.collect(Collectors.toList());
Related
I have a little problem that is driving me crazy.
I have a
List<Integer> with ids.
List<ObjectA> with 3 variables:
an id, and two string
I have to sort the second list by putting at the top the elements with id contained in the first list, then by string asc and by the second string asc.
What is the easiest way to make this work? I am trying to use the .sort(), Comparators etc.
An example:
#Getter
#Setter
public class ObjectA {
private Integer id;
private String code;
private String name;
}
// comparator:
static class SortByCode implements Comparator<ObjectA> {
public int compare(ObjectA a, ObjectA b) {
String as = a.getCode();
String bs = b.getCode();
return as.compareTo(bs);
}
}
static class SortByName implements Comparator<ObjectA> {
public int compare(ObjectA a, ObjectA b) {
String as = a.getName();
String bs = b.getName();
return as.compareTo(bs);
}
}
// then in service:
List<Integer> idsPreferred = new ArrayList<>();
List<ObjectA> listObj = new ArrayList<>();
idsPreferred = .... add preferred ids;
listObj = .... add objects;
listObj.sort(new SortByCode()).thenComparing(new SortByName());
With this i sort by code and by name - but i need to add the sorting by the first list - I need the elements that have an id contained in the List to come before the others.
I suppose something like this using chained comparing by extracted key:
listObj.sort(Comparator.comparing(o -> !idsPreferred.contains(((ObjectA) o).getId()))
.thenComparing(o -> ((ObjectA) o).getId())
.thenComparing(o -> ((ObjectA) o).getCode())
.thenComparing(o -> ((ObjectA) o).getName()));
or
listObj.sort(Comparator.comparing(ObjectA::getId,
(id1,id2)-> {if (!((idsPreferred.contains(id1))^idsPreferred.contains(id2)))
return 0;
else return (idsPreferred.contains(id2))?1:-1;})
.thenComparing(ObjectA::getId)
.thenComparing(ObjectA::getCode)
.thenComparing(ObjectA::getName));
The solution will involve 2 steps-
check id of objects from second list, which are present in first list.
Sort the contained objects using either of the solutions suggested- How to sort List of objects by some property
I have two objects:
First object:
public class ABC {
private String a;
private String b;
private String c;
private Boolean d;
//getters and setters
}
Second Object
public class DEF{
private String a;
private String b;
private String c;
//getters and setters
}
There are two Lists of objects ABC and DEF - List listabc and List listdef. And I want to run the following operation:
for(ABC abc : listabc){
if (abc.getD()) {
for(DEF def : listdef){
if(abc.getA().equals(def.getA()){
abc.setB(def.getB());
abc.setC(def.getC())
}
}
}
}
I am just looking to see if there's a way to implement this operation in Stream so that I can avoid iterating through lists and writing this code.
This solution should give the same output as your nested for loop.
listabc.stream()
.filter(ABC::getD)
.forEach(abc -> listdef.stream()
.filter(def -> def.getA().equals(abc.getA()))
.findFirst().ifPresent(def -> {
abc.setB(def.getB());
abc.setC(def.getC());
}));
You can replace the 2 nested for loops with two streams:
List<ABC> listWithNewValues = listabc.stream().filter(ABC::getD)
.map(abc -> {
DEF matchingDef = listdef.stream().filter(def -> def.getA().equals(abc.getA())).findAny().get();
abc.setB(matchingDef.getB());
abc.setC(matchingDef.getC());
return abc;
})
.collect(Collectors.toList());
You use your 2 lists, listabc and listdef. You stream the listabc, you filter on the boolean and you map to the new values. During mapping you stream the listdef and find the matching element. You can extract the logic to a method to make it cleaner.
I have two Lists containing objects of this class:
public class SchoolObj
{
private String name;
private String school;
public SchoolObj()
{
this(null, null);
}
public SchoolObj(String nameStr, String schoolStr)
{
this.setName(nameStr);
this.setSchool(schoolStr);
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public String getSchool()
{
return this.school;
}
public void setSchool(String school)
{
this.school = school;
}
#Override
public String toString()
{
return this.getName() + ' ' + this.getSchool();
}
}
I want to compare the objects in those two lists by name and school. If they are equal I need to create a new List containing those SchoolObj objects which are found in both Lists.
I know we can use two for loops and do it is in the createSharedListViaLoop method below.
My question is, how can I accomplish the same thing with Java streams?
I tried with createSharedListViaStream below, but it is not working as expected.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest
{
public static void main(String[] args)
{
List<SchoolObj> listOne = new ArrayList<SchoolObj>();
// TODO: Add sample data to listOne.
listOne.add(new SchoolObj("nameA", "schoolX"));
listOne.add(new SchoolObj("nameC", "schoolZ"));
List<SchoolObj> listTwo = new ArrayList<SchoolObj>();
// TODO: Add sample data to listTwo.
listTwo.add(new SchoolObj("nameA", "schoolX"));
listTwo.add(new SchoolObj("nameB", "schoolY"));
// Print results from loop method.
System.out.println("Results from loop method:");
List<SchoolObj> resultsViaLoop = StreamTest.createSharedListViaLoop(listOne, listTwo);
for (SchoolObj obj : resultsViaLoop)
{
System.out.println(obj);
}
// Print results from stream method.
System.out.println("Results from stream method:");
List<SchoolObj> resultsViaStream = StreamTest.createSharedListViaStream(listOne, listTwo);
for (SchoolObj obj : resultsViaStream)
{
System.out.println(obj);
}
}
public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
List<SchoolObj> result = new ArrayList<SchoolObj>();
for (SchoolObj one : listOne)
{
for (SchoolObj two : listTwo)
{
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
}
}
}
return result;
}
public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
List<SchoolObj> listOneList = listOne.stream().filter(two -> listTwo.stream()
.anyMatch(one -> one.getName().equals(two.getName()) && two.getSchool().equals(one.getSchool())))
.collect(Collectors.toList());
return listOneList;
}
}
Let's run through each part of the code. First, createSharedListViaStream:
public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We create a stream of elements from the first list.
List<SchoolObj> listOneList = listOne.stream()
// We select any elements such that in the stream of elements from the second list
.filter(two -> listTwo.stream()
// there is an element that has the same name and school as this element,
.anyMatch(one -> one.getName().equals(two.getName())
&& two.getSchool().equals(one.getSchool())))
// and collect all matching elements from the first list into a new list.
.collect(Collectors.toList());
// We return the collected list.
return listOneList;
}
After running through the code, it does exactly what you want it to do. Now, let's run through createSharedListViaLoop:
public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We build up a result by...
List<SchoolObj> result = new ArrayList<SchoolObj>();
// going through each element in the first list,
for (SchoolObj one : listOne)
{
// going through each element in the second list,
for (SchoolObj two : listTwo)
{
// and collecting the first list's element if it matches the second list's element.
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
}
}
}
// We return the collected list
return result;
}
So far, so good... right? In fact, your code in createSharedListViaStream is fundamentally correct; instead, it is your createSharedListViaLoop that may be causing discrepancies in output.
Think about the following set of inputs:
List1 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameC","SchoolZ")]
List2 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameA","SchoolX"), SchoolObj("nameB","SchoolY")]
Here, createSharedListViaStream will return the only element of the first list that appears in both lists: SchoolObj("nameA","SchoolX"). However, createSharedListViaLoop will return the following list: [SchoolObj("nameA","SchoolX"),SchoolObj("nameA","SchoolX")]. More precisely, createSharedListViaLoop will collect the correct object, but it will do so twice. I suspect this to be the reason for the output of createSharedListViaStream to be "incorrect" based on comparison to the output of createSharedListViaLoop.
The reason that createSharedListViaLoop does this duplication is based on the lack of termination of its inner for loop. Although we iterate over all elements of the first list to check if they are present in the second, finding a single match will suffice to add the element to the result. We can avoid redundant element addition by changing the inner loop to the following:
for (SchoolObj one : listOne)
{
for (SchoolObj two : listTwo)
{
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
break;
}
}
}
Additionally, if you don't want duplicate Objects in your list (by location in memory), you can use distinct like so:
List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());
As a final caution, the above will keep the results distinct in the following scenario:
List<SchoolObj> list = new ArrayList<>();
SchoolObj duplicate = new SchoolObj("nameC", "schoolD");
listOne.add(duplicate);
listOne.add(duplicate);
list.stream().distinct().forEach(System.out::println);
// prints:
// nameC schoolD
However, it will not work in the following scenario, unless you override the equals method for SchoolObj:
List<SchoolObj> list = new ArrayList<>();
listOne.add(new SchoolObj("nameC", "schoolD"));
listOne.add(new SchoolObj("nameC", "schoolD"));
list.stream().distinct().forEach(System.out::println);
// prints (unless Object::equals overridden)
// nameC schoolD
// nameC schoolD
You can filter in one list if contains in another list then collect.
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> listOne.contains(e))
.collect(Collectors.toList());
You need to override equals() method in SchoolObj class. contains() method you will uses the equals() method to evaluate if two objects are the same.
#Override
public boolean equals(Object o) {
if (!(o instanceof SchoolObj))
return false;
SchoolObj n = (SchoolObj) o;
return n.name.equals(name) && n.school.equals(school);
}
But better solution is to use Set for one list and filter in another list to collect if contains in Set. Set#contains takes O(1) which is faster.
Set<SchoolObj> setOne = new HashSet<>(listOne);
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> setOne.contains(e))
.collect(Collectors.toList());
You need to override hashCode() method also along with equals() in SchoolObj class for Set#contains.(assuming name and school can't be null)
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name.hashCode();
result = prime * result + school.hashCode();
return result;
}
Here you will get details how to override equals and hashCode in a better way
In my case I had Two lists. I compared both lists having common emails and collected the objects like this:
List<TUsers> comparedUsersOnEmail = tUsers.stream().filter(o1 -> userR.stream()
.anyMatch(o2->o2.getEmail().equals(o1.getEmail()))).collect(Collectors.toList());
Using Java 6, how is the better way to know if a list of objects contains a field of another list of objects?
I have implemented it using two for but I do not know if this affects to performance. Is there any Java feature like streams in Java 8?
public class Object1 {
private BigDecimal field1;
private String field2;
private String field3;
}
public class Object2 {
private BigDecimal field1;
private String field2;
}
List<Object1> list1 = new ArrayList<Object1>();
List<Object2> list2 = new ArrayList<Object2>();
List<Object1> listFilter = new ArrayList<Object1>();
for (Object object1 : list1) {
for (Object object2 : list2) {
if(object1.getField1().compareTo(object2.getField1())==0) {
listFilter.add(object1);
break;
}
}
}
Your code:
for (Object object1 : list1) {
for (Object object2 : list2) {
if(object1.getField1().compareTo(object2.getField1())==0) {
listFilter.add(object1);
break;
}
}
}
This is O(n2) time complexity. You should put the Object2.field1 values into a Set<?> first, then checking for existence is O(n)
// Build a Set of unique Object2.field1 values
Set<BigDecimal> o2f1set = new HashSet<BigDecimal>();
for (Object2 o2 : list2)
o2f1Set.add(o2.getField1);
// Select Object1 instances whose field1 value matches any
// field1 value that was in list2
for (Object1 o1 : list1)
if (o2f1Set.contains(o1.getField1())
listFilter.add(o1);
This of course assumes that the class of field1 properly implement equals() and hashCode(), which BigDecimal does.
If the range of possible field1 values is integer, strongly bounded and relatively small (i.e. your requirements and model limit the values to a range, say 0..1000), you could optimize this by using a boolean array instead of a set. In those highly limited circumstances this would improve performance even more.
I have read several posts for this but not getting the exact thing I am looking for. I know how to develop a complex logic for this, this is for Android and we can't expect too much processing on the device due to the limited resources available.
I have an ArrayList of an bean class objects consisting five fields as
Java Bean -- MyShares
fileName
filePath
fileSize
isShared
Creator
I have another ArrayList of String which contains only filepaths. Now what I want is to remove all the common elements between the two arraylist means the file paths in seconds arraylist and file path in first arraylist objects are similar then I have to remove from both of the arraylist but I don't want a new arraylist which contains the uncommon elements. But I want to get my both arraylist only without their common elements.
You could use a Map from String to your object type (I used Obj in order to make a SSCCE).
Assume we are given a list objects and a list strings.
Steps:
Put all objects in a map with their str variable as key
Get all those str variables using map.keySet()
Get all strings that are in objects but not in strings by keys.removeAll(strings)
Get all strings that are in strings but not in objects by strings.removeAll(keys)
Get the objects that correspond to the remaining keys
Note that you need to be careful in steps 3 and 4, because you need to back up one of the collections.
import java.util.*;
public class Test {
public static void main(String[] args) throws Exception {
new Test();
}
public Test() {
List<Obj> objects = new ArrayList<>();
objects.add(new Obj("a"));
objects.add(new Obj("b"));
objects.add(new Obj("c"));
List<String> strings = new ArrayList<>();
strings.add("a");
strings.add("d");
strings.add("e");
remove(objects, strings);
System.out.println(objects);
System.out.println(strings);
}
public void remove(List<Obj> objects, List<String> strings) {
Map<String, Obj> map = new HashMap<>();
for (Obj object : objects) {
map.put(object.str, object);
}
Set<String> keys = map.keySet();
List<String> oldStrings = new ArrayList<>(strings);
strings.removeAll(keys);
keys.removeAll(oldStrings);
objects.clear();
for (String key: keys) {
objects.add(map.get(key));
}
}
public class Obj {
public String str;
public Obj(String str) {
this.str = str;
}
#Override
public String toString() {
return str;
}
}
}
Prints:
[b, c]
[d, e]
Rough Java code:
HashSet<String> commonKeys = new HashSet();
for (Share share : shares) {
commonKeys.add(share.filePath);
}
commonKeys.retainAll(filePaths);
for (Iterator<Share> it = shares.iterator(); it.hasNext(); ) {
Share share = it.next();
if (commonKeys.contains(share.filePath)) {
it.remove();
}
}
filePaths.removeAll(commonKeys);
This won't be O(N) because remove on an ArrayList is expensive. To get O(N) behavior you either need to create new ArrayList instances, or add the elements you don't want removed to temporary lists, and then clear() and add them back into the original lists.
I will go with some clues for you
Suppose you have two lists one for bean objects namely myBeans and another for filePaths namely filePaths
List<MyBean> beansToRemove = new ArrayList<MyBean>();
List<FilePath> filePathsToRemove = new ArrayList<FilePath>();
for(Bean myBean : myBeans) {
for(FilePath filePath : filePaths) {
if(myBean.getfilePath.equals(filePath.getFilePath())) {
beansToRemove.add(myBean);
filePathsToRemove.add(filePath);
}
}
}
//Now remove filePaths and beans if any
for(Bean myBean : beansToRemove) {
myBeans.remove(myBean);
}
for(FilePath filePath : filePathsToRemove) {
filePaths.remove(filePath);
}
it is just a flow to make you clear for what to do; you can further customize it according to your needs.
You can use an outer loop to scan over the Bean objects, and an inner loop to scan over the file paths.
pseudo code:
for (Bean i in beans) {
for (String p in paths) {
if (i.path.equals(p)) {
beansToRemove.add(i);
pathsToRemove.add(p);
}
}
}
beans.removeAll(beansToRemove);
paths.removeAll(pathsToRemove);
I'm not sure if my extra arraylists to track the removed arraylists go against your question or not since the original arrays remain.
If you presort both arrays on the path and keep track of the position in each area (not exhaustive search) you can improve it from n2 to nlgn