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 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.
I have scratched my head over this problem for a while now. I am basically trying to generate a tree hierarchy from a set of CSV data. The CSV data is not necessarily ordered. This is like something as follows:
Header: Record1,Record2,Value1,Value2
Row: A,XX,22,33
Row: A,XX,777,888
Row: A,YY,33,11
Row: B,XX,12,0
Row: A,YY,13,23
Row: B,YY,44,98
I am trying to make the way the grouping is performed as flexible as possible. The simplest for of grouping would to do it for Record1 and Record2 with the Value1 and Value2 stored under Record2 so that we get the following output:
Record1
Record2
Value1 Value2
Which would be:
A
XX
22,33
777,888
YY
33,11
13,23
B
XX
12,0
YY
44,98
I am storing my group settings in a List at present - which I don't know if this is hindering my thoughts. This list contains a hierarchy of the groups for example:
Record1 (SchemaGroup)
.column = Record1
.columns = null
.childGroups =
Record2 (SchemaGroup)
.column = Record1
.columns = Value1 (CSVColumnInformation), Value2 (CSVColumnInformation)
.childGroups = null
The code for this looks like as follows:
private class SchemaGroup {
private SchemaGroupType type = SchemaGroupType.StaticText; // default to text
private String text;
private CSVColumnInformation column = null;
private List<SchemaGroup> childGroups = new ArrayList<SchemaGroup>();
private List<CSVColumnInformation> columns = new ArrayList<CSVColumnInformation>();
}
private enum SchemaGroupType {
/** Allow fixed text groups to be added */
StaticText,
/** Related to a column with common value */
ColumnGroup
}
I am stuggling producing an algorithm for this, trying to think of the underlying structure to use. At present I am parsing the CSV top to bottom, using my own wrapper class:
CSVParser csv = new CSVParser(content);
String[] line;
while((line = csv.readLine()) != null ) {
...
}
I am just trying to kick start my coding brain.
Any thoughts?
The basic idea isn't difficult: group by the first record, then by the second record, etc. until you get something like this:
(A,XX,22,33)
(A,XX,777,888)
-------------------------
(A,YY,33,11)
(A,YY,13,23)
=============
(B,XX,12,0)
-------------------------
(B,YY,44,98)
and then work backwards to build the trees.
However, there is a recursive component that makes it somewhat hard to reason about this problem, or show it step by step, so it's actually easier to write pseudocode.
I'll assume that every row in your csv is represented like a tuple. Each tuple has "records" and "values", using the same terms you use in your question. "Records" are the things that must be put into a hierarchic structure. "Values" will be the leaves of the tree. I'll use quotations when I use these terms with these specific meanings.
I also assume that all "records" come before all "values".
Without further ado, the code:
// builds tree and returns a list of root nodes
// list_of_tuples: a list of tuples read from your csv
// curr_position: used to keep track of recursive calls
// number_of_records: assuming each csv row has n records and then m values, number_of_records equals n
function build_tree(list_of_tuples, curr_position, number_of_records) {
// check if we have already reached the "values" (which shouldn't get converted into trees)
if (curr_position == number_of_records) {
return list of nodes, each containing a "value" (i.e. everything from position number_of_records on)
}
grouped = group tuples in list_of_tuples that have the same value in position curr_position, and store these groups indexed by such common value
unique_values = get unique values in curr_position
list_of_nodes = empty list
// create the nodes and (recursively) their children
for each val in unique_values {
the_node = create tree node containing val
the_children = build_tree(grouped[val], curr_position+1, number_of_records)
the_node.set_children(the_children)
list_of_nodes.append(the_node)
}
return list_of_nodes
}
// in your example, this returns a node with "A" and a node with "B"
// third parameter is 2 because you have 2 "records"
build_tree(list_parsed_from_csv, 0, 2)
Now you'd have to think about the specific data structures to use, but hopefully this shouldn't be too difficult if you understand the algorithm (as you mention, I think deciding on a data structure early on may have been hindering your thoughts).
Here is the basic working solution in the form of junit (no assertions though) simplified by using google-guava collections. The code is self-explanatory and instead of file io you use csv libraries for reading the csv. This should give you the basic idea.
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
public class MyTest
{
#Test
public void test1()
{
List<String> rows = getAllDataRows();
Multimap<Records, Values> table = indexData(rows);
printTree(table);
}
private void printTree(Multimap<Records, Values> table)
{
Set<String> alreadyPrintedRecord1s = Sets.newHashSet();
for (Records r : table.keySet())
{
if (!alreadyPrintedRecord1s.contains(r.r1))
{
System.err.println(r.r1);
alreadyPrintedRecord1s.add(r.r1);
}
System.err.println("\t" + r.r2);
Collection<Values> allValues = table.get(r);
for (Values v : allValues)
{
System.err.println("\t\t" + v.v1 + " , " + v.v2);
}
}
}
private Multimap<Records, Values> indexData(List<String> lines)
{
Multimap<Records, Values> table = ArrayListMultimap.create();
for (String row : lines)
{
Iterable<String> split = Splitter.on(",").split(row);
String[] data = Iterables.toArray(split, String.class);
table.put(new Records(data[0], data[1]), new Values(data[2], data[3]));
}
return table;
}
private List<String> getAllDataRows()
{
List<String> lines = Collections.emptyList();
try
{
lines = Files.readLines(new File("C:/test.csv"), Charsets.US_ASCII);
}
catch (IOException e)
{
e.printStackTrace();
}
lines.remove(0);// remove header
return lines;
}
}
public class Records
{
public final String r1, r2;
public Records(final String r1, final String r2)
{
this.r1 = r1;
this.r2 = r2;
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((r1 == null) ? 0 : r1.hashCode());
result = prime * result + ((r2 == null) ? 0 : r2.hashCode());
return result;
}
#Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof Records))
{
return false;
}
Records other = (Records) obj;
if (r1 == null)
{
if (other.r1 != null)
{
return false;
}
}
else if (!r1.equals(other.r1))
{
return false;
}
if (r2 == null)
{
if (other.r2 != null)
{
return false;
}
}
else if (!r2.equals(other.r2))
{
return false;
}
return true;
}
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("Records1and2 [r1=").append(r1).append(", r2=").append(r2).append("]");
return builder.toString();
}
}
public class Values
{
public final String v1, v2;
public Values(final String v1, final String v2)
{
this.v1 = v1;
this.v2 = v2;
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((v1 == null) ? 0 : v1.hashCode());
result = prime * result + ((v2 == null) ? 0 : v2.hashCode());
return result;
}
#Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof Values))
{
return false;
}
Values other = (Values) obj;
if (v1 == null)
{
if (other.v1 != null)
{
return false;
}
}
else if (!v1.equals(other.v1))
{
return false;
}
if (v2 == null)
{
if (other.v2 != null)
{
return false;
}
}
else if (!v2.equals(other.v2))
{
return false;
}
return true;
}
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("Values1and2 [v1=").append(v1).append(", v2=").append(v2).append("]");
return builder.toString();
}
}
If you know you'll have just two levels of Records, I would use something like
Map<string, Map<string, List<Values>>>
When you read new line, you look into the outer map to check whether that value for Record1 already exists and if not, create new empty inner Map for it.
Then check the inner map whether a value for that Record2 exists. If not, create new List.
Then read the values and add them to the list.
I recently had a need to do pretty much the same thing and wrote tree-builder.com to accomplish the task. The only difference is that as you have your CSV laid out, the last two parameters will be parent and child instead of peers. Also, my version doesn't accept a header row.
The code is all in JavaScript; it uses jstree to build the tree. You can use firebug or just view the source on the page to see how it's done. It would probably be pretty easy to tweak it to escape the comma in your CSV in order to keep the last two parameters is a single child.
public static void main (String arg[]) throws Exception
{
ArrayList<String> arRows = new ArrayList<String>();
arRows.add("A,XX,22,33");
arRows.add("A,XX,777,888");
arRows.add("A,YY,33,11");
arRows.add("B,XX,12,0");
arRows.add("A,YY,13,23");
arRows.add("B,YY,44,98");
for(String sTreeRow:createTree(arRows,",")) //or use //// or whatever applicable
System.out.println(sTreeRow);
}
public static ArrayList<String> createTree (ArrayList<String> arRows, String sSeperator) throws Exception
{
ArrayList<String> arReturnNodes = new ArrayList<String>();
Collections.sort(arRows);
String sLastPath = "";
int iFolderLength = 0;
for(int iRow=0;iRow<arRows.size();iRow++)
{
String sRow = arRows.get(iRow);
String[] sFolders = sRow.split(sSeperator);
iFolderLength = sFolders.length;
String sTab = "";
String[] sLastFolders = sLastPath.split(sSeperator);
for(int i=0;i<iFolderLength;i++)
{
if(i>0)
sTab = sTab+" ";
if(!sLastPath.equals(sRow))
{
if(sLastFolders!=null && sLastFolders.length>i)
{
if(!sLastFolders[i].equals(sFolders[i]))
{
arReturnNodes.add(sTab+sFolders[i]+"");
sLastFolders = null;
}
}
else
{
arReturnNodes.add(sTab+sFolders[i]+"");
}
}
}
sLastPath = sRow;
}
return arReturnNodes;
}
Based upon how this problem is posed, I would do the following:
Define what your final data structure will look like to contain the
tree.
Define a representation for each row in your original text
(perhaps a linked list for flexibility)
Write a method that takes the represented row and inserts it into the tree data structure. For each non-existent branch, create it; for each existing branch, traverse it, as you step through your "row" link list structure.
Start with an empty tree.
Read each line of the file into your row item structure and call the method defined in step 3.
Does that help?