You want intersection of the list while saving of first list type.
Get the common field values in the Set.
valuesToCheck=secondList.stream().map(SecondListObject::commonFiled).collect(Collectors.toList);
'''
Apply a stream on the first while filtering based on the matching common field value in the set built in the previous step.
firstList.stream().filter(x->valuesToCheck.contains(x.getCommonFiled)).collect(toList)
You got the gist.
Answer from Gajraj Tanwar on Stack OverflowYou want intersection of the list while saving of first list type.
Get the common field values in the Set.
valuesToCheck=secondList.stream().map(SecondListObject::commonFiled).collect(Collectors.toList);
'''
Apply a stream on the first while filtering based on the matching common field value in the set built in the previous step.
firstList.stream().filter(x->valuesToCheck.contains(x.getCommonFiled)).collect(toList)
You got the gist.
Here is one possible solution. (Not tested.)
Assuming that the lists are are random access and the same length, and that neither the list elements or the properties are null:
List<SomeType> list1 = ...
List<SomeType> list2 = ...
List<SomeType> res =
IntStream.range(0, list1.size())
.filter(i -> list1.get(i).getSomeProp().equals(list2.get(i).getSomeProp()))
.map(i -> list1.get(i))
.Collectors.toList();
(This will also work if the lists aren't random access, but it will be O(N^2).)
Supposing that the class is named Foo and that the field to change is String valueToReplace with getter/setter, you could use listOne.replaceAll() in this way :
list1.replaceAll(one -> list2.stream()
.filter(other -> other.getCode().equals(one.getCode())
.findAny()
.map(Foo::getValueToReplace)
.ifPresent( newValue -> one.setValueToReplace(newValue));
return one;
)
The idea is for each elements of list1 you replace the valueToReplace field of it by the value of the first match of list2. Otherwise you do nothing.
This code is not as efficient as it could but for small lists it is perfect.
For bigger lists, using a Map to store code/valueToReplace is very welcome.
// supposing that the code are unique in each list
Map<Integer, String> mapOfValueToReplaceByCode =
list2.stream()
.collect(toMap(Foo::getCode, Foo::getValueToReplace));
list1.replaceAll(one -> {
String newValue = mapOfValueToReplaceByCode.get(one.getCode());
if (newValue != null){
one.setValueToReplace(newValue);
}
return one;
)
Just keep the replace values in a map (with code as key) and then iterate over list1 to modify where necessary.
Map<String, String> replaceValues = list2.stream()
.collect(Collectors.toMap(x -> x.code, x -> x.tobereplace));
list1.stream
.filter(x -> replaceValues.containsKey(x.code))
.forEach(x -> x.tobereplace = replaceValues.get(x.code));
EDIT
As josejuan points out in the comments, the Collectors.toMap will throw an exception if list2 contains duplicate values. The OP doesn't really specify what to do in that case, but the solution is using a merge function in the Collectors.toMap.
This will use the first element it encounters with any given code:
Map<String, String> replaceValues = list2.stream()
.collect(Collectors.toMap(x -> x.code, x -> x.tobereplace, (x1, x2) -> x1));
The merge policy could be anything, like using the first element with a non-empty value e.g.
If the list was known to not have duplicates, please use a Set instead of a List. It will make things clearer for anyone reading the code, and help you avoid unnecessary checks.
You can check listA's all element exists in listB
boolean isSame = listA.stream()
.allMatch(a -> listB.stream()
.anyMatch(b -> b.getCode().equals(a.getCode())));
You can create a set of code from a list and check contains in O(1)
Set<String> set = listB.stream().map(e -> e.getCode()).collect(Collectors.toSet());
Then you can find in set for every list element
boolean isSame = listA.stream().allMatch(a -> set.contains(a.getCode()));
You need to start by pairing the elements of both lists together. Kotlin does it much more easily, but in java something like this should work:
IntStream.range(0, Math.min(listA.size(), listB.size()))
.mapToObj(i -> new Map.SimpleEntry<>(listA.get(i), listB.get(i)))
.allMatch(entry -> entry.getKey().getCode().equals(entry.getValue().getCode());
Let listOne.size() is N and listTwo.size() is M.
Then 2-for-loops solution has complexity of O(M*N).
We can reduce it to O(M+N) by indexing listTwo by ids.
Case 1 - assuming listTwo has no objects with the same id
// pair each id with its marks
Map<String, String> marksIndex = listTwo.stream().collect(Collectors.toMap(ObjectTwo::getId, ObjectTwo::getMarks));
// go through list of `ObjectOne`s and lookup marks in the index
listOne.forEach(o1 -> o1.setScore(marksIndex.get(o1.getId())));
Case 2 - assuming listTwo has objects with the same id
final Map<String, List<ObjectTwo>> marksIndex = listTwo.stream()
.collect(Collectors.groupingBy(ObjectTwo::getId, Collectors.toList()));
final List<ObjectOne> result = listOne.stream()
.flatMap(o1 -> marksIndex.get(o1.getId()).stream().map(o2 -> {
// make a copy of ObjectOne instance to avoid overwriting scores
ObjectOne copy = copy(o1);
copy.setScore(o2.getMarks());
return copy;
}))
.collect(Collectors.toList());
To implement copy method you either need to create a new object and copy fields one by one, but in such cases I prefer to follow the Builder pattern. It also results in more "functional" code.
Following code copies marks from ObjectTwo to score in ObjectOne, if both ids are equal, it doesn't have intermediate object List<ObjectOne> result
listOne.stream()
.forEach(one -> {listTwo.stream()
.filter(two -> {return two.getId().equals(one.getId());})
.limit(1)
.forEach(two -> {one.setScore(two.getMarks());});
});
It is not a good idea to have side effects like setting of properties in the middle of a Stream. A better solution is to just create a Map of your User instances:
Map<Long, User> usersById =
users.stream().collect(Collectors.toMap(User::getUserId, u -> u));
Now you can simply loop through your WorkflowStatus objects:
for (WorkflowStatus status : workflowStatuses) {
Long userId = status.getUserId();
Long statusId = status.getWorkflowStatusId();
usersById.get(userId).setWorkflowStatusId(statusId);
}
<User> filteredList = users.stream().filter(user -> workflowStatuses
.stream()
.allMatch(user.getUserId().equals(workflowStatus.getWorkflowStatusId)))
.map(how to set workflowStatusId in user? can i do something like user->user.setWorkflowStatusId((workflowStatus.getWorkflowStatusId)))
.collect(Collectors.toList());
On this generate the lamba exprestion loop on java 8
workflowStatus.foreach( data ) -> { user.setworkflowStatusId(data.getWorkflowStatusId);user.setuserId(data.getuserId);}
Or simple replace below
<User> filteredList = users.stream().filter(user -> workflowStatuses
.stream()
.allMatch(user.getUserId().equals(workflowStatus.getWorkflowStatusId)))
.map(workflowStatus.foreach( data ) -> { user.setworkflowStatusId(data.getWorkflowStatusId);user.setuserId(data.getuserId)})
.collect(Collectors.toList());
You could write the following code:
products.forEach(p -> {
ProductWebElement productElement = productElements.stream().filter(
pe -> pe.getProductCode().equals(p.getCode())
).findAny().orElseThrow(() -> new AssertionError("Product: " + p.getCode() + " not found!"));
Assert.assertEquals(p.getQuantity(), productElement.getQuantity());
});
For each of your products, we try to find any matching ProductElement (having the same product code).
If none is found, we throw a AssertionError. If we found one, we can assert that the quantities are the same.
You should get rid of the double iteration in general. One way to do it, is to store the values in a Map:
Map<ProductCode,Quantity> map=productElements.stream().collect(
Collectors.toMap(ProductWebElement::getProductCode, ProductWebElement::getQuantity));
products.forEach(product -> {
Quantity q=map.get(product.getCode());
if(q==null) fail("Product: " + product.getCode() + " not found!");
Assert.assertEquals(product.getQuantity(), q);
});
Note that since the actual types of your properties don’t show up in your question, I used ProductCode and Quantity as placeholders here. Further note that I stored the actual quantity property values in the map, as that’s the only value you are interested in, thus there is no need to query the ProductWebElements again in the second loop.
This logic could also be implemented using ordinary pre-Java 8 loops…
If I understand correctly, this is the example scenario:
- listOne [datab] items:
[A, B, C, D] - listTwo [front] items:
[B, C, D, E, F]
and what you need to get as an effect is:
- added:
[E, F] - deleted:
[A]
First thing first, I would use some type adapter or extend the different types from one common class and override the equals method so you can match them by id and name
Secondly, this is very easy operations on sets (you could use set's but list are fine too). I recommend using a library: https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html
And now basically:
- added is
listTwo - listOne - deleted is
listOne - listTwo
and using java code:
- added:
CollectionUtils.removeAll(listTwo, listOne) - deleted:
CollectionUtils.removeAll(listOne, listTwo)
Otherwise, all collections implementing Collection (Java Docs) also has removeAll method, which you can use.
I propose solution using java 8 streams:
ArrayList<ObjOne> list = new ArrayList<>(Arrays.asList(new ObjOne("1","1"),new ObjOne("3","3"),new ObjOne("2","2")));
ArrayList<ObjTwo> list2 = new ArrayList<>(Arrays.asList(new ObjTwo("1","1"),new ObjTwo("3","3"),new ObjTwo("4","4")));
List<ObjOne> removed = list.stream().filter(o1 -> list2.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
.collect(Collectors.toList());
System.out.print("added ");
removed.forEach(System.out::println);
List<ObjTwo> added = list2.stream().filter(o1 -> list.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
.collect(Collectors.toList());
System.out.print("removed ");
added.forEach(System.out::println);
This is basically your solution but implemented using streams, which will make your code shorter and easer to read
You can make the code shorter with filter and count, but using loops is cleaner here. Streams are not always the solution.
return Arrays.asList(
IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) > b.get(i)).count(),
IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) < b.get(i)).count()
);
Java 8 - partitioningBy() & counting()
One of the way to solve this problem with streams performing only one iteration over the given set of data is to make use of the built Collectors partitioningBy() and counting():
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.map(i -> a.get(i) - b.get(i))
.filter(i -> i != 0)
.boxed()
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(i -> i > 0, Collectors.counting()),
map -> Arrays.asList(map.get(true).intValue(), map.get(false).intValue())
));
}
Java 8 - custom Collector
Another option would be to define a custom Collector using static factory method Collector.of(). As well as previous approach, it would allow to process the data using a single stream:
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.boxed()
.collect(Collector.of(
() -> new int[]{0, 0},
(int[] score, Integer i) -> {
if (a.get(i) > b.get(i)) score[0]++;
if (b.get(i) > a.get(i)) score[1]++;
},
(int[] left, int[] right) -> {
Arrays.setAll(left, i -> left[i] + right[i]);
return left;
},
arr -> Arrays.asList(arr[0], arr[1])
));
}
Java 12 - Collector teeing()
Another option that would allow to produce the result using a single stream is Java 12 Collector teeing(), which expects two downstream Collectors and a Function which performs a final transformation by merging the results they produced.
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.boxed()
.collect(Collectors.teeing(
Collectors.filtering(i -> a.get(i) > b.get(i), Collectors.counting()),
Collectors.filtering(i -> b.get(i) > a.get(i), Collectors.counting()),
(alice, bob) -> List.of(alice.intValue(), bob.intValue())
));
}
While Deadpool's answer is great, I'd be more inclined to avoid using Optional here, as there is no use for the actual value to be used.
Also, creating a Stream for each element traversing the Stream is not a good idea in terms of performance.
Set<String> products = arrayList1.stream()
.map(Action::getProductSku)
.map(String::toUpperCase)
.collect(toSet());
boolean shouldThrowAnException = arrayList2.stream()
.filter(type -> "EQUIPMENT".equals(type.getItemType()))
.map(Action::getProductSku)
.map(String::toUpperCase)
.noneMatch(products::contains);
if (shouldThrowAnException) {
// throw exception
}
You can stream the arrayList2 and try finding at least one matching element in arrayList1 using findFirst or else throw exception
arrayList2.stream()
.filter(type -> "EQUIPMENT".equals(type.getItemType()))
.filter(list2-> arrayList1.stream()
.anyMatch(list1->list1.getProductSku().equals(list2.getProductSku())))
.findFirst()
.orElseThrow(//some exception)
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
You can map from A --> String , apply the distinct intermediate operation, utilise limit(2) to enable optimisation where possible and then check if count is less than or equal to 1 in which case all objects have the same name and if not then they do not all have the same name.
boolean result = myList.stream()
.map(A::getName)
.distinct()
.limit(2)
.count() <= 1;
With the example shown above, we leverage the limit(2) operation so that we stop as soon as we find two distinct object names.
One way is to get the name of the first list and call allMatch and check against that.
String firstName = yourListOfAs.get(0).name;
boolean allSameName = yourListOfAs.stream().allMatch(x -> x.name.equals(firstName));