Efficient intersection of two List<String> in Java? - java

Question is simple:
I have two List
List<String> columnsOld = DBUtils.GetColumns(db, TableName);
List<String> columnsNew = DBUtils.GetColumns(db, TableName);
And I need to get the intersection of these. Is there a quick way to achieve this?

You can use retainAll method:
columnsOld.retainAll (columnsNew);

Using Google's Guava library:
Sets.intersection(Sets.newHashSet(setA), Sets.newHashSet(setB))
Note: This is much more efficient than naively doing the intersection with two lists: it's O(n+m), versus O(n×m) for the list version. With two million-item lists it's the difference between millions of operations and trillions of operations.

Since retainAll won't touch the argument collection, this would be faster:
List<String> columnsOld = DBUtils.GetColumns(db, TableName);
List<String> columnsNew = DBUtils.GetColumns(db, TableName);
for(int i = columnsNew.size() - 1; i > -1; --i){
String str = columnsNew.get(i);
if(!columnsOld.remove(str))
columnsNew.remove(str);
}
The intersection will be the values left in columnsNew. Removing already compared values fom columnsOld will reduce the number of comparisons needed.

How about
private List<String> intersect(List<String> A, List<String> B) {
List<String> rtnList = new LinkedList<>();
for(String dto : A) {
if(B.contains(dto)) {
rtnList.add(dto);
}
}
return rtnList;
}

There is a nice way with streams which can do this in one line of code and you can two lists which are not from the same type which is not possible with the containsAll method afaik:
columnsOld.stream().filter(c -> columnsNew.contains(c)).collect(Collectors.toList());
An example for lists with different types. If you have a realtion between foo and bar and you can get a bar-object from foo than you can modify your stream:
List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));
fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());

If you put the second list in a set say HashSet. And just iterate over the first list checking for presence on the set and removing if not present, your first list will eventually have the intersection you need.
It will be way faster than retainAll or contains on a list.
The emphasis here is to use a set instead of list. Lookups are O(1).
firstList.retainAll (new HashSet (secondList)) will also work.

using retainAll if don't care occurrences, otherwise using N.intersection
a = N.asList(12, 16, 16, 17, 19);
b = N.asList(16, 19, 107);
a.retainAll(b); // [16, 16, 19]
N.println(a);
a = N.asList(12, 16, 16, 17, 19);
b = N.asList(16, 19, 107);
a = N.intersect(a, b);
N.println(a); // [16, 19]
N is an utility class in abacus-common

use org.apache.commons.collections4.ListUtils#intersection

With Java 8 Stream API (and Java 9 List.of()) you can do following:
List<Integer> list1 = List.of(1, 1, 2, 2);
List<Integer> list2 = List.of(2, 2, 3, 3);
List<Integer> intersection = list1.stream()
.filter(list2::contains)
.distinct()
.collect(Collectors.toList());

Related

Comparing elements at same position of two list using Java 8 stream

I have two lists I want to compare each element of list 1 to list 2 and get result in list 3 e.g
List1 = {99,22,33}
list2 = {11,24,33}
Result:
list3 = {1,-1,0}
How I can do this preferably using stream?
Try it like this:
This replaces the stream of ints used to index each list, with the result of the comparison. Then collect those into a list.
Note: I added a safeguard to account for different size lists. It will use the smaller of the two to prevent an exception being thrown. But trailing elements of the longer list will be ignored.
List<Integer> list1 = List.of(99, 22, 33);
List<Integer> list2 = List.of(11, 24, 33);
// safeguard against different sized lists.
int minLen = Math.min(list1.size(), list2.size());
List<Integer> result = IntStream.range(0, minLen)
.map(i -> list1.get(i).compareTo(list2.get(i)))
.boxed().collect(Collectors.toList());
System.out.println(result);
Prints
[1, -1, 0]

Uniquify Arraylist of Arrays by their second element

