How to compare two arraylist containing objects with shared properties.
Ex: I have a pojo class Abc
Class Abc {
String dataString ;
int rowNum;
......
}
Abc list1 contains - 2000 records & more sometimes
Abc list2 contains - 60 records & more sometimes
I want to compare list1 dataString to list2 datastring and return rownum
if list1.dataString Notequals list2.dataString
I need rowNumber from List1 if data string DOESN'T matches. List2 Rownum can be ignored.
In high-level terms, your code should:
iterate over list1
for each element check whether its data string appears somewhere in list2
if so, include the row number of the list1 element in the result
To make the code fast, in the second step the set of list2 data strings can be precomputed. Still in high-level terms, the code is:
List<int> filtered(List<Abc> list1, List<Abc> list2) {
var dataStrings = setOf(list2.map(x -> x.dataString));
var abcs = list1.filter(x -> dataStrings.contains(x.dataString));
return abcs.map(x -> x.rowNum);
}
In standard Java, the code looks more bloated, of course:
List<int> filtered(List<Abc> list1, List<Abc> list2) {
Set<String> dataStrings = list2.stream()
.map(x -> x.dataString))
.collect(Collectors.toSet());
return list1.stream()
.filter(x -> dataStrings.contains(x.dataString))
.map(x -> x.rowNum)
.collect(Collectors.toList());
}
If in this case, you have two different lists and wants to have comparison, one of the simple way to do that can be two foreach loops with equality condition and then having the value:
for(Abc list1Obj : list1)
{
for(Abc list2Obj : list2)
{
if(list1.dataString.Equals(list2.dataString))
{
int value = Integer.parseInt(list1.rowNumber);
}
}
}
In this case, it would for each item in list1 with each item in list2.
Related
I have this scenario where I have to sort listA based on the order of elements in listB.
But the problem I am facing is that if there is that if listA contains an element which is not defined in the listB then the sort result has all contents of listA sorted as per order in listB but the element which is not defined in listB as the first element.
Example
Input:
listA = [us,au,in,gb]
listB = [au,us,in] // sort order list
Current Output:
listA = [gb,au,us,in] // result after sorting
Expected Output:
listA = [au,us,in,gb] // result after sorting
Here, since "gb" is not present in the sort list listB, the result has "gb" as the first element, but I want that to be the last element.
I am using the below code to sort the listA:
listA.sort(Comparator.comparingInt(listB::indexOf));
It would be performance wise to generate a HashMap associating the string elements with the corresponding indices (i.e. Map<String,Integer>), instead of relaying on List.indexOf() method which performs iteration under the hood. And then define a Comparator based on this Map.
In order to place the elements that not are not present in the listB at the end of the sorted list, we can make use of the method Map.getOrDefault() providing the size of the map as a default value.
List<String> listA = new ArrayList<>(List.of("us","au","in","gb"));
List<String> listB = new ArrayList<>(List.of("au","us","in"));
Map<String, Integer> order = IntStream.range(0, listB.size())
.boxed()
.collect(Collectors.toMap(
listB::get,
Function.identity()
));
Comparator<String> comparator = Comparator.comparingInt(s -> order.getOrDefault(s, order.size()));
listA.sort(comparator);
System.out.println(listA);
Output:
[au, us, in, gb]
In case in you want to preserve the initial order of the elements in listA that are not present in the listB (i.e. group them at the very end of the list according to their initial ordering), you can generate an additional Map. This time based on the listA, which associate each element like gb with a unique Value greater or equal to the size of listB:
List<String> listA = new ArrayList<>(List.of("fr","us","nl","au","in","gb"));
List<String> listB = new ArrayList<>(List.of("au","us","in"));
Map<String, Integer> order = IntStream.range(0, listB.size())
.boxed()
.collect(Collectors.toMap(
listB::get,
Function.identity()
));
Map<String, Integer> resolver = IntStream.range(0, listA.size())
.filter(i -> !order.containsKey(listA.get(i))) // to reduce the size of the Map
.boxed()
.collect(Collectors.toMap(
listA::get,
i -> i + order.size()
));
Comparator<String> comparator = Comparator.comparingInt(
s -> order.getOrDefault(s, resolver.get(s))
);
listA.sort(comparator);
Output:
[au, us, in, fr, nl, gb] // the order of "fr","nl","gb" is preserved
just a quick hack but,
public class ExList extends ArrayList<String> {
public int indexOf(String s) {
int returnValue = super.indexOf(s);
if ( returnValue == -1 ) {
return size();
}
return returnValue;
}
}
and create listB as instance of ExList, it would give you a desired result.
I've 2 lists of String
A = {"apple", "mango", "pineapple", "banana", ... }
B = {"app", "framework",...}
What I'm looking for is this: is any element of B at least a partial match ( substring/contains/startsWith ) with any element of A. e.g., 1st element of B 'app' matches partially with at least one element 'apple'.
Other closely matching topics on StackOverflow don't consider 2 lists.
Is there any elegant way to express a solution using Java lambda?
I feel it's a general problem in Search domain. So, if there is any helping or interesting read on this topic, I'd be glad to receive pointers.
Depends on what you mean by elegant, but try this:
List<String> r = list1
.parallelStream()
.filter( w1->{
return list2
.parallelStream()
.anyMatch( w2->w1.contains(w2) );
}
)
.collect(Collectors.toList());
anyMatch (and filter) can be short circuited and will abort the stream of the second list after finding the first match of w1.contains(w2) and return true if found, giving some efficiency. Use this to filter the first stream. Do it in parallel streams.
You could chain streams of the two List<String> and filter with String.contains() or any other condition you want to use (substring(), startsWith()) .
Then you could map couple of String that valid the condition into a String array :
List<String> listOne = Arrays.asList("apple", "mango", "pineapple", "framework");
List<String> listTwo = Arrays.asList("app", "frame");
List<String[]> values = listOne.stream()
.flatMap(x -> listTwo.stream()
.filter(y -> x.contains(y))
.map(y -> {
return new String[] { x, y };
}))
.collect(Collectors.toList());
for (String[] array : values) {
System.out.println(Arrays.toString(array));
}
Output :
[apple, app]
[pineapple, app]
[framework, frame]
The Problem
I am trying to write a method that recursively generates, and returns, a set of all possible maps between elements from one given list to another.
For example, say I input an arraylist of integers and an arraylist of strings.
List<Integer> integers = new ArrayList<>();
integers.add(1); integers.add(2);
List<String> strings = new ArrayList<>();
strings.add("a"); strings.add("b"); strings.add("c");
combinations(integers, strings);
This should return the set containing all the following maps from integers to strings.
{
(1 -> "a", 2 -> "b"),
(1 -> "a", 2 -> "c"),
(1 -> "b", 2 -> "a"),
(1 -> "b", 2 -> "c"),
(1 -> "c", 2 -> "a"),
(1 -> "c", 2 -> "b")
}
Here, each string (value) can only be mapped to one integer (key). In other words, no two integers can be mapped to the same string.
Disclaimer and Specification
Full discretion: this is a simplified version of the specification of one of the methods for a homework assignment -- and thus the method signature should look like this
Set<Map<Integer, String>> combinations(List<Integer> integers, List<String> strings);
and it must be written using recursion. Here it is assumed that the size of integers is smaller than or equal to the size of strings. I will share what I have so far and explain why it isn't working and I need help.
My Attempt (So far!)
public static Set<Map<Integer, String>> combinations(
List<Integer> integers, List<String> strings) {
// Return set: The set of all possible mappings from integers -> strings
Set<Map<Integer, String>> result = new HashSet<>();
/* Base case: integers is empty => return the empty map.
NOTE: It is assumed that there are fewer integers than strings. */
if (integers.isEmpty()) {
result.add(new HashMap<>());
return result;
}
/* Recursive case: integers is non-empty =>
* get the first integer (call it "first") in integers and map it to any
* string (call it string) in strings.
* Recursively calculate all the mappings from the "rest of integers"
* (integers without "first") to the "rest of strings" (strings without
* "string").*/
Integer first = integers.get(0);
for (String string: strings) {
Map<Integer, String> thisMap = new HashMap<>();
thisMap.put(first, string);
integers.remove(first);
strings.remove(string);
result = combinations(integers, strings);
result.add(thisMap);
}
return result;
}
Here the output is just
{
(),
(1 -> "a", 2 -> "b")
}
which is just a set containing the empty map and the first possible map.
If someone could point me in the right direction, it would be greatly appreciated!
Rather than just providing the code, let me give pseudo code to give you an idea of how to do it. Then I suggest you try again and come back if you have any questions:
combinations(keys, values):
create result set
for each key in keys
for each value in values
combos = combinations(keys without key, values without value)
for each combo
add map from key -> value to combo
add combo to result set
return result set
Update
Now that OP has accepted this answer, here's working code to demonstrate the algorithm:
Set<Map<Integer, String>> combinations(List<Integer> keys, List<String> values) {
if (keys.isEmpty())
return Collections.singleton(new HashMap<>());
else
return keys.stream().flatMap(k ->
values.stream().flatMap(v ->
combinations(listWithout(keys, k), listWithout(values, v)).stream()
.peek(c -> c.put(k, v)))).collect(toSet());
}
private <T> List<T> listWithout (List<T> input, T value) {
return input.stream().filter(v -> !v.equals(value)).collect(toList());
}
I have two scenarios with below two domain objects :
class A{
String id;
String name;
String value;
String val1;
String val2;
}
class PropVal{
String id;
String propVal1;
String propVal2;
String propVal3;
String propVal4;
}
1) I have 4 lists
List<String> 1 = { id1,id2 ,id3}
List<String> 2 = { "one","two","three"}
Note- List 1 elements correspond to List 2 elements like id1 = "one" , id2 = "two" and so on
List<String> 3 = { "one","two","three"}
List<String> 4 = { 1,2,3}
Note- List 3 elements correspond to List 4 elements like "one" = 1 , "two" = 2 and so on
All values of these list correspond to properties of class A so more lists like above with all properties so may be cannot make map of just two above lists.
What i want is to merge these lists based on common field,ie, name ( values is List 2 & list 3) like
List<A> onjList = {[id=id1,name=one,value=1] , [id=id2,name=two,value=2] ,[id=id3,name=three,value=3]..... }
2) I have two lists
List<A> onjList - {[id=id1,name=one,value=1] , [id=id2,name=two,value=2] ,[id=id3,name=three,value=3]..... } -----obtained from Step 1
List<PropVal> list 2 = { [id=id1,propVal1=w,propVal2=x,propVal3=y,propVal4=z] , [id=id2,propVal1=a,propVal2=b,propVal3=c,propVal4=d] ....}
I want a final list like
List<A> final List = {[id=id1,name=one,value=1,val1=w ,val2=x] , [id=id2,name= two,value = 2,val1 = a ,val2 = b]..... }
note val1 = propVal1 and val2 = propVal2.
What is the best way to do both of these scenarios ? Preferably using java 8 streams and lambdas ?
Your examples are misleading. Numbers don’t make good variable names and all four lists are in the same order, but I assume, your question is supposed to imply that the first two list may have a different order than the other two, e.g.
List<String> aNames=Arrays.asList("one", "two", "three");
List<String> aIDs =Arrays.asList("id1", "id2", "id3");
List<String> bNames =Arrays.asList("two", "one", "three");
List<String> bValues=Arrays.asList("2", "1", "3");
While merging all lists in one step is possible, the repeated linear search would yield an overall quadratic time complexity, so this is discouraged. Instead, merge two associated lists into a map, allowing efficient lookup, then, merge the other two with the map:
assert bNames.size()==bValues.size();
Map<String,String> bNameToValue = IntStream.range(0, bNames.size())
.collect(HashMap::new, (m,i) -> m.put(bNames.get(i),bValues.get(i)), Map::putAll);
assert aNames.size()==aIDs.size();
List<A> list = IntStream.range(0, aNames.size())
.mapToObj(i -> new A(aIDs.get(i), aNames.get(i), bNameToValue.get(aNames.get(i))))
.collect(Collectors.toList());
The considerations for the second task are similar. If the list of PropVal elements is not in the same order, i.e. a lookup is needed, it’s recommended to have a map, which implies that it might be simpler to let the previous step generate a map in the first place.
assert bNames.size()==bValues.size();
Map<String,String> bNameToValue = IntStream.range(0, bNames.size())
.collect(HashMap::new, (m,i)->m.put(bNames.get(i),bValues.get(i)), Map::putAll);
assert aNames.size()==aIDs.size();
Map<String,A> idToA = IntStream.range(0, aNames.size())
.mapToObj(i -> new A(aIDs.get(i), aNames.get(i), bNameToValue.get(aNames.get(i))))
.collect(Collectors.toMap(A::getId, Function.identity()));
List<PropVal> list2 = …
then, if A is mutable:
list2.forEach(pVal -> {
A a = idToA.get(pVal.id);
a.setVal1(pVal.propVal1);
a.setVal2(pVal.propVal2);
a.setVal3(pVal.propVal3);
a.setVal4(pVal.propVal4);
});
List<A> finalList = new ArrayList<>(idToA.values());
or if A is immutable:
List<A> finalList = list2.stream()
.map(pVal -> {
A a = idToA.get(pVal.id);
return new A(pVal.id, a.getName(), a.getValue(),
pVal.propVal1, pVal.propVal2, pVal.propVal3, pVal.propVal4);
})
.collect(Collectors.toList());
(note that this includes only those A instances into the list, for which a PropVal exists).
Please do not abuse java 8 features! You have data design drawbacks. Provide useful methods in your classes, migrate to suitable data structures. In your model it is easy to get inconsistent data.
Do not delegate all your problems to lambdas and streams.
I have 2 lists:
List1: Object1 (name1, id1)
List2: Object2(name2, id2)
Given the size of list1 is the same as list2
I want to iterate ove the list2 and if the name2 of list2 is not null than update the name1 of list1.
here is the code using old java:
for(Object1 obj1:list1) {
for(Object2 obj2:list2) {
if(obj1.getId1.equals(obj2.getId2)) {
obj1.setName1(obj2.getName2);
}
}
}
Which is the best way to implement this with java.util.stream?
Just to be clear, I think your code is intended to do the following: update the name of each item in list1 to be the name of any item in list2 that has the same ID. There doesn't seem to be anything checking if the names of items in list1 are null.
If that's correct, then:
list2.forEach(obj2 -> list1.stream()
.filter(obj1 -> obj1.getId().equals(obj2.getId()))
.forEach(obj1 -> obj1.setName(obj2.getName()));
If you want to check if name is null, then add a new filter before setting the name:
.filter(Objects::isNull)
As I mentioned in the comments. If the id is a uniqe identifier for your objects, then a Map is more appropriate than a List.
So you better work on such a map (assuming id is an integer):
Map<Integer, Object1> obj1map;
You could create that map from your first list with
obj1map = list1.stream().collect(toMap(Object1::getId, Function.identity()));
Now you can stream over your second list and update the map accordingly:
list2
.stream()
.filter(o -> o.getName() != null) // remove null names
.filter(o -> obj1map.containsKey(o.getId())) // just to make sure
.forEach(o -> obj1map.get(o.getId()).setName(o.getName()));
The idea of a stream is that it does not have context. Knowing where you are in the first stream is context which you would need to find the corresponding item in the second stream.
Use a normal for loop with an index i instead.
for (int i=0; i < list2.size(); i++) {
Object2 item2 = list2.get(i);
if (list2.get(i).name != null) {
list1.get(i).name = item.name;
}
}