Union of N lists in java - java

What is the best way to create a union of N lists in java ?
For eg
List<Integer> LIST_1 = Lists.newArrayList(1);
List<Integer> LIST_2 = Lists.newArrayList(2);
List<Integer> LIST_3 = Lists.newArrayList(3);
List<Integer> LIST_4 = Lists.newArrayList(4);
List<Integer> LIST_1_2_3_4 = Lists.newArrayList(1,2,3,4);
assert LIST_1_2_3_4.equals(union(LIST_1,LIST_2,LIST_3,LIST_4));
The union method will take a var args parameter
<Item> List<Item> union(List<Item> ... itemLists)
Is there a library which provides this method.Simplest way is to loop through the array and accumulate each list into one

There may be a library, but including it only for these 3 lines of code would probably not worth another dependency...
private static <Item> List<Item> union(List<Item> ... itemLists)
{
List<Item> result = new ArrayList<Item>();
for (List<Item> list : itemLists) result.addAll(list);
return result;
}

You could use Google Guava:
List<Integer> joined = new ArrayList<>( Iterables.concat(LIST_1, LIST_2, LIST_3, LIST_4) );
or for comparison only:
Iterables.elementsEqual( LIST_1_2_3_4, Iterables.concat(LIST_1, LIST_2, LIST_3, LIST_4) );

I am not sure what you mean by best solution but a simple solution would involve using the addAll method.
For extra performance you may also hint the size by summing all sizes.
new ArrayList<...>(totalSizeHere)
Also see this answer: How to do union, intersect, difference and reverse data in java

Related

Java Stream : Create new List from 2 Lists keeping only matching values

I'm working with java streams and I have an issue.
I have a List like:
[1,2,3,4,5]
and another like:
[1,3,5,7,9]
My question is how can I create a new list like:
[1,3,5]
Thanks.
There is a much simpler way than using a stream here:
List<Integer> newList = new ArrayList<>(list1);
newList.retainAll(list2);
However, as pointed out by #Holger, if the lists are large, this solution can be inefficient, so instead try:
newList.retainAll(new HashSet<>(list2));
You have a stream answer in the comments.
You can also utilize the retainAll method to achieve this.
ArrayList<Integer> newArr = new ArrayList<>(arr); //Create a new List based off the first list
newArr.retainAll(arr2); //Retain only the elements in the first list and second list
In this example newArr would be [1,3,5]
If you have lists
List l1 = ..., List l2 = ...
You can do:
List result = l1.stream().filter(x -> l2.contains(x)).collect(Collectors.toList());

Make ArrayList<LinkedList<String>> into an Iterable

I have an ArrayList<LinkedList> and I want to make one Iterator for all the LinkedLists.
What is the best way to do this?
I found this:
'final Iterable<Integer> all =
Iterables.unmodifiableIterable(
Iterables.concat(first, second, third));'
But I have many LinkedLists. What can I do to combine all of them?
Thanks!
It's not clear whether you want to do this using only standard Java classes, but if so, you can do it this way as of Java 8:
ArrayList<LinkedList<String>> strings = /* ... */;
Iterator<String> i = strings.stream().flatMap(l -> l.stream()).iterator();
One simple solution
ArrayList<LinkedList<String>> l1 = ...
List<String> l2 = new ArrayList<>();
for(List<String> e : l1) {
l2.addAll(e);
}

How to make a new list with a property of an object which is in another list

Imagine that I have a list of certain objects:
List<Student>
And I need to generate another list including the ids of Students in the above list:
List<Integer>
Avoiding using a loop, is it possible to achieve this by using apache collections or guava?
Which methods should be useful for my case?
Java 8 way of doing it:-
List<Integer> idList = students.stream().map(Student::getId).collect(Collectors.toList());
With Guava you can use Function like -
private enum StudentToId implements Function<Student, Integer> {
INSTANCE;
#Override
public Integer apply(Student input) {
return input.getId();
}
}
and you can use this function to convert List of students to ids like -
Lists.transform(studentList, StudentToId.INSTANCE);
Surely it will loop in order to extract all ids, but remember guava methods returns view and Function will only be applied when you try to iterate over the List<Integer>
If you don't iterate, it will never apply the loop.
Note: Remember this is the view and if you want to iterate multiple times it will be better to copy the content in some other List<Integer> like
ImmutableList.copyOf(Iterables.transform(students, StudentToId.INSTANCE));
Thanks to Premraj for the alternative cool option, upvoted.
I have used apache CollectionUtils and BeanUtils. Accordingly, I am satisfied with performance of the following code:
List<Long> idList = (List<Long>) CollectionUtils.collect(objectList,
new BeanToPropertyValueTransformer("id"));
It is worth mentioning that, I will compare the performance of guava (Premraj provided) and collectionUtils I used above, and decide the faster one.
Java 8 lambda expression solution:
List<Integer> iDList = students.stream().map((student) -> student.getId()).collect(Collectors.toList());
If someone get here after a few years:
List<String> stringProperty = (List<String>) CollectionUtils.collect(listOfBeans, TransformerUtils.invokerTransformer("getProperty"));
You can use Eclipse Collections for this purpose
Student first = new Student(1);
Student second = new Student(2);
Student third = new Student(3);
MutableList<Student> list = Lists.mutable.of(first, second, third);
List<Integer> result = list.collect(Student::getId);
System.out.println(result); // [1, 2, 3]
The accepted answer can be written in a further shorter form in JDK 16 which includes a toList() method directly on Stream instances.
Java 16 solution
List<Integer> idList = students.stream().map(Student::getId).toList();
It is Mathematically impossible to do this without a loop. In order to create a mapping, F, of a discrete set of values to another discrete set of values, F must operate on each element in the originating set. (A loop is required to do this, basically.)
That being said:
Why do you need a new list? You could be approaching whatever problem you are solving in the wrong way.
If you have a list of Student, then you are only a step or two away, when iterating through this list, from iterating over the I.D. numbers of the students.
for(Student s : list)
{
int current_id = s.getID();
// Do something with current_id
}
If you have a different sort of problem, then comment/update the question and we'll try to help you.