Given an Arraylist like this:
List<Integer[]> list = new Arraylist<>();
list.add(new Integer[] {13, 1});
list.add(new Integer[] {100, 2});
list.add(new Integer[] {143, 2});
list.add(new Integer[] {185, 3});
list.add(new Integer[] {111, 3});
list.add(new Integer[] {98, 4});
how can I uniquify the list in respect to their second element with lowest value for the first element?
Usually, I would simply turn a list into a set an return it as a list again but how do I handle this case?
Thanks in advance!
I am not sure if that's the best solution but you can still use a set for the found values and do it like that:
Set<Integer> found=new HashSet<>();
list.removeIf(p->!found.add(p[1]));
The idea is that you need to store the found values anyway. You can use a filter with the streams Api but it is harder to keep a state and you will probably still need to maintain another collection with the already added values. So I think it is an easy solution.
Thats if you have it as a given that first value is always 100. Otherwise you might need to rework it a bit ;)
You could define an intermediate object
class Intermediate implements Comparable<Intermediate> {
private Integer[] array;
public Intermediate(Integer[] arr) { array = arr; }
public boolean equals(Object o) {
return (o instanceof Intermediate) &&
array[1] == ((Intermediate)o).array[1];
}
public int compareTo(Intermediate i) {
return Integer.compare(array[0], i.array[0]);
}
public Integer[] toTarget() { return array; }
}
then create a stream from your list which maps to the intermediate
yourList.stream().map(Intermediate::new)
this is now a Stream<Intermediate>. The equals is defined on the second int, so you can use distinct, then map back to your array and collect.
.sorted() // make sure to take the lowest first value
.distinct()
.map(Intermediate::toTarget)
.collect(toList());
or, if you only want the first int from the arrays,
.map(i -> i.toTarget()[0])
.collect(toList());
(which would give you [13, 100, 111, 98] for your sample list).

How to compare two arrays in java and return an element that isn't included in two list

I need to provide a Java solution that given two list, a & b, that returns a value that is present in one list but not in another.
e.g.
lists a = [26, 13, 88, 9]
lists b = [26, 1, 8, 12]
for those kind of operation it would be better to use collections,
the method removeAll() will filter the data containers, from the doc:
Removes from this list all of its elements that are contained in the
specified collection (optional operation).
List<Integer> myVarListA = Arrays.asList(26, 13, 88, 9);
List<Integer> myVarListB = Arrays.asList(26, 1, 8, 12);
List<Integer> myVarListAcomplementB = new ArrayList<>(myVarListA);
List<Integer> myVarListBcomplementA = new ArrayList<>(myVarListB);
myVarListAcomplementB.removeAll(myVarListB);
myVarListBcomplementA.removeAll(myVarListA);
System.out.println("elements in A but no in B: " + myVarListAcomplementB);
System.out.println("elements in B but no in A: " + myVarListBcomplementA);
myVarListBcomplementA.addAll(myVarListAcomplementB);
System.out.println("both together: " + myVarListBcomplementA);
Simple solution is to calculate the intersection and remove that from the desired list. If you only want what is missing, you can optimize this a little by going a solution like ΦXocę 웃 Пepeúpa ツ.
The great thing about this solution is that you can easily expand this to use 3+ sets/lists. Instead of A-B = diff or A-I(A,B)=diff, you can also do A-I(A,I(B,C)) to find what A is missing from the common set between A, B and C.
public static <T> HashSet<T> intersection(Collection<T> a, Collection<T> b) {
HashSet<T> aMinusB = new HashSet<>(a);
aMinusB.removeAll(b);
HashSet<T> common = new HashSet<>(a);
common.removeAll(aMinusB);
return common;
}
Let's call the intersection set Collection I = intersection(a,b);.
Now if you want to find what is missing from list A that was in B:
new LinkedList(A).removeAll(I);//ordered and possibly containing duplicates
OR
new ArrayList(A).removeAll(I);//ordered and possibly containing duplicates. Faster copy time, but slower to remove elements. Experiment with this and LinkedList for speed.
OR
new LinkedHashSet<T>(a).removeAll(I);//ordered and unique
OR
new HashSet<T>(a).removeAll(I);//unique and no order
Also, this question is effectively duplicate of How to do union, intersect, difference and reverse data in java
You may try this:
a.removeAll(b);
Simply parse the second list and add unique elements to the first, delete others.
for (Integer elem : secondList)
if (firstList.contains(elem))
firstList.remove(elem);
else
firstList.add(elem);
In firstList you will have the values present only in one of the lists.

