Nullifying elements that doesnt match condition in list using streams - java

I have an POJO like so,
class Val {
private String name;
private String value;
}
Now I've got a List<Val> values. I'm trying to obtain all the elements from values that has name "ABC" such that the elements in the list that doesn't have the matching name should be null.
For example:
List<Val> values = [Val(name=ABC,value=val1), Val(name=DEF,value=val2), Val(name=ABC,value=val3), Val(name=ABC,value=val4)]
values.stream().filter(x -> x.getName().equals("ABC")).collect(); //this will filter out the matching elements. This I know.
//Expected Output. Not sure how to do this.
List<Val> valuesOut = [Val(name=ABC,value=val1), null, Val(name=ABC,value=val3), Val(name=ABC,value=val4)]
Size of both the input and output lists remain the same. Only the elements with names not as ABC is turned to null.
Any suggestions on how to do this using streams?

Related

Filtering Object having a null property along with Objects with non-null property matching certain criteria without getting NPE

I have a list of objects List<Objs>.
Each object has a property objId of type String which can be initialized with a comma-separated string like "1,2,3".
And I have a string array sections containing comma-separated strings like "2,4".
I'm splitting those strings using split(',') into arrays and then performing comparison for each element (e.g. "1,2" -> {"1,","2"}).
I'm trying to collect all the objects which are either match StringUtils.isEmpty(object.getObjId) or their objId match one of the sections values.
That is the desired behavior depending on the value of objId:
Case:1 objId = "1,2,3" and sections = "2,4", as long as both have one value match "2", hence object should be included into the list.
Case:2 objId = "1,3" and sections = "2", since both don't have match value and hence object should not be included into the list.
Case:3 objId = null || "" and doesn't matter what sections = "{any}" (meaning the contents of sections doesn't matter), the object should be included into the list.
Case:4 sections = null || "", only object having objId = null || "" should be included into the list.
The sample code below always returns me a NullPointerException. Could someone help to correct me?
My code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestStream {
private List<Objs> getData() {
List<Objs> objects = new ArrayList<>();
Objs objs1 = new Objs();
Objs objs2 = new Objs();
Objs objs3 = new Objs();
Objs objs4 = new Objs();
objs1.setObjId("1,2,3");
objects.add(objs1);
objs1.setObjId("4,5,6");
objects.add(objs2);
objs2.setObjId("7,84,3");
objects.add(objs3);
objs3.setObjId(null);
objects.add(objs4);
return objects;
}
private List<Objs> test () {
String[] sections = "2,8".split(",");
List<Objs> objects = getData();
List<Objs> result = objects.stream()
.filter(object -> object.getObjId() == null)
.filter(object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains))
.skip(0)
.limit(2)
.collect(Collectors.toList());
return result;
}
public static void main(String[] args) {
TestStream testStream= new TestStream();
List<Objs> result = testStream.test();
System.out.println(Arrays.toString(result.toArray()));
}
class Objs {
private String objId;
public void setObjId(String id) { objId = id;}
public String getObjId() { return objId;}
}
}
The skip and limit is for paging, currently I set it to skip 0 and limit for each page is 3 which means I am trying to retrieve the first page, each page can only have 3 items.
NullPointerException is being produced in the nested stream created inside the second filter:
.filter(object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains))
It occurs while attempting to invoke method contains on the property of your object retrieved by getObjId() and objId happens to be null.
Why getObjId() returns null ? There's an object in your sample data which has this field set to null and in the first filter you're specifically telling that you want to retain in the stream pipeline only those objects whose objId is null:
filter(object -> object.getObjId() == null)
You might have a misconception on how filter works, it will retain in the stream only those elements for which the predicate passed to the filter would be evaluated to true, not the opposite.
Here is a quote from the documentation:
Returns a stream consisting of the elements of this stream that match the given predicate.
You can to change the predicate in the first filter to object.getObjId() != null, but that can lead to incorrect results because Case 3 of your requirements will not be fulfilled.
Case:3 objId = null || "" and doesn't matter what sections = "{any}" (meaning the contents of sections doesn't matter), the object should be included into the list.
If filter out only objects having non-null id, then we will lose the objects with id null (if any), and empty string id also might not pass the criteria. Otherwise, if we would not apply any filtering before accessing id, it will result in getting NullPointerException.
Conclusion: to retain objects having id equal to null and to an empty string and also objects having id matching to one of the strings in the sections array, we can not use two separate filters.
Instead, we need to we need to combine these filters in a single filter.
To simplify the code, we can define two predicates. One as static field for checking if the id is null or an empty a string. Another as a local variable because it'll depend on the sections array.
That's how it might look like (the full code provided in the link below):
public static final Predicate<Objs> IS_NULL_OR_EMPTY =
object -> object.getObjId() == null || object.getObjId().isEmpty();
private List<Objs> test(String section) {
String[] sections = section.split(",");
List<Objs> objects = getData();
Predicate<Objs> hasMatchingSection =
object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains);
return objects.stream()
.filter(IS_NULL_OR_EMPTY.or(hasMatchingSection))
.limit(2)
.collect(Collectors.toList());
}
I didn't apply skip() because it doesn't make sense skipping 0 elements.
A link to Online Demo

