I have a list of objects which I want to aggregate one of the values of this object grouped by other values of the objects in this list.
I'm currently using the properties I want to group by as a Hash key and I'm traversing the object so:
ArrayList<MyObject> raw = Some Data;
Map<String, MyObject> map = new HashMap<String, MyObject>();
for (MyObject ungrouped : raw) {
String key = ungrouped.getStringOne().getName() + ungrouped.getStringTwo() + ungrouped.getStringThree();
if (map.containsKey(key)){
MyObject holder = map.get(key);
holder.setNumericProp(holder.getNumericProp() + ungrouped.getNumericProp());
// map.put(key, holder); //Edited after comments
}
else{
map.put(key, ungrouped);
}
}
return map.values().toArray(new MyObject[map.values().size()]);
Is there a more elegant way to do this without using the concatenated strings as a key?
If this was SQL (from which I'm several application layers away) it would be:
SELECT SUM(numericvalue) FROM sometable GROUP BY stringone, stringtwo , stringthree
Apart from some problems I see with the code, one solution would be to use (if you can afford it) Guava's Equivalence (or replicate it in your code). You'd implement an Equivalence<MyObject> and use a Map<Equivalence.Wrapper<MyObject>, MyObject> as a container; you'd make the equivalence on your three string members.
That would allow it not to break in this situation:
// Oops! Same key...
s1 = "foo", s2 = "bar", s3 = "baz"
s1 = "fooba", s2 = "rb", s3 = "az"
Also, you could use the return value of the map's .put() method (the old value):
MyObject holder = map.put(key, ungrouped);
if (holder != null)
holder.setNumericProp(etc);
If your looking elegantly solve this you can use lambdja libraries (Download Here, Website). For example you can SUM a column with the following code (look at this link):
double sum = sumFrom(select(sales,
having(on(Sale.class).getBuyer().isMale())
.and( having(on(Sale.class).getSeller().isMale())))).getCost();
You also can group with this (look at this link):
Group<Person> group = group(meAndMyFriends, by(on(Person.class).getAge()));
With this libraries you can solve your problem in few lines.
Related
When I wrote this piece of code due to the pnValue.clear(); the output I was getting was null values for the keys. So I read somewhere that adding values of one map to the other is a mere reference to the original map and one has to use the clone() method to ensure the two maps are separate. Now the issue I am facing after cloning my map is that if I have multiple values for a particular key then they are being over written. E.g. The output I am expecting from processing a goldSentence is:
{PERSON = [James Fisher],ORGANIZATION=[American League, Chicago Bulls]}
but what I get is:
{PERSON = [James Fisher],ORGANIZATION=[Chicago Bulls]}
I wonder where I am going wrong considering I am declaring my values as a Vector<String>
for(WSDSentence goldSentence : goldSentences)
{
for (WSDElement word : goldSentence.getWsdElements()){
if (word.getPN()!=null){
if (word.getPN().equals("group")){
String newPNTag = word.getPN().replace("group", "organization");
pnValue.add(word.getToken().replaceAll("_", " "));
newPNValue = (Vector<String>) pnValue.clone();
annotationMap.put(newPNTag.toUpperCase(),newPNValue);
}
else{
pnValue.add(word.getToken().replaceAll("_", " "));
newPNValue = (Vector<String>) pnValue.clone();
annotationMap.put(word.getPN().toUpperCase(),newPNValue);
}
}
sentenceAnnotationMap = (LinkedHashMap<String, Vector<String>>) annotationMap.clone();
pnValue.clear();
}
EDITED CODE
Replaced Vector with List and removed cloning. However this still doesn't solve my problem. This takes me back to square one where my output is : {PERSON=[], ORGANIZATION=[]}
for(WSDSentence goldSentence : goldSentences)
{
for (WSDElement word : goldSentence.getWsdElements()){
if (word.getPN()!=null){
if (word.getPN().equals("group")){
String newPNTag = word.getPN().replace("group", "organization");
pnValue.add(word.getToken().replaceAll("_", " "));
newPNValue = (List<String>) pnValue;
annotationMap.put(newPNTag.toUpperCase(),newPNValue);
}
else{
pnValue.add(word.getToken().replaceAll("_", " "));
newPNValue = pnValue;
annotationMap.put(word.getPN().toUpperCase(),newPNValue);
}
}
sentenceAnnotationMap = annotationMap;
}
pnValue.clear();
You're trying a bunch of stuff without really thinking through the logic behind it. There's no need to clear or clone anything, you just need to manage separate lists for separate keys. Here's the basic process for each new value:
If the map contains our key, get the list and add our value
Otherwise, create a new list, add our value, and add the list to the map
You've left out most of your variable declarations, so I won't try to show you the exact solution, but here's the general formula:
List<String> list = map.get(key); // try to get the list
if (list == null) { // list doesn't exist?
list = new ArrayList<>(); // create an empty list
map.put(key, list); // insert it into the map
}
list.add(value); // update the list
EDIT
I've tried this HashMap with multiple values under the same key, and my hashMap now looks like this HashMap<String, List<Place>> placeMap = new HashMap<>();
Also tried to put Object instead of Place(place is my superclass). But when I now create my subclasses and wants to add them to the HashMap I get:
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, NamedPlace)
and
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, DescPlace)
here is my adding which created the error:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
placeMap.put(answer, p);
DescPlace dp = new DescPlace(x,y,answer, desc, col, cat);
mp.add(dp);
placeMap.put(answer, dp);
NamedPlace and DescPlace are both subclasses to Place, and I want them both in the same HashMap..
OP
I'm working on a little project here. The thing is that I need to use a HashMap instead of a ArrayList on this part of the project because HashMap is alot faster for searching. I've created a HashMap like this:
HashMap<String, Object> placeMap = new HashMap<>();
The String is the name of the Object, but the thing is that more than one object can have the same name. So I search for a object in my searchfield and I want to store all those objects that has that name into an ArrayList so I can change info in just them.
The object have alot of different values, like name, position, some booleans etc.
Do I need to create a HashCode method into my object class which shall create a unique hashcode?
When using a standard Map<String, List<YourClassHere>> instance, it is important to remember that the map's values for each entry will be a List<YourClassHere>, and will not handle it in any special way. So in your case, if you have
private Map<String, List<Place>> placeMap = new HashMap<>();
Then to store values you will need to do as follows:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
List<Place> list = placeMap.get (answer);
list.add(p);
However, this piece of code has some underlying problems.
It doesn't take into account that answer might not be present in placeMap.
It assumes that there's always a List<Place> instance for each key you query.
So the best way to fix those potential problems is to do as follows (Java 7 and later):
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
if (placeMap.containsKey (answer) && placeMap.get (answer) != null) {
placeMap.get (answer).add(p);
} else {
List<Place> list = new ArrayList<Place> (); // ..or whatever List implementation you need
list.add (p);
placeMap.put (answer, list);
}
If you want to scna through the list of places, the code would look like this:
if (placeMap.containsKey (key) && placeMap.get (answer) != null) {
for (Place p: placeMap.get (key)) {
// Do stuff
}
}
Lets say you have an Iterator which will contains values that you need to compare with values that are located in a separate List.
Iterator<Map.Entry<String, Object>> it = aObj.items();
while (it.hasNext()) {
Map.Entry<String, Object> item = it.next();
nameValue = item.getNameValue();
keyValue = item.getKeyValue();
System.out.println("Name: " + nameValue);
System.out.println("Value: " + keyValue);
}
This outputs:
Name: header
Value: 22222
Lets say you have a separate list (in which you want to compare the above values with):
List<Items> items = new ArrayList<>();
for (Item item : items) {
itemNameValue = item.getName();
itemKeyValue = item.getKey();
System.out.println("Name: " + itemNameValue);
System.out.println("Value: " + itemKeyValue);
}
This outputs:
Name: header
Value: 44444
Since these are different types of loops (one is a while loop and the other one is a for each loop)
how can you compare for example:
if (nameValue.equals(itemNameValue())) {
// do something?
}
I need to iterate over both collections / data structures at the same time...
Would this be the solution?
String nameValue = "";
Object keyValue = "";
String itemNameValue = "";
String itemKeyValue = "";
Iterator<Map.Entry<String, Object>> it = aObj.items();
while (it.hasNext()) {
Map.Entry<String, Object> item = it.next();
nameValue = item.getNameValue();
keyValue = item.getKeyValue();
for (Item item : items) {
itemNameValue = item.getName();
itemKeyValue = item.getKey();
}
if (nameValue.equals(itemNameValue())) {
// do something?
}
}
Basically, what I am trying to ask (in a very simplified way is this):
(1) The collection that needs to be iterated in a while loop is just test input (sample data)
(2) The array list from the second collection is really a list of data which was returned from a database call (DAO) and placed into the ArrayList.
I am trying to verify if the input from Iterator inside the while loop is the same as the values from the ArrayList (which came from a database). Since these are different data structures requiring different looping mechanisms. How could I iterate through both data structures at the same time and compare them? The second data structure (the array list) is the actual set of values that are correct.
I don't know if there's a guarantee that each iteration would be comparing the same items if I use a nested loop?
Thank you for taking the time to read this...
The problem you are facing is a direct result of a BAD Application design.
The underline incorrect assumption of this question is that the map and the list will hold the objects in the same sequence.
List --> A data structure that is ordered by not sorted
Map --> A data structure that is neither ordered nor sorted
This is not to say that these two data structures don't work well together. However, using them to store the same list should only result from an awkward program design.
Even though to answer your question, you can use the below code to accomplish this:
Iterator<Map.Entry<String, Object>> it = aObj.items();
List<Items> items = dbCall.getItems(); // Get the list of Items from the DB
int index = 0;
while (it.hasNext()) {
Map.Entry<String, Object> itemFromMap = it.next();
Item itemFromList = items.get(index);
if(itemFromMap.getNameValue().equals(itemFromList.getName()) &&
itemFromMap.getKeyValue().equals(itemFromList.getKey())){
// If you prefer a single .equals() method over &&, then you can implement a Comparator<Item>
return false;
}
index++;
}
return true;
I am currently working on one of the usecases where you are given 6 strings which has 3 oldValues and 3 newValues like given below:
String oldFirstName = "Yogend"
String oldLastName = "Jos"
String oldUserName = "YNJos"
String newFirstName = "Yogendra"
String newLastName ="Joshi"
String newUserName = "YNJoshi"
now what I basically want to do is compare each of the oldValue with its corresponding new value and return true if they are not equal i.e
if(!oldFirstName.equalsIgnoreCase(newFirstName)) {
return true;
}
Now, since I am having 3 fields and it could very well happen that in future we might have more Strings with old and new value I am looking for an optimum solution which could work in all cases no matter how many old and new values are added and without having gazillions of if else clauses.
One possibility I thought was of having Old values as OldArrayList and new values as newArraylist and then use removeAll where it would remove the duplicate values but that is not working in some cases.
Can anyone on stack help me out with some pointers on how to optimum way get this done.
Thanks,
Yogendra N Joshi
you can use lambdaj (download here,website) and hamcrest (download here,website), this libraries are very powerfull for managing collections, the following code is very simple and works perfectly:
import static ch.lambdaj.Lambda.filter;
import static ch.lambdaj.Lambda.having;
import static ch.lambdaj.Lambda.on;
import static org.hamcrest.Matchers.isIn;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> oldNames = Arrays.asList("nameA","nameE","nameC","namec","NameC");
List<String> newNames = Arrays.asList("nameB","nameD","nameC","nameE");
List<String> newList = filter(having(on(String.class), isIn(oldNames)),newNames);
System.out.print(newList);
//print nameC, nameE
}
}
With this libraries you can solve your problem in one line. You must add to your project: hamcrest-all-1.3.jar and lambdaj-2.4.jar Hope this help serve.
NOTE: This will help you assuming you can have alternatives to your code.
You can use two HashMap<yourFieldName, yourFieldValue> instead of two Arrays / Lists / Sets of Strings (or multiple random Strings);
Then you need a method to compare each value of both maps by their keys;
The result will be an HashMap<String,Boolean> containing the name of each field key, and true if the value is equal in both maps, while false if it is different.
No matter how many fields you will add in the future, the method won't change, while the result will.
Running Example: https://ideone.com/dIaYsK
Code
private static Map<String,Boolean> scanForDifferences(Map<String,Object> mapOne,
Map<String,Object> mapTwo){
Map<String,Boolean> retMap = new HashMap<String,Boolean>();
Iterator<Map.Entry<String, Object>> it = mapOne.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>)it.next();
if (mapTwo.get(entry.getKey()).equals(entry.getValue()))
retMap.put(entry.getKey(), new Boolean(Boolean.TRUE));
else
retMap.put(entry.getKey(), new Boolean(Boolean.FALSE));
it.remove(); // prevent ConcurrentModificationException
}
return retMap;
}
Test Case Input
Map<String,Object> oldMap = new HashMap<String,Object>();
Map<String,Object> newMap = new HashMap<String,Object>();
oldMap.put("initials","Y. J.");
oldMap.put("firstName","Yogend");
oldMap.put("lastName","Jos");
oldMap.put("userName","YNJos");
oldMap.put("age","33");
newMap.put("initials","Y. J.");
newMap.put("firstName","Yogendra");
newMap.put("lastName","Joshi");
newMap.put("userName","YNJoshi");
newMap.put("age","33");
Test Case Run
Map<String,Boolean> diffMap = Main.scanForDifferences(oldMap, newMap);
Iterator<Map.Entry<String, Boolean>> it = diffMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Boolean> entry = (Map.Entry<String,Boolean>)it.next();
System.out.println("Field [" + entry.getKey() +"] is " +
(entry.getValue()?"NOT ":"") + "different" );
}
You should check too if a value is present in one map and not in another one.
You could return an ENUM instead of a Boolean with something like EQUAL, DIFFERENT, NOT PRESENT ...
You should convert your String to some Set.
One set for OLD and another for NEW. And your goal of varity number of elements will also be resolved using same.
As it's set order of it will be same.
Edit: My list is sorted as it is coming from a DB
I have an ArrayList that has objects of class People. People has two properties: ssn and terminationReason. So my list looks like this
ArrayList:
ssn TerminatinoReason
123456789 Reason1
123456789 Reason2
123456789 Reason3
568956899 Reason2
000000001 Reason3
000000001 Reason2
I want to change this list up so that there are no duplicates and termination reasons are seperated by commas.
so above list would become
New ArrayList:
ssn TerminatinoReason
123456789 Reason1, Reason2, Reason3
568956899 Reason2
000000001 Reason3, Reason2
I have something going where I am looping through the original list and matching ssn's but it does not seem to work.
Can someone help?
Code I was using was:
String ssn = "";
Iterator it = results.iterator();
ArrayList newList = new ArrayList();
People ob;
while (it.hasNext())
{
ob = (People) it.next();
if (ssn.equalsIgnoreCase(""))
{
newList.add(ob);
ssn = ob.getSSN();
}
else if (ssn.equalsIgnoreCase(ob.getSSN()))
{
//should I get last object from new list and append this termination reason?
ob.getTerminationReason()
}
}
To me, this seems like a good case to use a Multimap, which would allow storing multiple values for a single key.
The Google Collections has a Multimap implementation.
This may mean that the Person object's ssn and terminationReason fields may have to be taken out to be a key and value, respectively. (And those fields will be assumed to be String.)
Basically, it can be used as follows:
Multimap<String, String> m = HashMultimap.create();
// In reality, the following would probably be iterating over the
// Person objects returned from the database, and calling the
// getSSN and getTerminationReasons methods.
m.put("0000001", "Reason1");
m.put("0000001", "Reason2");
m.put("0000001", "Reason3");
m.put("0000002", "Reason1");
m.put("0000002", "Reason2");
m.put("0000002", "Reason3");
for (String ssn : m.keySet())
{
// For each SSN, the termination reasons can be retrieved.
Collection<String> termReasonsList = m.get(ssn);
// Do something with the list of reasons.
}
If necessary, a comma-separated list of a Collection can be produced:
StringBuilder sb = new StringBuilder();
for (String reason : termReasonsList)
{
sb.append(reason);
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
String commaSepList = sb.toString();
This could once again be set to the terminationReason field.
An alternative, as Jonik mentioned in the comments, is to use the StringUtils.join method from Apache Commons Lang could be used to create a comma-separated list.
It should also be noted that the Multimap doesn't specify whether an implementation should or should not allow duplicate key/value pairs, so one should look at which type of Multimap to use.
In this example, the HashMultimap is a good choice, as it does not allow duplicate key/value pairs. This would automatically eliminate any duplicate reasons given for one specific person.
What you might need is a Hash. HashMap maybe usable.
Override equals() and hashCode() inside your People Class.
Make hashCode return the people (person) SSN. This way you will have all People objects with the same SSN in the same "bucket".
Keep in mind that the Map interface implementation classes use key/value pairs for holding your objects so you will have something like myHashMap.add("ssn",peopleobject);
List<People> newlst = new ArrayList<People>();
People last = null;
for (People p : listFromDB) {
if (last == null || !last.ssn.equals(p.ssn)) {
last = new People();
last.ssn = p.ssn;
last.terminationReason = "";
newlst.add(last);
}
if (last.terminationReason.length() > 0) {
last.terminationReason += ", ";
}
last.terminationReason += p.terminationReason;
}
And you get the aggregated list in newlst.
Update: If you are using MySQL, you can use the GROUP_CONCAT function to extract data in your required format. I don't know whether other DB engines have similar function or not.
Update 2: Removed the unnecessary sorting.
Two possible problems:
This won't work if your list isn't sorted
You aren't doing anything with ob.getTerminationReason(). I think you mean to add it to the previous object.
EDIT: Now that i see you´ve edited your question.
As your list is sorted, (by ssn I presume)
Integer currentSSN = null;
List<People> peoplelist = getSortedList();//gets sorted list from DB.
/*Uses foreach construct instead of iterators*/
for (People person:peopleList){
if (currentSSN != null && people.getSSN().equals(currentSSN)){
//same person
system.out.print(person.getReason()+" ");//writes termination reason
}
else{//person has changed. New row.
currentSSN = person.getSSN();
system.out.println(" ");//new row.
system.out.print(person.getSSN()+ " ");//writes row header.
}
}
If you don´t want to display the contents of your list, you could use it to create a MAP and then use it as shown below.
If your list is not sorted
Maybe you should try a different approach, using a Map. Here, ssn would be the key of the map, and values could be a list of People
Map<Integer,List<People>> mymap = getMap();//loads a Map from input data.
for(Integer ssn:mymap.keyset()){
dorow(ssn,mymap.get(ssn));
}
public void dorow(Integer ssn, List<People> reasons){
system.out.print(ssn+" ");
for (People people:reasons){
system.out.print(people.getTerminationReason()+" ");
}
system.out.println("-----");//row separator.
Last but not least, you should override your hashCode() and equals() method on People class.
for example
public void int hashcode(){
return 3*this.reason.hascode();
}