Remove value from list in java containing map
I am having list in this form:
List: [{id=1,c_id=3,value=5},{id=1,c_id=2,value=5},{id=1,c_id=3,value=5},{id=2,c_id=1,value=5},
{id=2,c_id=null,value=5}}];
In result I am trying to get
List: [{id=1,c_id=3,value=5},,{id=1,c_id=2,value=5},{id=2,c_id=1,value=5}}]
For same id if c_id is same it should be removed and also if c_id is null it should be removed.
You can do
for(Iterator<Map<String, Object>> iter = list.iterator(); iter.hasNext(); ) {
Map<String, Object> map = iter.next();
Object c_id = map.get("c_id");
if (c_id == null || c_id.equals(matching_c_id))
iter.remove();
}
Guava solution:
Iterables.removeIf(list, new Predicate<Element>(){
#Override public boolean apply(Element e) {
return e.c_id == null || e.c_id.equals(matching_c_id);
}});
I think a solution could be the following one and I think it is readable.
First, construct a set element proxy for your object type:
public class MyTypeProxy {
private MyType obj;
public MyTypeProxy(MyType obj) {
this.obj = obj;
}
public MyType getObj() { return this.obj; }
public int hashcode() {
return obj.getC_id() ^ obj.getId();
}
public boolean equals(Object ref) {
if (ref.getClass() != MyTypeProxy.class) {
return false;
}
return
((MyTypeProxy)ref).obj.getC_id() == this.obj.getC_id() &&
((MyTypeProxy)ref).obj.getId() == this.obj.getC_id();
}
}
Then, in your code, you can construct a HashSet.
HashSet<MyTypeProxy> set = new HashSet<MyTypeProxy>();
for (MyType mt : list) {
if (mt.getC_id() != null){
set.add(new MyTypeProxy(myList));
}
}
The following does two things. First, it excludes objects with a null c_id field and second, it filters out objects with the same id and c_id combination. You might want to consider if keeping the first one is ok or not (that is what the above code does).
The set now contains exactly what you need.
Related
I am facing a situation similar to described below in my project, of which I am unable to implement the code.
I have a POJO Class
public class TranObject {
public String loadId;
public String vDate;
public String dDate;
public String pDate;
public TranObject(String loadId, String vDate, String dDate, String pDate) {
super();
this.loadId = loadId;
this.vDate = vDate;
this.dDate = dDate;
this.pDate = pDate;
}
//Getter and Setters
//toString()
}
Now I have another processor class where I want to implement some comparison between tranload objects that I am receiving through a data service call and collect them into another collection.
The implementation logic is given in the comments below. Please read the below comments
import java.util.Arrays;
import java.util.List;
public class DemoClass {
public static void main(String[] args) {
List<TranObject> listObj = Arrays.asList(
new TranObject("LOAD1", "20180102", "20180202", null),
new TranObject("LOAD2", "20180402", "20180403", null),
new TranObject("LOAD3", "20180102", "20180202", "20190302"),
new TranObject("LOAD4", "20180402", "20180403", null),
new TranObject("LOAD5", "20200202", "20200203", null)
);
/*
IF (obj1, obj3 vdate and dDate are equal)
IF(pDate == null for obj1 or obj3)
THEN obj1 and obj3 are equal/duplicates, and we collect them.
ELSE IF(pDate != null for obj1 and obj3)
IF(pDate is same for obj1 and obj3)
THEN obj1 and obj3 are duplicates, and we collect them.
ELSE
THEN obj1 and obj3 are unique.
*/
}
}
My End result should be a collection like List containing duplicate Tran objects for further update.
I searched internet in order to how to solve it using Lambda API.
-> Tried using groupingBy first with vDate and then dDate, but then I could not compare them for pDate equality.
Can anyone help me solve this issue. A little help will be very helpful for me. I am stuck here
UPDATE:
After some reading I am trying to implement the same by over-riding equals method in POJO class as shown below:
#Override
public boolean equals(Object obj) {
boolean isEqual=false;
if(obj!=null) {
TranObject tran = (TranObject) obj;
isEqual=(this.vDate.equals(tran.getvDate()) && this.dDate.equals(tran.getdDate()));
if(isEqual && this.pDate != null && tran.getpDate()!= null) {
isEqual = (this.pDate.equals(tran.getpDate()));
}
}
return isEqual;
}
Still it's not working as expected... Can anyone please help me why??
The closest to your requirement would be grouping in a nested manner and then filtering the inner Map for varied conditions while being interested only in values eventually.
Stream<Map<String, List<TranObject>>> groupedNestedStream = listObj.stream()
.collect(Collectors.groupingBy(a -> Arrays.asList(a.vDate, a.dDate)
, Collectors.groupingBy(t -> t.pDate == null ? "default" : t.pDate)))
.values().stream();
from these groupings further the conditions for the values (from map) to be eligible are
they all have same pDate in this case the innerMap would have just one entry with the common pDate (m.size() == 1)
one of the values after grouping has exactly one pDate as null (meaning m.containsKey("default") && m.get("default").size() == 1)
List<TranObject> tranObjects = groupedNestedStream
.filter(m -> m.size() == 1 || (m.containsKey("default") && m.get("default").size() == 1))
.flatMap(m -> m.values().stream().flatMap(List::stream))
.collect(Collectors.toList());
Note, the use of "default" string constant to avoid failures(or poor practice) in collecting a Map with null keys or values.
Sounds like TranObject needs an equals and hashCode method.
#Override
public boolean equals(Object obj) {
//check instanceof and self comparison
TranObject other = (TranObject) obj;
if(this.vDate.equals(other.vDate) && this.dDate.equals(other.dDate)) {
//if pDate is not given then consider them duplicate
if(this.pDate == null || other.pDate == null)
return true;
//if pDate are the same then they are duplicate, otherwise they are unique
return this.pDate.equals(other.pDate);
}
return false;
}
//auto generated by Eclipse
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dDate == null) ? 0 : dDate.hashCode());
result = prime * result + ((pDate == null) ? 0 : pDate.hashCode());
result = prime * result + ((vDate == null) ? 0 : vDate.hashCode());
return result;
}
Now that you have an equals method to determine if two TranObjects are considered equal (based on the rules you specified), just collect the elements that occur in the list more than once:
private static List<TranObject> collectDuplicates(List<TranObject> list) {
List<TranObject> result = new ArrayList<TranObject>();
for(TranObject element : list) {
if(Collections.frequency(list, element) > 1)
result.add(element);
}
return result;
}
This will return all elements that have a duplicate.
Note: collectDuplicates does not return a unique list of the elements that are duplicated. Instead, it returns a list of each duplicated element (as required by OP's question).
Have two lists
Class Fruit
{
private String name;
}
List<Fruit> list1 = new ArrayList<>;
List<Fruit> list2 = new ArrayList<>;
Find elements in list1 that are not in list2 comparing by the field in Fruit - name.
Solution1:
List list = new ArrayList<>;
for(Fruit list : list1)
{
if(!list2.contains(list1)
{
list.add(list);
}
}
But this does not take into consideration the field - Name to compare.
Please suggest the solution that helps in filtering with Name field without using streams
This is not taking Fruit name in consideration because you didn't implemented equals method.
From List boolean contains(Object o)
Returns true if this list contains the specified element. More formally, returns true if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
in this last condition the one which will check based on your equals implementation.
You have to implement equals method in you Fruit class to compare based on name
Eclipse generate equals method is below you can change based on your requirement:
public class Fruit {
private String name;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Fruit other = (Fruit) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
UPDATE
Anyway if you don't want to implement equals method you have to do bit more, here are stream code:
//It assume you have .getName() method in your Fruit class.
Predicate<Fruit> notInList2 = s -> list2.stream().noneMatch(e -> s.getName().equals(e.getName()));
List<Fruit> list3 = list1.stream().filter(notInList2).collect(Collectors.toList());
In the case of a large amount of data, consider time complexity. In most cases, I will consider converting the list into a set and using the characteristics of the hash table to speed up the lookup.
Set<String> set = new HashSet<>();
for(Fruit e:list2){
set.add(e.name);
}
List<Fruit> result = new ArrayList<>();
for(Fruit e:list1){
if(!set.contains(e.name)){
result.add(e);
}
}
How can i search through an ArrayList using the .contains method while being case insensitive? I've tried .containsIgnoreCase but found out that the IgnoreCase method only works for Strings.
Here's the method I'm trying to create:
private ArrayList<String> Ord = new ArrayList<String>();
public void leggTilOrd(String ord){
if (!Ord.contains(ord)){
Ord.add(ord);
}
}
You will need to iterate over the list and check each element. This is what is happening in the contains method. Since you are wanting to use the equalsIgnoreCase method instead of the equals method for the String elements you are checking, you will need to do it explicitly. That can either be with a for-each loop or with a Stream (example below is with a Stream).
private final List<String> list = new ArrayList<>();
public void addIfNotPresent(String str) {
if (list.stream().noneMatch(s -> s.equalsIgnoreCase(str))) {
list.add(str);
}
}
If you are using Java7, simply override the contains() method,
public class CastInsensitiveList extends ArrayList<String> {
#Override
public boolean contains(Object obj) {
String object = (String)obj;
for (String string : this) {
if (object.equalsIgnoreCase(string)) {
return true;
}
}
return false;
}
}
If you are using Java 8.0, using streaming API,
List<String> arrayList = new ArrayList<>();
arrayList.stream().anyMatch(string::equalsIgnoreCase);
The List#Ccontains() method check if the parameter is present in the list but no changes are made in the list elements
use streams instead
public void leggTilOrd(String ordParameter) {
final List<String> ord = Arrays.asList(new String[]{ "a", "A", "b" });
final boolean ordExists = ord.stream().anyMatch(t -> t.equalsIgnoreCase(ordParameter));
System.out.println(ordExists);
}
If you want to avoid duplicates use HashSet instead of List. Hashing works faster while searching. In the underlying class override the equals and hashcode method to return expected results using String.toUpperCase(). If you have a List of String, you can create a string wrapper class.
String Wrapper could look like this:-
public class CaseInsensitiveString {
String string;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((string == null) ? 0 : string.toUpperCase().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CaseInsensitiveString other = (CaseInsensitiveString) obj;
if (string == null) {
if (other.string != null)
return false;
} else if (!string.toUpperCase().equals(other.string.toUpperCase()))
return false;
return true;
}
// Other Methods to access string
}
I have this List<MyObject> list.
MyObject looks like this :
public class MyObject {
String typeOfObject;
Integer anInteger;
Integer value;
}
How to get the object(s) from list where typeOfObject = "objectA" and anInteger = 1 ?
My goal is to set many variables with value of MyObject.
I looked at Predicate, but it seems that I can base my search condition only on one attribute ?
Predicates can do exactly what you want and can work on as many attributes as you want them to. You just need to implement the evaluate() method with whatever criteria you want to test against.
public class MyPredicate implements Predicate {
private String testId;
private String otherId;
public MyPredicate(String testId,String otherId) {
this.testId = testId;
this.otherId = otherId;
}
public boolean evaluate( Object obj ) {
boolean match = false;
if( obj instanceof MyObject ) {
if( testId.equalsIgnoreCase( ((MyObject)obj).getId())
&& otherId.equalsIgnoreCase( ((MyObject)obj).getOtherId()) ) {
match = true;
}
}
return match;
}
}
So in your specific case, the evaluate method would look something like:
public boolean evaluate( Object obj ) {
boolean match = false;
if( obj instanceof MyObject ) {
if( "objectA".equals( ((MyObject)obj).typeOfObject )
&& 1 == ((MyObject)obj).anInteger ) {
match = true;
}
}
return match;
}
You would have to override equals() and hashcode() to accomplish that refer this link to accomplish that.
If you are using Java 8, you can use filter to get the elements matching your criteria, e.g. using a lambda expression. You can also implement a custom Predicate and use that in filter
List<MyObject> objects = Arrays.asList(
new MyObject("foo", 1, 42),
new MyObject("foo", 3, 23),
new MyObject("bar", 3, 42),
new MyObject("foo", 4, 42));
List<MyObject> filtered = objects.stream()
.filter(o -> o.typeOfObject.equals("foo") && o.value == 42)
.collect(Collectors.toList());
System.out.println(filtered);
Output is [(foo,1,42), (foo,4,42)], using this test class:
class MyObject {
String typeOfObject;
Integer anInteger;
Integer value;
public MyObject(String type, int i, int v) {
this.typeOfObject = type;
this.anInteger = i;
this.value = v;
}
public String toString() {
return String.format("(%s,%d,%d)", typeOfObject, anInteger, value);
};
}
I am filtering the all list which ahve same lat,long in one list and put into an same list and put that list into map My code is as:-
private Collection<List<Applicationdataset>> groupTheList(ArrayList<Applicationdataset> arrayList)
{
Map<Key, List<Applicationdataset>> map = new HashMap<Key, List<Applicationdataset>>();
for(Applicationdataset appSet: arrayList)
{
Key key = new Key(appSet.getLatitude(), appSet.getLongitude());
List<Applicationdataset> list = map.get(key);
if(list == null){
list = new ArrayList<Applicationdataset>();
}
list.add(appSet);
map.put(key, list);
}
return map.values();
}
public class Key {
String _lat;
String _lon;
Key(String lat, String lon) {
_lat = lat;
_lon = lon;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!_lat.equals(key._lat)) return false;
if (!_lon.equals(key._lon)) return false;
return true;
}
#Override
public int hashCode() {
int result = _lat.hashCode();
result = 31 * result + _lon.hashCode();
return result;
}
}
But When I am debuging my code according to xml which come from web-service there is 2 list which have same lat long and they are saving in same list in amp at time of debuging but when I go next step of debug the element of map which have 2 item list decrease and showing size 1 I am unable to rectify this issue.
Your code looks OK: You've overridden equals() and hashCode() consistently.
Check for whitespace in the lat/lng values as the cause of your problems, perhaps trim() in the constructor:
Key(String lat, String lon) {
_lat = lat.trim();
_lon = lon.trim();
}
Also, you can simplify your code to this:
#Override
public boolean equals(Object o) {
return o instanceof Key
&& _lat.equals(((Key)o)._lat))
&& _lon.equals(((Key)o)._lon));
}
#Override
public int hashCode() {
// String.hashCode() is sufficiently good for this addition to be acceptable
return _lat.hashCode() + _lon.hashCode();
}
Thats a bit hard to understand what you are trying to accomplish. But I believe the issue is that you are using both latitude and longitude in Key hashCode()/equals() implementation thats why second Applicationdataset in your input list replaces the first one in your map object. You should implement the case when related list was already put into map and do not replace it.