Is there a better way to combine two string sets in java?

I need to combine two string sets while filtering out redundant information, this is the solution I came up with, is there a better way that anyone can suggest? Perhaps something built in that I overlooked? Didn't have any luck with google.
Set<String> oldStringSet = getOldStringSet();
Set<String> newStringSet = getNewStringSet();
for(String currentString : oldStringSet)
{
if (!newStringSet.contains(currentString))
{
newStringSet.add(currentString);
}
}
Since a Set does not contain duplicate entries, you can therefore combine the two by:
newStringSet.addAll(oldStringSet);
It does not matter if you add things twice, the set will only contain the element once... e.g it's no need to check using contains method.
You can do it using this one-liner
Set<String> combined = Stream.concat(newStringSet.stream(), oldStringSet.stream())
.collect(Collectors.toSet());
With a static import it looks even nicer
Set<String> combined = concat(newStringSet.stream(), oldStringSet.stream())
.collect(toSet());
Another way is to use flatMap method:
Set<String> combined = Stream.of(newStringSet, oldStringSet).flatMap(Set::stream)
.collect(toSet());
Also any collection could easily be combined with a single element
Set<String> combined = concat(newStringSet.stream(), Stream.of(singleValue))
.collect(toSet());
The same with Guava:
Set<String> combinedSet = Sets.union(oldStringSet, newStringSet)
From the definition Set contain only unique elements.
Set<String> distinct = new HashSet<String>();
distinct.addAll(oldStringSet);
distinct.addAll(newStringSet);
To enhance your code you may create a generic method for that
public static <T> Set<T> distinct(Collection<T>... lists) {
Set<T> distinct = new HashSet<T>();
for(Collection<T> list : lists) {
distinct.addAll(list);
}
return distinct;
}
If you are using Guava you can also use a builder to get more flexibility:
ImmutableSet.<String>builder().addAll(someSet)
.addAll(anotherSet)
.add("A single string")
.build();
If you are using the Apache Common, use SetUtils class from org.apache.commons.collections4.SetUtils;
SetUtils.union(setA, setB);
Just use newStringSet.addAll(oldStringSet). No need to check for duplicates as the Set implementation does this already.
http://docs.oracle.com/javase/7/docs/api/java/util/Set.html#addAll(java.util.Collection)
Since sets can't have duplicates, just adding all the elements of one to the other generates the correct union of the two.
newStringSet.addAll(oldStringSet);
This will produce Union of s1 and s2
If you care about performance, and if you don't need to keep your two sets and one of them can be huge, I would suggest to check which set is the largest and add the elements from the smallest.
Set<String> newStringSet = getNewStringSet();
Set<String> oldStringSet = getOldStringSet();
Set<String> myResult;
if(oldStringSet.size() > newStringSet.size()){
oldStringSet.addAll(newStringSet);
myResult = oldStringSet;
} else{
newStringSet.addAll(oldStringSet);
myResult = newStringSet;
}
In this way, if your new set has 10 elements and your old set has 100 000, you only do 10 operations instead of 100 000.
Set.addAll()
Adds all of the elements in the specified collection to this set if they're not already present (optional operation). If the specified collection is also a set, the addAll operation effectively modifies this set so that its value is the union of the two sets
newStringSet.addAll(oldStringSet)

Java Get Multiple Items From Collection

Do Java collections have a built-in method to return multiple items from that collection? For example, the list below has n elements, some of which are duplicated in the list. How could I get all elements where the value = "one"? I realize it would be very easy to write my own method to achieve such functionality, I just wanted to make sure I am not missing a built in method to do this.
List<String> ls=new ArrayList<String>();
ls.add("one");
ls.add("two");
ls.add("three");
ls.add("one");
ls.add("one");
//some type of built in function????
//ls.getItems("one");
//should return elements 0,3,4
Thanks
Google Collections have Predicates for this purpose.
In this example, it's enough to know the number of times "one" appears in the list, which you can get with java.util.Collections.frequency(ls, "one").
You could also have been using a Multiset from google-collections, and called m.count("one"), which would be much more efficient.
There isn't a built-in method, but Apache Commons has a select method in CollectionUtils that will get all the elements that match some criterion. Example usage:
List<String> l = new ArrayList<String>();
// add some elements...
// Get all the strings that start with the letter "e".
Collection beginsWithE = CollectionUtils.select(l, new Predicate() {
public boolean evaluate(Object o) {
return ((String) o).toLowerCase().startsWith("e");
}
);
I think you could do the trick of retaining the elements of this list which are in another list with the retainall method of Collection class link text. In the other list you can add only the "one" object.
List<String> ls=new ArrayList<String>();
ls.add("one");
ls.add("two");
ls.add("three");
ls.add("one");
ls.add("one");
List<String> listToCompare = new ArrayList<String>();
listToCompare.add("one");
ls.retainAll(listToCompare);

Categories

Resources