Iterate throught a collection and put all values into a list

I have a collection [[140], [141,143], [11], [11,22]]
what is the best way to take these numbers and store them into a array list with no duplicates?
[140,141,143,11,22]
Collection<Collection<Integer>> ints = new ArrayList<>();
Collections.addAll(ints,
Arrays.asList(149),
Arrays.asList(141, 143),
Arrays.asList(11),
Arrays.asList(11, 22));
// Java 8
List<Integer> flattenedUniqueInts = ints.stream()
.flatMap(x -> x.stream()).sorted().distinct()
.collect(Collectors.toList());
// Java 7, optimally using Set.
Set<Integer> result = new HashSet<>();
for (Collection<Integer> sub : ints) {
Collections.addAll(sub);
}
Two solutions, one for java 8, one for java 7. In Java 8 one could be content with an IntStream result, using flatMapToInt without collect; using int instead of Integer. And maybe experiment with parallel.

Common elements in two lists

I have two ArrayList objects with three integers each. I want to find a way to return the common elements of the two lists. Has anybody an idea how I can achieve this?
Use Collection#retainAll().
listA.retainAll(listB);
// listA now contains only the elements which are also contained in listB.
If you want to avoid that changes are being affected in listA, then you need to create a new one.
List<Integer> common = new ArrayList<Integer>(listA);
common.retainAll(listB);
// common now contains only the elements which are contained in listA and listB.
You can use set intersection operations with your ArrayList objects.
Something like this:
List<Integer> l1 = new ArrayList<Integer>();
l1.add(1);
l1.add(2);
l1.add(3);
List<Integer> l2= new ArrayList<Integer>();
l2.add(4);
l2.add(2);
l2.add(3);
System.out.println("l1 == "+l1);
System.out.println("l2 == "+l2);
List<Integer> l3 = new ArrayList<Integer>(l2);
l3.retainAll(l1);
System.out.println("l3 == "+l3);
Now, l3 should have only common elements between l1 and l2.
CONSOLE OUTPUT
l1 == [1, 2, 3]
l2 == [4, 2, 3]
l3 == [2, 3]
Why reinvent the wheel? Use Commons Collections:
CollectionUtils.intersection(java.util.Collection a, java.util.Collection b)
Using Java 8's Stream.filter() method in combination with List.contains():
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
/* ... */
List<Integer> list1 = asList(1, 2, 3, 4, 5);
List<Integer> list2 = asList(1, 3, 5, 7, 9);
List<Integer> common = list1.stream().filter(list2::contains).collect(toList());
consider two list L1 ans L2
Using Java8 we can easily find it out
L1.stream().filter(L2::contains).collect(Collectors.toList())
You can get the common elements between two lists using the method
"retainAll". This method will remove all unmatched elements from the list to
which it applies.
Ex.: list.retainAll(list1);
In this case from the list, all the elements which are not in list1 will be
removed and only those will be remaining which are common between list and
list1.
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(13);
list.add(12);
list.add(11);
List<Integer> list1 = new ArrayList<>();
list1.add(10);
list1.add(113);
list1.add(112);
list1.add(111);
//before retainAll
System.out.println(list);
System.out.println(list1);
//applying retainAll on list
list.retainAll(list1);
//After retainAll
System.out.println("list::"+list);
System.out.println("list1::"+list1);
Output:
[10, 13, 12, 11]
[10, 113, 112, 111]
list::[10]
list1::[10, 113, 112, 111]
NOTE: After retainAll applied on the list, the list contains common element between
list and list1.
List<String> lista =new ArrayList<String>();
List<String> listb =new ArrayList<String>();
lista.add("Isabella");
lista.add("Angelina");
lista.add("Pille");
lista.add("Hazem");
listb.add("Isabella");
listb.add("Angelina");
listb.add("Bianca");
// Create an aplusb list which will contain both list (list1 and list2) in which common element will occur twice
List<String> listapluslistb =new ArrayList<String>(lista);
listapluslistb.addAll(listb);
// Create an aunionb set which will contain both list (list1 and list2) in which common element will occur once
Set<String> listaunionlistb =new HashSet<String>(lista);
listaunionlistb.addAll(listb);
for(String s:listaunionlistb)
{
listapluslistb.remove(s);
}
System.out.println(listapluslistb);
public <T> List<T> getIntersectOfCollections(Collection<T> first, Collection<T> second) {
return first.stream()
.filter(second::contains)
.collect(Collectors.toList());
}
// Create two collections:
LinkedList<String> listA = new LinkedList<String>();
ArrayList<String> listB = new ArrayList<String>();
// Add some elements to listA:
listA.add("A");
listA.add("B");
listA.add("C");
listA.add("D");
// Add some elements to listB:
listB.add("A");
listB.add("B");
listB.add("C");
// use
List<String> common = new ArrayList<String>(listA);
// use common.retainAll
common.retainAll(listB);
System.out.println("The common collection is : " + common);
In case you want to do it yourself..
List<Integer> commons = new ArrayList<Integer>();
for (Integer igr : group1) {
if (group2.contains(igr)) {
commons.add(igr);
}
}
System.out.println("Common elements are :: -");
for (Integer igr : commons) {
System.out.println(" "+igr);
}
Some of the answers above are similar but not the same so posting it as a new answer.
Solution:
1. Use HashSet to hold elements which need to be removed
2. Add all elements of list1 to HashSet
3. iterate list2 and remove elements from a HashSet which are present in list2 ==> which are present in both list1 and list2
4. Now iterate over HashSet and remove elements from list1(since we have added all elements of list1 to set), finally, list1 has all common elements
Note: We can add all elements of list2 and in a 3rd iteration, we should remove elements from list2.
Time complexity: O(n)
Space Complexity: O(n)
Code:
import com.sun.tools.javac.util.Assert;
import org.apache.commons.collections4.CollectionUtils;
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
list1.add(4);
list1.add(5);
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(3);
list2.add(5);
list2.add(7);
Set<Integer> toBeRemoveFromList1 = new HashSet<>(list1);
System.out.println("list1:" + list1);
System.out.println("list2:" + list2);
for (Integer n : list2) {
if (toBeRemoveFromList1.contains(n)) {
toBeRemoveFromList1.remove(n);
}
}
System.out.println("toBeRemoveFromList1:" + toBeRemoveFromList1);
for (Integer n : toBeRemoveFromList1) {
list1.remove(n);
}
System.out.println("list1:" + list1);
System.out.println("collectionUtils:" + CollectionUtils.intersection(list1, list2));
Assert.check(CollectionUtils.intersection(list1, list2).containsAll(list1));
output:
list1:[1, 2, 3, 4, 5]
list2:[1, 3, 5, 7]
toBeRemoveFromList1:[2, 4]
list1:[1, 3, 5]
collectionUtils:[1, 3, 5]
public static <T> List<T> getCommonElements(
java.util.Collection<T> a,
java.util.Collection<T> b
) {
if(a==null && b==null) return new ArrayList<>();
if(a!=null && a.size()==0) return new ArrayList<>(b);
if(b!=null && b.size()==0) return new ArrayList<>(a);
Set<T> set= a instanceof HashSet?(HashSet<T>)a:new HashSet<>(a);
return b.stream().filter(set::contains).collect(Collectors.toList());
}
For better time performance, please use HashSet (O(1) look up) instead of List(O(n) look ups)
Time complexity- O(b)
Space Complexity- O(a)
Below code
Remove common elements in the list
List<String> result = list1.stream().filter(item-> !list2.contains(item)).collect(Collectors.toList());
Retrieve common elements
List<String> result = list1.stream()
.distinct()
.filter(list::contains)
.collect(Collectors.toList());
The question talks about three items and many of the suggestions suggest using retainAll. I think it must be stated that as the size of the lists grown retainAll seems to become more inefficient.
In my tests I found that converting to Sets and looping is around 60 times faster than using retainAll for Lists with 1000s of items
List<Integer> common(List<Integer> biggerList, List<Integer> smallerList) {
Set<Integer> set1 = new HashSet<>(biggerList);
List<Integer> result = new ArrayList<>(smallerList.size());
for (Integer i : smallerList) {
if (set1.contains(i)) {
result.add(i);
}
}
return result;
}
Using Java 8 below solution
list1.stream()
.filter(list2::contains)
.collect(Collectors
.toList()));

Categories