I have several very large ArrayLists of objects which i would like to find their Symmetric Differences ( or disjunction). To do so i have decided to use Sets and their "contain()" methods. However, this method uses the equals() method to evaluate said objects.
Problem is, i cannot make any changes in my class. So, i cannot override any method. (my code is just a small part of a very bigger project)
so this leaves me here, is there any other alternative to altering the classes themselves ? or any other way that would not require me to make any changes to my classes ?
I've recently found out about this so I have an alternate solution (only for Java 8):
// Being T the class of the objects in the list
ArrayList<T> list1 = ...;
ArrayList<T> list2 = ...;
// A function to compare two elements
BiFunction<T, T, Boolean> funcEquals = (a,b) -> yourEquals(a,b);
// A function that given a List returns a predicate that states if an element is on that list
Function<List<T>, Predicate<T>> notIn = (s) -> (e) -> s.stream().filter((y) -> funcEquals.apply(e, y)).count() == 0;
// Get the elements in list1 that are not in list2
Stream<String> list1Filtered = list1.stream().filter(notIn.apply(list2));
// Get the elements in list2 that are not in list1
Stream<String> list2Filtered = list2.stream().filter(notIn.apply(list1));
/*
If you have more than two lists, comparisons can be concatenated:
Stream<String> list1Filtered = list1.stream().filter(notIn.apply(list2)).filter(notIn.apply(list3));
Stream<String> list2Filtered = list2.stream().filter(notIn.apply(list1)).filter(notIn.apply(list3));
Stream<String> list3Filtered = list3.stream().filter(notIn.apply(list1)).filter(notIn.apply(list2));
*/
// Add them all together
ArrayList<T> result = new ArrayList<T>();
result.addAll(list1Filtered.collect(Collectors.toList()));
result.addAll(list2Filtered.collect(Collectors.toList()));
It's a little confusing at first, but you don't have to create any more classes.
I ended up using a wrapper class, originally suggested by "Oliver Charlesworth" and other people in the comments.
Related
I need an immutable list where I can get derive a second immutable list preserving all elements of the previous list plus an additional element in Java (without additional libraries).
Note: This question is similar to What is an efficient and elegant way to add a single element to an immutable set? but I need a list and don't have Guava.
What I have tried so far:
var list = List.of(someArrayOfInitialElements);
var newList = Stream.concat(list.stream(), Stream.of(elementToAppend))
.collect(CollectorsCollectors.toUnmodifiableList());
That would work but creating a stream and copying elements one by one seems inefficient to me. You could basically bulk copy memory given that List.of() stores data in a field-based or array-based data structure.
Is there a more efficient solution than using streams? A better data structure in the Java standard library that I am missing?
I would create a new ArrayList append the element and then return that as an unmodifiable list. Something like,
private static <T> List<T> appendOne(List<T> al, T t) {
List<T> bl = new ArrayList<>(al);
bl.add(t);
return Collections.unmodifiableList(bl);
}
And to test it
public static void main(String[] args) {
List<String> al = appendOne(new ArrayList<>(), "1");
List<String> bl = appendOne(al, "2");
System.out.println(bl);
}
I get (unsurprisingly):
[1, 2]
See this code run at IdeOne.com.
The Answer by Frisch is correct, and should be accepted. One further noteā¦
Calling Collections.unmodifiableList produces a collection that is a view onto the original mutable list. So a modification to the original list will "bleed through" to the not-so-immutable second list.
This issue does not apply to the correct code shown in that Answer, because the new ArrayList object deliberately goes out-of-scope. Therefore that new list cannot be accessed for modification. But in other coding scenarios, this issue could be a concern.
List.copyOf
If you want an independent and truly immutable second list, use List.copyOf in Java 10+. This returns an unmodifiable list.
return List.copyOf( bl ) ;
Both answers are great, I would create a bit more generic solution:
private static <T> List<T> append(final List<T> al, final T... ts) {
final List<T> bl = new ArrayList<>(al);
for (final T t : ts) {
bl.add(t);
}
return List.copyOf(bl);
}
It can be used exactly like previous answer:
List<String> al = append(new ArrayList<>(), "1");
List<String> bl = append(al, "2");
System.out.println(bl);
But also slightly more efficient:
List<String> bl = append(new ArrayList<>(), "1", "2");
System.out.println(bl);
After reading the Oracle docs I did not found what I am looking for. I have an object that it can be compared in different ways, but one of its attributes is a List. How can I compare based on the size of the that List attribute?
Comparator comparator = Comparator.comparing(Product::getCommentList::size);
List<Product> soldL = new LinkedList();
soldL.addAll(sold);
Collections.sort(soldL,comparator);
I tried the code above without success.
You can simply use lambda and improve your existing code as :
Comparator<Product> comparator = Comparator.comparing(p -> p.getCommentList().size()); // type 'Product' bound
List<Product> soldL = new LinkedList<>(); // type inferred '<>'
soldL.addAll(sold);
soldL.sort(comparator); // use 'List.sort'
Edit: You can make use of the comparingInt instead of comparaing to avoid boxing as:
Comparator<Product> comparator = Comparator.comparingInt(p -> p.getCommentList().size());
You can simplify the comparison by implementing a method on Product that returns the size of the list (ex: public int getCommentListSize() { return commentList.size(); }).
So you can create a comparator this way:
Comparator comparator = Comparator.comparing(Product::getCommentListSize);
After that, you pass comparator to the sort method.
List<Object1> list1 = userVo.getMstCardTypeVOs();
Object1 object1 = new Object1();
object1.setId(1);
object1.setName("Test");
-- More fields which are not matched with object2
list1.add(object1);
object1 = new Object1();
object1.setId(2);
object1.setName("Test1");
-- More fields which are not matched with object1
list1.add(object1)
List<Object2> list2 = mapMenuProgramRepo.findAll();
Object2 object2 = new Object2();
object2.setId(1);
object2.setName("Test");
list1.add(object2);
object2 = new Object2();
object2.setId(2);
object2.setName("Test1");
list1.add(object2);
What I need to check same id exist in list1 with reference from list2?
I have used below code:
for (Object1 object1 : obj1) {
for (Object2 obj2: obj2) {
if (object2.getId().equals(object1.getId())) {
// removed entry from the list1
// We can removed by using iterator instead of list
}
}
}
What are the better way and optimized way using jdk8 or 7?
// removed entry from the list1
If you need to remove from one of lists than you can use following code
list1.removeIf(val1 -> list2.stream()
.filter(val2 -> val2.getId().equals(val1.getId()).findFirst().orElse(null) != null)
Although using stream like other answers looks cool, imho it may not be optimal, especially when there is a lot of values in list2.
Instead of using stream to verify if a value exists in list2, which gives complexity of O(M*N) (M,N for number of values in list1 and list2), I would recommend doing things like:
Set<> excludeIds = list2.stream().map(Object2::getId).collect(Collectors.toSet());
list1.removeIf(v -> excludeIds.contains(v.getId()));
Easier to understand, and run faster.
With a HashSet, the complexity of the logic reduce to O(M+N)
You can do this easily with JDK8 streams as shown below by using noneMatch() method:
list1.removeIf(value1 ->
list2.stream().anyMatch(value2 ->
(value2.getId().equals(value1.getId))));//equal ids then remove it from list1
If you have to solve the problem without using streams, then you need to use Iterator (look here for example on remove) and then loop through the list1 and list2 and remove the matched elements from list1
Also, I suggest never create any class names (even example classes) with Object1 or Object2, etc.. as it is very confusing for other developers(i.e., it is not easy to understand the code with class names as Object1, Object2).
I have a list of object :
List<Object[]> list = new ArrayList<>();
Object[] object = {"test", "test1", "test2"};
list.add(object);
List contains some data.
I have another string String str = "test";
I am using below code. What are best other ways:
for (Object []object1 : list) {
for (Object obj : object1) {
if (obj.equals("test")) {
System.out.println("true");
}
}
}
How to check this string present in above list with minimum of code.
Java 8 introduced Streams which are powerful, yet code-compact as you demanded. This answer uses more features of Java 8 sucha as Lambdas and Method References.
Here is a one-liner instruction:
boolean containsObject = list.stream().flatMap(Arrays::stream).filter(s->str.equals(s) ).findFirst().isPresent();
Here how it works:
boolean containsObject = list.stream() // Turning the List into a Stream of Arrays
.flatMap(Arrays::stream) // flattening the 2D structure into a single-dimensional stream of Objects (Note: using a Method reference)
.filter(s->str.equals(s)) // Filtering the flat stream to check for equality (Note: using a Lambda expression)
.findFirst() // Demands to find the first Occurence that passed the Filter test
.isPresent(); // Collapse the stream and returns the result of the above demand (Note: the Stream makes no computation until this instruction)
This solution is code-compact, and brings the nice features of Streams such as parallelization and laziness.
If you convert the Object[]s to lists, then you can call their contains(Object). You could either have list be a List<List<Object>>, or you could leave it with Object[] and wrap the Object[]s in a List as-needed.
Example of the "convert as needed":
for(Object[] object1 : list)
if(Arrays.asList(object1).contains("test"))
System.out.println("true");
Personally, I would have list be a List<List>. Whenever you add to it, just wrap your arrays in a list. Assuming arr is an Object[], that means list.add(Arrays.asList(arr));.
Alexander's answer is also correct (I think; I didn't examine it too closely), but I find long strings of stream operators to be less readable. If you disagree with my opinion on that, then use the stream operators.
In java suppose I have 2 lists
List<Object1> list1
List<Object2> list2
object1.getName(); returns a String
object2.getName(); return a String
is there any way to compare the names and get a difference of the two list
those 2 objects are defined in the 3rd party library, and I can't override the equals and compareto methods
I am in favour of googles Guava or commons collections library
but the Sets.symmetricDifference(Set1, Set2) ask for 2 to be passed in,
even i juse Sets.newHashSet(lis1) and Sets.newHashSet(lis2) to create two sets
but still they have difference type of objects in the sets.
or in commons CollectionUtils.disjunction(lis1, list2) the lists still has to contain the same object type
without doing 2 expensive for loops, is there any other way?
First, we'll build two maps, one for each list, mapping names to objects. Then we iterate over the differences between the key sets, processing whichever kind of object had that name. The maps let us avoid scanning through the list looking for the object with that name. (In using Map rather than Multimap, I'm relying on the asker's comment on another answer that within each list, names are unique. If you're still using Java 7, replace the method reference with a Function implementation.)
Map<String, Object1> map1 = Maps.uniqueIndex(list1, Object1::getName);
Map<String, Object2> map2 = Maps.uniqueIndex(list2, Object1::getName);
for (String name : Sets.difference(map1.keySet(), map2.keySet()))
processObject1(map1.get(name));
for (String name : Sets.difference(map2.keySet(), map1.keySet()))
processObject2(map2.get(name));
If all you want to do is build lists or sets of the objects in exactly one list, processObject1 and processObject2 can just add the objects to collections.
uniqueIndex's iteration order is that of the input iterable, and difference returns a SetView with the same iteration order as its first argument, so you can process objects in the order they appeared in the input lists, if that order is relevant to your problem.
Java 8 streams provide basically the same functionality:
Map<String, Object1> map1 = list1.stream().collect(Collectors.toMap(Function.identity(), Object1::getName));
Map<String, Object2> map2 = list2.stream().collect(Collectors.toMap(Function.identity(), Object2::getName));
map1.keySet().stream().filter(n -> !map2.keySet().contains(n)).map(map1::get).forEachOrdered(o1 -> processObject1(o1));
map2.keySet().stream().filter(n -> !map1.keySet().contains(n)).map(map2::get).forEachOrdered(o2 -> processObject1(o2));
Again, you can replace the forEachOrdered call with collect(Collectors.toList()) if you just want to collect the objects.
First you will have to transfor your lists to String based lists:
private static final class FromObject1ToName implements Function<Object1, String> {
#Override
public String apply(Object1 input) {
return input.name;
}
}
The same transformation has to be done for Object2
Then transform the input list:
Collection<String> transformed = Collections2.transform(list1, new FromObject1ToName());
//list1 is a List on Object1
Then create the multiset:
Multiset<String> multiset1 = HashMultiset.create();
multiset1.addAll(transformed);
Then simply do :
Multisets.difference(multiset1, multiset2) // multiset1 is from Object1 and multiset2 is from Object2
This will give you the difference and how many times it differes
If you need to know just the differences, then do the same transform, then load the Collection of strings in a Set adn then do Sets.symmetricDifference
Using Guava, try this. It works for me ->
Multisets.difference(multiset1,multiset2);
How to convert ArrayList to Multiset.
List x = new ArrayList();
x.add(3);.....
Multiset newX = HashMultiset.create();
newX.addAll(x);