I have two arraylists of objects, I want to know which strings are unique to arraylist 1, and which strings are unique to arraylist 2. What I have come up with is the forloop below, which I have to implement twice, reversing the positions of the arraylists. I'm hopeful someone can suggest a more elegant way to do this.
Per request, a bunch more stuff I guess I wrongfully assumed was implied in the code-snippet itself. And the output this produces is:
grape doesn't exist in second arrayList
pineapple doesn't exist in first arrayList
Works great, everything is great, but, per above, I'm hopeful someone with more knowledge of streams/java in general can provide a better solution than just running my stream twice, with the inputs reversed.
import java.util.ArrayList;
public class CompareTwoArrays {
ArrayList<MyCustomObject> firstArrayListOfObjects = new ArrayList<>();
ArrayList<MyCustomObject> secondArrayListOfObjects = new ArrayList<>();
public void superSpecificExampleMethod() {
firstArrayListOfObjects.add(new MyCustomObject(1, 1, "apple"));
firstArrayListOfObjects.add(new MyCustomObject(1, 1, "orange"));
firstArrayListOfObjects.add(new MyCustomObject(1, 1, "banana"));
firstArrayListOfObjects.add(new MyCustomObject(1, 1, "grape"));
secondArrayListOfObjects.add(new MyCustomObject(1, 1, "apple"));
secondArrayListOfObjects.add(new MyCustomObject(1, 1, "pineapple"));
secondArrayListOfObjects.add(new MyCustomObject(1, 1, "orange"));
secondArrayListOfObjects.add(new MyCustomObject(1, 1, "banana"));
for (MyCustomObject object : firstArrayListOfObjects) {
if (!secondArrayListOfObjects.stream().map(MyCustomObject::getString).filter(object.getString()::equals).findFirst().isPresent()) {
System.out.println(object.getString() + " doesn't exist in second arrayList");
}
}
for (MyCustomObject object : secondArrayListOfObjects) {
if (!firstArrayListOfObjects.stream().map(MyCustomObject::getString).filter(object.getString()::equals).findFirst().isPresent()) {
System.out.println(object.getString() + " doesn't exist in first arrayList");
}
}
}
}
class MyCustomObject {
private int randomIntOne;
private int randomIntTwo;
private String string;
public MyCustomObject(int randomIntOne, int randomIntTwo, String string) {
this.randomIntOne = randomIntOne;
this.randomIntTwo = randomIntTwo;
this.string = string;
}
public String getString() {
return string;
}
}
Assuming there are two array lists of objects MyObject containing strings:
List<MyObject> listOne = new ArrayList<>(Arrays.asList(
new MyObject("aaa"), new MyObject("bbb"), new MyObject("ccc"), new MyObject("ddd")
));
List<MyObject> listTwo = new ArrayList<>(Arrays.asList(
new MyObject("fff"), new MyObject("bbb"), new MyObject("ggg"), new MyObject("ddd")
));
To find "unique" objects in listOne that is those which are not available in listTwo there are several ways:
Use List::removeAll providing that the methods equals and hashCode are properly implemented in this class
removeAll should be applied to a copy of listOne
List<MyObject> diffOneMinusTwo = new ArrayList<>(listOne); // copy
diffOneMinusTwo.removeAll(listTwo); // ["aaa", "ccc"]
Use List::removeIf accepting a predicate and using a set of the strings contained in the objects of listTwo:
Set<String> listTwoStrings = listTwo
.stream()
.map(MyObject::getString)
.collect(Collectors.toSet);
List<MyObject> diffOneMinusTwo = new ArrayList<>(listOne); // copy
diffOneMinusTwo.removeIf(x -> listTwoStrings.contains(x.getString()));
Use Stream API filter and collect - no copy is needed here but a temporary set of strings is used
List<MyObject> diffOneMinusTwo = listOne
.stream()
.filter(x -> !listTwoStrings.contains(x.getString()))
.collect(Collectors.toList());
In Java 11 there is static Predicate::not method so the stream version may look like this (if hashCode and equals are implemented properly):
List<MyObject> diffOneMinusTwo = listOne
.stream()
.filter(Predicate.not(listTwo::contains)) // using method reference
.collect(Collectors.toList());
The difference between listTwo and listOne can be created vice versa.
Related
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());
Say that we have a 3-dimensional List of Objects.
class OneDObject {
int id;
List<Integer> list;
OneDObject(int id, List<Integer>list) { // Constructor }
// Getters and Setters
}
class TwoDObject {
int id;
List<OneDObject> list;
TwoDObject(int id, List<OneDObject> list) { // Constructor }
// Getters and Setters
}
var l1 = List.of(1,2,4);
var l2 = List.of(2,4,6);
var obj1d1 = new OneDObject(1, l1);
var obj1d2 = new OneDObject(2, l2);
var l3 = List.of(obj1d1, obj1d2);
var l4 = List.of(obj1d1);
var obj2d1 = new TwoDObject(3, l3);
var obj2d2 = new TwoDObject(4, l4);
var l5 = List.of(obj2d1, obj2d2); // 3-d list
Say that I want to filter "l5" such that if any element in the inner most list is an odd number then the entire list should be deleted, and if that makes the 2nd level list as empty, then that should be deleted in return.
So, for the given example, before filtering if it is:
[[[1,2,4],[2,4,6]], [[1,2,4]]]
After filtering, it should be:
[[[2,4,6]]]
How can I do this using Streams in Java?
Since you need your lists to be updated, in the below solution I am using removeIf method of the List to remove any elements which does not meet the necessary criteria. So for removeIf to work, the list should not be immutable. So replace the var list = List.of(...) code with var list = new ArrayList<>(List.of(...));
(Note: Null checks have been ignored as well.)
Now, this problem could be split into components:
Predicate to identify if a list has any odd elements.
Predicate<OneDObject> hasOdd = obj-> obj.getList().stream().anyMatch(i -> i % 2 != 0);
Predicate to remove objects from 2d list, which has odd elements in its 1d list.
Predicate<TwoDObject> validate2d = obj -> {
// remove any 1d list that has atleast one odd number.
obj.getList().removeIf(hasOdd);
// check if there are any valid 1d lists
return obj.getList().isEmpty();
};
Now apply the predicate to the final list:
l5.removeIf(validate2d); // l5 will now contain only the 2d object having [2,4,6] list
Here's the final code (in Java, but I think it should almost be interchangeable with Kotlin)
List<TwoDObject> l6 = l5.stream()
.peek(twoDObject -> {
List<OneDObject> filteredOneDObjectList = twoDObject.getList()
.stream()
.filter(oneDObject -> oneDObject.getList()
.stream()
.noneMatch(i -> i % 2 == 1))
.toList();
twoDObject.setList(filteredOneDObjectList);
})
.filter(twoDObject -> twoDObject.getList().size() > 0)
.toList();
First we go through every twoDObject by calling Stream#peek, then stream its list and filter out every oneDObject, which contains an odd number. Then the list is saved back into the current twoDObject.
In the end we filter out all empty twoDObjects.
Note that Stream#peek should normally only be used for the purpose of debugging and not mutating the stream elements.
In this case it could also be replaced with
List<TwoDObject> l6 = l5.stream()
.map(twoDObject -> {
...
return twoDObject;
})
...
After reading several questions and examples I came with this example which I modified a bit to make it work as expected.
Collections.sort(listToOrder, Comparator.comparing(item -> someObject.getListOfLongs().indexOf(item.getId())));
So listToOrder is a list of MyObject which has name and id, so I need to order listToOrder in the same order as listOfLongs.
With the example of code given it work as expected however if the listToOrder is different in size it fails, wondering how I could make it to work even if the sizes are different.
Edit:
I misread, the error I was getting was an IndexOutOfBoundsException which wasn't triggered by the line of code I put up there, it was because of a manual log.
List.indexOf() returns -1 if the element is not found, which means such items will be ordered first in the resulting sorted list.
Without ordering data, the only other sensible way to handle such elements is to order them last:
Collections.sort(listToOrder, Comparator.comparing(item -> someObject.getListOfLongs().contains(item.getId()) ? someObject.getListOfLongs().indexOf(item.getId()) : Integer.MAX_VALUE));
This has nothing to do with sorting, but ordering. Having the following object with full-args constructor and getters:
public static class MyObject {
private final long id;
private final String name;
}
... and the following data in a random order ...
List<Integer> ids = Arrays.asList(5,4,7,0,2,1,3,8,6);
List<MyObject> list = Arrays.asList(
new MyObject(1, "one"),
new MyObject(3, "three"),
...
new MyObject(6, "six"),
new MyObject(8, "eight")
);
The solution you are looking for is this:
List<MyObject> newList = new ArrayList<>(list);
for (int i=0; i<ids.size(); i++) {
int id = ids.get(i);
for (MyObject myObject: list) {
if (myObject.getId() == id) {
newList.set(i, myObject);
break;
}
}
}
Simply find the object with the matching ID and set it to a new list. There is no dedicated method to do that.
I've searched through many questions on this site with somewhat similar underlying concepts, however after many hours of attempting to solve this problem myself and reviewing I am still lost. If there is another question that answers this I will be more than happy to give it a look over.
Ultimately I want to create a recursive method such that it takes two lists and returns a Set of String lists:
//Example of such a function definition
private static Set<List<String>> myRecursiveMethod(List<String> listOne,
List<String> listTwo) {
}
When I say "Set of String lists" I mean specifically the following:
(Note:"AD" == "DA")
// if the two following lists are INPUTTED into myRecursiveMethod();
// listOne = ["A","B"]
// listTwo = ["C","D"]
// the following set is OUTPUTTED: [["AC","BD"],["AD","BC"]]
Such that if there were three elements in both listOne and listTwo, there would be SIX elements in the set. i.e:
// listOne = ["A","B","C"]
// listTwo = ["D","E","F"]
// OUTPUTTED: [["AD","BE","CF"],["AD","BF","CE"],["BD","AE","CF"],
// ["BD","AF","CE"],["CD","AE","BF"],["CD","AF","BE"]]
I tried writing this using a double enhanced FOR loop so I could understand the logic. My FOR loop approach is terrible and only works for the HARD-CODED limit of list.size() == 2.
// Create Lists and append elements
List<String> listOne = new ArrayList<String>();
listOne.add("A");
listOne.add("B");
List<String> listTwo = new ArrayList<String>();
listTwo.add("C");
listTwo.add("D");
// List One = ["A","B"]
// List Two = ["C","D"]
// Create new List
List<List<String>> newList = new ArrayList<List<String>>();
Integer counter = 0;
for (String s : listOne) {
counter++;
for (String p : listTwo) {
// A HARD-CODED bad implementation of this method
if (counter < 3) {
List<String> newListTwo = new ArrayList<String>();
newListTwo.add(s.concat(p));
newList.add(newListTwo);
} else if (!(counter % 2 == 0)) {
newList.get(1).add(s.concat(p));
} else {
newList.get(0).add(s.concat(p));
}
}
}
System.out.println(newList); // = [["AC","BD"],["AD","BC"]]
Also you can note that I defined List<List<String>> Rather than Set<List<String>>. This was due to my badly coded attempted which relies on the list.get() method.
So my current recursive method is as follows:
private static Set<List<String>> myRecursiveMethod(List<String> listOne,
List<String> listTwo)
{
//Base Case:
if (listOne.isEmpty){
return new HashSet<List<String>>;
}
//Recursive Case:
else {
String listOneFirst = listOne.get(0);
String listTwoFirst = listTwo.get(0);
List<String> sampleList = new ArrayList<String>();
sampleList.add(listOneFirst+listTwoFirst);
Set<List<String>> newSet = new HashSet<List<String>>(myRecursiveMethod())
newSet.add(sampleList);
return newSet;
}
}
This method only acts like this currently:
INPUT:
List One = ["A","B"]
List Two = ["C","D"]
OUTPUT:
[["AC"]["BD"]]
DESIRED OUTPUT:
[["AC","BD"],["AD","BC"]]
EDIT:
After reviewing responses my W.I.P code for the class:
private static Set<List<String>> myRecursiveMethod(List<String> listOne,
List<String> listTwo) {
//Backup Case (user enters an empty list)
if (listOne.isEmpty()){
return new HashSet<List<String>>();
}
// Base Case:
if (listOne.size() == 1) {
List<String> mergedStrings = new ArrayList<>();
for (String s : listTwo) {
mergedStrings.add(listOne.get(0).concat(s));
}
Set<List<String>> builtHashSet = new HashSet<List<String>();
builtHashSet.add(mergedStrings);
return builtHashSet;
}
// Recursive Case:
else {
// Ensure original list values arn't changed.
List<String> newListOne = new ArrayList<String>(listOne);
List<String> newListTwo = new ArrayList<String>(listTwo);
//first two elements...I don't think this is correct
String listOneFirst = newListOne.get(0);
String listTwoFirst = newListTwo.get(0);
List<String> sampleList = new ArrayList<String>();
sampleList.add(listOneFirst + listTwoFirst);
//used for making recursive case smaller
newListOne.remove(0);
// Calls recursion
Set<List<String>> newSet = new HashSet<List<String>>(
myRecursiveMethod(newListOne, newListTwo));
newSet.add(sampleList);
return newSet;
}
}
I think the problem is here:
if (listOne.isEmpty){
return new HashSet<List<String>>;
}
You are correct, at some point your recursion has to end, and you have to start building the desired output. But the desired output is not a Set with an empty list. It is a Set containing some lists with some content. Thus: don't wait until listOne is empty. Instead:
if (listOne.size() == 1) {
List<String> mergedStrings = new ArrayList<>();
mergedStrings = ... merge the ONE listOne entry with all listTwo entries
Set<List<String>> rv = new HashSet<>();
rv.add(mergedStrings);
return rv;
}
In other words: you use recursion to reduce the length of the first list by one. And when only one element is left in that list, it is time to merge in the second list.
Now lets look into how to "use" that (calling the method rec for brevity); putting down some pseudo code to show the steps we need:
rec([a, b], [c,d]) -->
rec([a], [c,d]) X rec([b], [c, d]) -->
<[ac, ad]> X <[bc, bd]> -->
<[ac, ad], [bc, bd]>
"X" meaning "joining" two results from recursive calls; should be as easy as:
Set<List<String>> rec1 = rec(...);
return rec1.addAll(rec2 ...
I would like to populate ComboBox in Java, but:
when I use array of strings, I must define the size of array before (this is disadvantage),
when I would like to use ArrayList, I cant have empty items with null values or I cant skip ids:
ArrayList<String> a = new ArrayList<String>();
a.add(0, "hahah");
a.add(1, "bleeeee");
a.add(5, "cleeeee"); //this makes an error, when I change index to 2, it works
JComboBox supplierComboBox = new JComboBox(a.toArray());
My array is for example:
[1] => "dog",
[5] => "mouse",
[8] => "cat".
(some ids missing).
THX.
You can't have an index of 5 without having indexes of 2, 3, and 4 as well. Java will either throw an exception at this, or it will silently fill all of the skipped indexes with null values. So just add values at 2, 3, and 4 and it should work. Make sure that there are no other skipped indexes as well.
To remove all the null values in a List, try this code:
public class RemoveNullValues {
private ArrayList<String> test = new ArrayList<String>();
public RemoveNullValues() {
test.add("0");
test.add(null);
test.add("1");
test.add(null);
test.add(null);
test.add("2");
test.add("3");
test.add("4");
test.add(null);
test.add("5");
System.out.println("Before: " + test);
//Java 7 and below method:
test.removeAll(Collections.singleton(null));
//Java 8+ method:
test.removeIf(Objects::isNull);
System.out.println("After: " + test);
}
public static void main(String[] args) {
new RemoveNullValues();
}
}
You can instantiate a combobox without children
JComboBox supplierComboBox = new JComboBox();
and then add the children in a for cycle:
ArrayList<String> a = new ArrayList<String>();
a.add("hahah");
a.add("bleeeee");
a.add("cleeeee");
for (String value : a) {
supplierComboBox.addItem(value); // you can put every kind of object in "addItem"
}
Some examples (if you need the id field):
Using Map.Entry
ArrayList<Map.Entry<Integer, String>> a = new ArrayList<Map.Entry<Integer, String>>();
a.add(new AbstractMap.SimpleEntry<Integer, String>(0, "hahah"));
a.add(new AbstractMap.SimpleEntry<Integer, String>(1, "bleeeee"));
a.add(new AbstractMap.SimpleEntry<Integer, String>(5, "cleeeee"));
for (Map.Entry<Integer, String> value : a) {
supplierComboBox.addItem(value);
}
Using MyClass:
public class MyClass{
private Integer id;
private String value;
public MyClass(Integer id, String value) {
this.id = id;
this.value= value;
}
// Getters & Setters
}
and then:
ArrayList<MyClass> a = new ArrayList<MyClass>();
a.add(new MyClass(0, "hahah"));
a.add(new MyClass(1, "bleeeee"));
a.add(new MyClass(5, "cleeeee"));
for (MyClass value : a) {
supplierComboBox.addItem(value);
}