Java How to identify which values of an Array of Strings are not present in a List of Objects?

Which is the most efficient way to achieve the following?
Having a List of Strings and a List of Objects :
List<String> arrayStrings=Arrays.asList("1", "2", "3","4");
List<MyCustomDTO> dtoList=new ArrayList();
MyCustomDTO myFirstDTO=new MyCustomDTO("1");
MyCustomDTO mySecondDTO=new MyCustomDTO("2");
dtoList.add(myFirstDTO);
dtoList.add(mySecondDTO);
Where MyCustomDTO only has the following:
public class MyCustomDTO {
private String code;
public MyCustomDTO(String code) {
this.code=code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
I need to iterate dtoList and check which values from arrayStrings are not present in it comparing to the Code field
In that example the correct output would be ["3","4"] ,because the only 2 elements inside dtoList ,have the code "1" and "2" ,that appears in the arrayStrings .
I know there are a couple of ways,but i am looking for the most efficient way to do this,because i could have a lot of elements inside both list.
Thanks in advance !
If expected number of elements in dtoList is big (let's say > 10_000) you should first create set of all DTO's codes and then filter arrayStrings using this set:
Set<String> dtoCodes = dtoList.stream().map(d -> d.getCode()).collect(Collectors.toSet());
List<String> result = arrayStrings.stream().filter(s -> !dtoCodes.contains(s)).collect(Collectors.toList());
However if expected cardinality of dtoList is small , you should go the most straightforward way without bothering CPU performance optimization.
Also note that cardinality of arrayStrings doesn't matter in that case.

Java: Searching for an entry in a List of Objects only by one attribute

I have the following List
List<A> list= new ArrayList<>();
public class A{
private String name;
private List<B> listB = new ArrayList<>(); }
I now want to search for an entry in list by which I only identify by it's name. The background is, that I get an Object of A which I want to add in listB. To make it more nice, I want to search if there is already a entry with this name in A and then add the entries which are given to the method there:
Example in text:
list:
name1
entry11
etnry12
entry13
name2
entry21
entry22
Now I want to Add the following listB to listA
name1
entry14
entry15
Behaviour now:
list:
name1
entry11
etnry12
entry13
name2
entry21
entry22
name1
entry14
entry15
Desired bahaviour:
list:
name1
entry11
etnry12
entry13
entry14
entry15
name2
entry21
entry22
Why not using a Set<A> instead of List<A> as the name should be unique for each instance of A in the collection ?
To achieve your need, you could override equals() and hashcode() method in A by relying on the the name field.
In this way, when you want to add elements from a list of A with a specific label to a list of A with this same specific label, you could create a A object with the expected name and retrieve it from the both lists.
Here is a sample code :
String name = "nameFromObjectWhereIWantToAddElements";
A aToSearch = new A(name);
A targetA = listTarget.get(listTarget.indexOf(aToSearch));
A originaA = listOrigin.get(listOrigin.indexOf(aToSearch));
targetA.addElements(originaA.getElements());
You could also use a Map if the structure suits to your needs.
A list with a key for the value has a data structure Map. Instead of using array list you can use Map to take the advantages of the JavaSE API.
You can use this method to do so if you use map. "A Set is a Collection that cannot contain duplicate elements." for more info refer to here.
public static void add(Map<String, HashSet<String>> listB) {
for (String entry : listB.keySet()) {
if (listA.containsKey(entry)) {
listA.get(entry).addAll(listB.get(entry));
} else {
listA.put(entry, listB.get(entry));
}
}
}
NOTE: I used Set to remove duplicate entries as the values for the keys. if you want to keep duplicate you can use ArrayList<>(). Moreover, Set do not keep the order beased on some implimentations and the order of the entries may vary time to time.

String Array into arraylist?

I have a csv file with unknown amount of columns and row. The only thing I know is that each entry is separated by a comma. Can I use the split method to convert each line of the data into an array and then can I store that Array into an Arraylist. One of the things that concerns me is would I be able to rearrange the Arraylist alphabetically or numerically.
I would suggest using OpenCSV. If you just split on the comma separator, and you happen to have a single cell text containing a comma, but which is enclosed in double quotes to make it clear that it's a single cell, the split method won't work:
1, "I'm a single cell, with a comma", 2
3, hello, 4
OpenCSV will let you read each line as an array of Strings, handling this problem for you, and you can of course store each array inside a List. You will need a custom comparator to sort your list of lines. Search StackOverflow: the question of how to sort a list comes back twice a day here.
Yes, you can use:
String[] array = input.split("\",\"");
List<String> words = new ArrayList<String>(Arrays.asList(array))
Note that Arrays.asList(..) also returns a List, but you can't modify it.
Also note that the above split is on ",", because CVSs usually look like this:
"foo","foo, bar"
Using split with simple comma is not a fool proof one. If your column data contains a comma, csv would be stored something like a,"b,x",c. In such case split would fail.
I'm not a regex expert maybe someone could write a EMBEDDED_COMMA_DETECTING_REGEX or GIYF.
String[] array = input.split(EMBEDDED_COMMA_DETECTING_REGEX);
List<String> words = new ArrayList<String>(Arrays.asList(array));
There are several questions here so I'll cover each point individually.
Can I use the split method convert each line of the data into an array
This would work as you expect in the naive case. However, it doesn't know anything about escaping; so if a comma is embedded within a field (and properly escaped, usually by double-quoting the whole field) the simple split won't do the job here and will chop the field in two.
If you know you'll never have to deal with embedded commas, then calling line.split(",") is acceptable. The real solution however is to write a slightly more involved parse method which keeps track of quotes, and possibly backslash escapes etc.
...into an array than can I store that Array into an Arraylist
You certainly could have an ArrayList<String[]>, but that doesn't strike me as particularly useful. A better approach would be to write a simple class for whatever it is the CSV lines are representing, and then create instances of that class when you're parsing each line. Something like this perhaps:
public class Order {
private final int orderId;
private final String productName;
private final int quantity;
private final BigDecimal price;
// Plus constructor, getters etc.
}
private Order parseCsvLine(String line) {
String[] fields = line.split(",");
// TODO validation of input/error checking
final int orderId = Integer.parseInt(fields[0]);
final String productName = fields[1];
final int quantity = Integer.parseInt(fields[2]);
final BigDecimal price = new BigDecimal(fields[3]);
return new Order(orderId, productName, quantity, price);
}
Then you'd have a list of Orders, which more accurately represents what you have in the file (and in memory) than a list of string-arrays.
One of the things that concerns me is would I be able to rearrange the Arraylist according alphabetically or numerically?
Sure - the standard collections support a sort method, into which you can pass an instance of Comparator. This takes two instances of the object in the list, and decides which one comes before the other.
So following on from the above example, if you have a List<Order> you can pass in whatever comparator you want to sort it, for example:
final Comparator<Order> quantityAsc = new Comparator<Order>() {
public int compare(Order o1, Order o2) {
return o2.quantity - o1.quantity; // smaller order comes before bigger one
}
}
final Comparator<Order> productDesc = new Comparator<Order>() {
public int compare(Order o1, Order o2) {
if (o2.productName == null) {
return o1.productName == null ? 0 : -1;
}
return o2.productName.compareTo(o1.productName);
}
}
final List<Order> orders = ...; // populated by parsing the CSV
final List<Order> ordersByQuantity = Collections.sort(orders, quantityAsc);
final List<Order> ordersByProductZToA = Collections.sort(orders, productDesc);

Groovy / Java method for converting nested List String representation back to List

I need to convert a String representation of a nested List back to a nested List (of Strings) in Groovy / Java, e.g.
String myString = "[[one, two], [three, four]]"
List myList = isThereAnyMethodForThis(myString)
I know that there's the Groovy .split method for splitting Strings by comma for example and that I could use regular expressions to identify nested Lists between [ and ], but I just want to know if there's an existing method that can do this or if I have to write this code myself.
I guess the easiest thing would be a List constructor that takes the String representation as an argument, but I haven't found anything like this.
In Groovy, if your strings are delimited as such, you can do this:
String myString = "[['one', 'two'], ['three', 'four']]"
List myList = Eval.me(myString)
However, if they are not delimited like in your example, I think you need to start playing with the shell and a custom binding...
class StringToList extends Binding {
def getVariable( String name ) {
name
}
def toList( String list ) {
new GroovyShell( this ).evaluate( list )
}
}
String myString = "[[one, two], [three, four]]"
List myList = new StringToList().toList( myString )
Edit to explain things
The Binding in Groovy "Represents the variable bindings of a script which can be altered from outside the script object or created outside of a script and passed into it."
So here, we create a custom binding which returns the name of the variable when a variable is requested (think of it as setting the default value of any variable to the name of that variable).
We set this as being the Binding that the GroovyShell will use for evaluating variables, and then run the String representing our list through the Shell.
Each time the Shell encounters one, two, etc., it assumes it is a variable name, and goes looking for the value of that variable in the Binding. The binding simply returns the name of the variable, and that gets put into our list
Another edit... I found a shorter way
You can use Maps as Binding objects in Groovy, and you can use a withDefault closure to Maps so that when a key is missing, the result of this closure is returned as a default value for that key. An example can be found here
This means, we can cut the code down to:
String myString = "[[one, two], [three, four]]"
Map bindingMap = [:].withDefault { it }
List myList = new GroovyShell( bindingMap as Binding ).evaluate( myString )
As you can see, the Map (thanks to withDefault) returns the key that was passed to it if it is missing from the Map.
I would parse this String manually. Each time you see a '[' create a new List, each time you see a ',' add an element to the list and each time you see a ']' return.
With a recursive method.
public int parseListString(String listString, int currentOffset, List list){
while(currentOffset < listString.length()){
if(listString.startsWith("[", currentOffset)){
//If there is a [ we need a new List
List newList = new ArrayList();
currentOffset = parseListString(listString, currentOffset+1, newList);
list.add(newList);
}else if(listString.startsWith("]", currentOffset){
//If it's a ], then the list is ended
return currentOffset+1;
}else{
//Here we have a string, parse it until next ',' or ']'
int nextOffset = Math.min(listString.indexOf(',', currentOffset), listString.indexOf(']', currentOffset));
String theString = listString.substring(int currentOffset, int nextOffset);
list.add(theString);
//increment currentOffset
currentOffset = nextOffset;
}
}
return currentOffset;
}

Categories