I have created a new LinkedHashMap 'workingStrData' using 'strData' and I still get the error.
I am trying to remove some items from this LinkedHashMap based of another list.
The way strData is structured is
strData = [components[{key1:value1}{key2:value2}...]]
def workingStrData = new LinkedHashMap(strData)
List componentsToRemove = ['item1','item2'...]
int itemsRemoved = 0
workingStrData.components.eachWithIndex {
comp, workingStrIndex ->
println("Index: "+workingStrIndex+" Component: "+comp.number)
def baditem = comp.number in componentsToRemove
if (baditem) {
strData.components.remove(workingStrIndex - itemsRemoved)
itemsRemoved += 1
}
}
You cannot remove an element from a list while iterating with each or with eachWithIndex which by the way is a bad practice. Groovy offers much more elegant and simpler solutions.
As suggested, try retainAll() or as suggested here, try removeAll():
def strData = [
components : [
[number: 'item0'],
[number: 'item1']
]
]
def componentsToRemove = [
'item1','item2'
]
componentsToRemove.each { removeIt ->
strData.components.removeAll { it.number == removeIt }
}
assert strData.components == [[number: 'item0']]
It is not complaining about the strData/workingStrData maps, it is complaining about the List referenced by the value of the "components" key in the map.
You know, the List you are actually iterating, and the List you actually call remove(int index) on.
You don't actually iterate or modify the map, so copying that is meaningless.
Eddiel is right, you can't modify array ( and strData.components is an array ) while you iterate it.
Even if you created a copy of root map def workingStrData = new LinkedHashMap(strData) the content of this map referencing the same data.
it means that workingStrData.components and strData.components referencing the same array.
groovy styled code to modify array:
def strData = [
components:[
[number:'111', name:'aaa'],
[number:'222', name:'bbb'],
[number:'333', name:'ccc'],
[number:'444', name:'ddd'],
]
]
List componentsToRemove = ['222','333']
//--------------------------------------
// if you want to keep original data:
def modData = [
components: strData.components.findAll{c-> c.number in componentsToRemove }
]
println "original : $strData"
println "modified : $modData"
//--------------------------------------
// if you want to modify existing data
strData.components.retainAll{c-> !( c.number in componentsToRemove) }
println "original (modified): $strData"
You can not modify the map unless you use an iterator or you use a synchronized collection. This is by design in Java. Check https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedMap-java.util.Map- to wrap your collection or, remove the elements using the iterator's own remove function.
In java, that would look like this:
Iterator<String> iterator = workingStrData.keySet().iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
// workingStrData is empty at this point.
Related
Java 11 here. I have a List<Foobar> as well as a Map<Foobar,List<String>>.
I would like to iterate over the list and:
if the current Foobar is a key in the map, and a specific string ("Can't please everyone") to that entry's value list
if the current Foobar is not a key in the map, and it as a new key, with a value that is an ArrayList consisting of a single string with the same value
I can accomplish this like so:
List<Foobar> foobarList = getSomehow();
Map<Foobar,List<String>> foobarMap = getItSomehow();
String msg = "Can't please everyone";
for (Foobar fb : foobarList) {
if (foobarMap.containsKey(fb)) {
foobarMap.get(fb).add(msg);
} else {
foobarMap.put(fb, Collections.singletonList(msg));
}
}
This works great, but I'm trying to get this to work using the Java Stream API. My best attempt thus far:
List<Foobar> foobarList = getSomehow();
Map<Foobar,List<String>> foobarMap = getItSomehow();
String msg = "Can't please everyone";
foobarList.stream()
.filter(fb -> foobarMap.containsKey(fb))
.map(fb -> foobarMap.get(fb).add(msg))
.filter(fb -> !foobarMap.containsKey(fb))
.map(fb -> foobarMap.put(fb. Collections.singleton(msg));
Yields several compiler errors. Can anyone spot where I'm going awry?
Streams are used either
To modify the contents of the stream elements, or
To produce another stream from it, or
To iterate over the elements and do something that doesn't affect the elements of this stream.
Since your use case is the last type, the logical operation is simply forEach(..). (I know it is a dampener :-), but that is how the use case is.)
foobarList.forEach( fb -> {
if (foobarMap.containsKey(fb)) {
foobarMap.get(fb).add(msg);
} else {
foobarMap.put(fb, Collections.singletonList(msg));
}
} );
As noticed by #Sree Kumar, you should use forEach().
However, I would suggest leveraging the Map.merge() method:
foobarList.forEach(fb -> foobarMap.merge(fb, Collections.singletonList(msg),
(l1, l2) -> Stream.concat(l1.stream(), l2.stream()).toList()));
My sample request
{
"requestModel":{
"CUSTID": "100"
},
"returnParameters":[
{
"name":"NETWORK/NETID",
"datatype":"String",
"order":"asc",
"sequence":1
},
{
"name":"INFODATA/NAME",
"datatype":"String",
"order":"asc",
"sequence":1
},
{
"name":"SOURCE/SYSTEM",
"datatype":"int",
"order":"asc",
"sequence":2
},
]
}
Sample Response
Below is my dynamically generated Map format of json response[Response parameters will be different each time based on the request params],
"responseModel":{
"documents": [
{
"NETWORK":[
{"NETID":"1234"},
{"ACT":"300"}
],
"SOURCE": {
"SYSTEM":"50"
},
"INFODATA":{
"NAME":"PHIL"
}
},
{
"NETWORK":[
{"NETID":"1234"},
{"ACT":"300"}
],
"SOURCE": {
"SYSTEM":"100"
},
"INFODATA":{
"NAME":"PHIL"
}
}
]
}
Problem Statement
I need to do multi level sorting based on the "returnParameters" in the request which is dynamic...
"order" indicates ascending (or) descending and sequence indicates the the priority for ordering like (group by in sql query)
Code
Map<String,Object> documentList = new HashMap<String,Object>();
JSONObject jsonObject= new JSONObject(response.getContent());
response.getContent() -> is nothing but it contains the above json response in Map format.
Now I converting the map to list of json object
JSONArray jsonArray= (JSONArray)jsonObject.get("documents");
ArrayList<JSONObject> list = new ArrayList<>();
for(int i=0;i<jsonArray.length();i++){
list.add((JSONObject) jsonArray.get(i));
}
Collections.sort(list, new ResponseSorter());
public class ResponseSorter implements Comparator<JSONObject> {
#Override
public int compare(JSONObject o1,JSONObject o2){
String s1= (String)((JSONObject) o1.get("NETWORK")).get("NETID");
String s2= (String)((JSONObject) o2.get("NETWORK")).get("NETID");
int i1=Integer.parseInt(s1);
int i2=Integer.parseInt(s2);
return i1-i2;
}
}
I'm stuck here to proceed further. Created one for Integer comparator, .Should I create for each dataType? also
I need to dynamically construct the composite comparator by parsing the "retunrParameters" , below sample is hard coded, how to create dynamically??
(String)((JSONObject) o1.get("NETWORK")).get("NETID"); -> this should be dynamically framed , since "returnParameters" are also dynamic in nature.[NETWORK & NETID may not be come in another request],so my comparator should be capable enough to frame the keys in runtime
Would anyone able to assist me to create composite comparator in runtime for sorting?
NOTE:- Java Pojo cannot be created as the response is dynamic nature
In your case a simple comparator that's provided with the sort parameters might be easier to understand than a bunch of nested comparators.
Basically you'd do something like this:
class ReturnParameterComparator implements Comparator<JSONObject> {
private List<ReturnParameter> params; //set via constructor
public int compare( JSONObject left, JSONObject right) {
int result = 0;
for( ReturnParameter p : params ) {
//how exactly you get those values depends on the actual structure of your data and parameters
String leftValueStr = left.get( p );
String rightValueStr = right.get( p );
switch( p.datatype ) {
case "String":
result = String.compare( leftValueStr, rightValueStr );
break;
case "int":
//convert and then compare - I'll leave the rest for you
}
//invert the result if the order is descending
if( "desc".equals(p.order ) {
result += -1;
}
//the values are not equal so return the order, otherwise continue with the next parameter
if( result != 0 ) {
return result;
}
}
//at this point all values are to be considered equal, otherwise we'd have returned already (from the loop body)
return 0;
}
}
Note that this is just a stub to get you started. You'll need to add quite a few things:
how to correctly use the parameters to extract the values from the json objects
how to convert the data based on the type
how to handle nulls, missing or incompatible data (e.g. if a value should be sorted as "int" but it can't be parsed)
Adding all those would be way too much for the scope of this question and depends on your data and requirements anyway.
EDITED after additional questions in comments and additional info in description
You have a couple of steps you need to do here to get to the solution:
You want to have the sorting be dynamic based on the value of the property sequence in the request. So you need to parse the names of those returnParameters and put them in order. Below I map them to a List where each String[] has the name and order (asc/desc). The list will be ordered using the value of sequence:
List<String[]> sortParams = params.stream() // params is a List<JSONObject>
.filter(json -> json.containsKey("sequence")) // filter those that have "sequence" attribute
.sorted( sequence ) // sorting using Comparator called sequence
.map(jsonObj -> new String[]{jsonObj.get("name").toString(), jsonObj.get("order").toString()} )
.collect(Collectors.toList());
Before this you'll map the objects in the returnParameters array in the request to a List first.Then the stream is processed by 1. filtering the JSONObjects to only keep those that have prop sequence, 2. sorting the JSONObjects using comparator below. 3. from each JSONObject get "name" & "order" and put them in a String[], 4. generate a list with those Arrays. This list will be ordered in the order of attributes with priority 1 first, then priority 2, etc, so it will be ordered in the same way you want the JSONObjects ordered in the end.
Comparator<JSONObject> sequence = Comparator.comparingInt(
jsonObj -> Integer.valueOf( jsonObj.get("sequence").toString() )
);
So for your example, sortParams would look like: List( String[]{"NETWORK/NETID", "asc"}, String[]{""INFODATA/NAME", "asc"}, String[]{"SOURCE/SYSTEM", "asc"} )
Then you need to write a method that takes two params: a JSONObject and a String (the path to the property) and returns the value of that property. Originally I advised you to use JSONAware interface and then figure out the sub-class, but let's forget about that for now.
I am not going to write this method for you. Just keep in mind that .get(key) method of JSON.Simple always yields an Object. Write a method with this signature:
public String findSortValue(JSONObject doc, String path){
// split the path
// find the parent
// cast it (parent was returned as an Object of type Object)
// find the child
return value;
}
Write a generic individual comparator (that compares values of just one sort attribute at a time) and figures out if it's an Int, Date or regular String. I would write this as a regular method so it'll be easier to combine everything later on. Since you had so many questions about this I've made an example:
int individualComparator(String s1, String s2){
int compResult = 0;
try{
int numeric1 = Integer.parseInt(s1);
int numeric2 = Integer.parseInt(s2);
compResult = numeric1 - numeric2; // if this point was reached both values could be parsed
} catch (NumberFormatException nfe){
// if the catch block is reached they weren't numeric
try{
DateTime date1 = DateTime.parse(s1);
DateTime date2 = DateTime.parse(s2);
compResult = date1.compareTo(date2); // compareTo method of joda.time, the library I'm using
} catch (IllegalArgumentException iae){
//if this catch block is reached they weren't dates either
compResult = s1.compareTo(s2);
}
}
return compResult;
};
Write an overall Comparator that combines everything
Comparator<JSONObject> overAllComparator = (jsonObj1, jsonObj2) -> {
List<String[]> sortValuesList = sortParams.stream()
.map(path -> new String[]{ findValueByName(jsonObj1, path), findValueByName(jsonObj2, path) } )
.collect(Collectors.toList());
//assuming we always have 3 attributes to sort on
int comp1 = individualComparator(sortValuesList.get(0)[0], sortValuesList.get(0)[1]);
int comp2 = individualComparator(sortValuesList.get(1)[0], sortValuesList.get(1)[1]);
int comp3 = individualComparator(sortValuesList.get(2)[0], sortValuesList.get(2)[1]);
int result = 0;
if (comp1 != 0){
result = comp1;
} else if (comp2 != 0){
result = comp2;
} else{
result = comp3;
}
return result;
};
This Comparator is written lambda-style, for more info https://www.mkyong.com/java8/java-8-lambda-comparator-example/ .
First it takes the ordered list of sortParams we made in step 1 and for each returns an array where position 0 has the value for jsonObj1, and position 1 has the value for jsonObj2 and collects it in sortValuesList. Then for each attribute to sort on, it get the result of the individualComparatormethod. Then it goes down the line and returns as result of the overall comparison the first one that doesn't result in 0 (when a comparator results in 0 both values are equal).
The only thing that's missing now is the asc/desc value from the request. You can add that by chainingint comp1 = individualComparator(sortValuesList.get(0)[0], sortValuesList.get(0)[1]); with a simple method that takes an int & a String and multiplies the int by -1 if the String equals "desc". (Remember that in sortParams we added the value for order on position 1 of the array).
Because the first list we made, sortParams was ordered based on the priority indicated in the request, and we always did evertything in the order of this list, the result is a multi-sort in this order. It is generic & will be determined dynamically by the contents of returnParams in the request. You can apply it to your list of JSONObjects by using Collections.sort()
My suggestion: learn about:
Comparator.comparing which allows you to build your comparator by specifying the key extractor
Comparator.thanComparing which allows you to chain multiple comparators. The comparators later in the chain are called only if predecessors say the objects are equal
A tutorial if you need one: https://www.baeldung.com/java-8-comparator-comparing
I have a HashMap that contains List<Dto> and List<List<String>>:
Map<List<Dto>, List<List<String>>> mapData = new HashMap();
and an Arraylist<Dto>.
I want to iterate over this map, get the keys-key1, key2 etc and get the value out of it and set it to the Dto object and thereafter add it to a List. So i am able to successfully iterate using foreach and get it added to lists but not able to get it correctly done using Java 8. So i need some help on that. Here is the sample code
List<DTO> dtoList = new ArrayList();
DTO dto = new DTO();
mapData.entrySet().stream().filter(e->{
if(e.getKey().equals("key1")){
dto.setKey1(e.getValue())
}
if(e.getKey().equals("key2")){
dto.setKey2(e.getValue())
}
});
Here e.getValue() is from List<List<String>>()
so first thing is I need to iterate over it to set the value.
And second is I need to add dto to a Arraylist dtoList. So how to achieve this.
Basic Snippet that i tried without adding to a HashMap where List has keys, multiList has values and Dto list is where finally i add into
for(List<Dto> dtoList: column) {
if ("Key1".equalsIgnoreCase(column.getName())) {
index = dtoList.indexOf(column);
}
}
for(List<String> listoflists: multiList) {
if(listoflists.contains(index)) {
for(String s: listoflists) {
dto.setKey1(s);
}
dtoList.add(dto);
}
}
See https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.
So in your snippet above, filter isn't really doing anything. To trigger it, you'd add a collect operation at the end. Notice that the filter lambda function needs to return a boolean for your code to compile in the first place.
mapData.entrySet().stream().filter(entry -> {
// do something here
return true;
}).collect(Collectors.toList());
Of course you don't need to abuse intermediate operations - or generate a bunch of new objects - for straightforward tasks, something like this should suffice:
mapData.entrySet().stream().forEach(entry -> {
// do something
});
I have an JSONArray(org.json.JSONArray) of JSONObjects(org.json.JSONObject) like
[
{"id":"abc", "parent_id":""},
{"id":"def", "parent_id":"abc"},
{"id":"ghi", "parent_id":""},
{"id":"jkl", "parent_id":"abc"},
{"id":"mno", "parent_id":"ghi"},
{"id":"mno", "parent_id":"def"},
]
Here "id" field represents unique id of the Object and "parent_id" represents id of it's parent. I have to convert this JSONArray into another JSONArray where I can have elements nested inside their parent(directory like structure) like
[
{"id":"abc", "parent_id":"","children":[
{"id":"def", "parent_id":"abc","children":[
{"id":"mno", "parent_id":"def","children":[]}
]},
{"id":"jkl", "parent_id":"abc","children":[]}
]},
{"id":"ghi", "parent_id":"","children":[
{"id":"mno", "parent_id":"ghi","children":[]}
]},
]
Can anybody help me here what is the best possible way to do so?
You'll have something like this (pseudo code)
// Element is { id, children }
Dictionary<String, Element> elements;
for (JSONObject obj : arr) {
if (elements.hasKey(obj.id)) {
// Maybe you need to update your element or something here
} else {
// Create your element
elements[obj.id] = new Element(obj.id);
}
// if the parent does not exist, create a shadow of the parent
// (it'll get filled in with more info above if encountered later)
if (!elements.hasKey(obj.parent)) {
elements[obj.parent] = new Element(obj.parent);
}
// Add yourself to children
elements[obj.parent].children.push(elements[obj.id]);
}
// TODO: iterate your dictionary and put it into an array, this should be straightforward
// Or if you want the root of your tree return elements[""]
I apologize in advance for not being more specific, but this should work pretty generically for whatever you want to do. Also it's not Java, but easily convertible.
I'd like some sorthand for this:
Map rowToMap(row) {
def rowMap = [:];
row.columns.each{ rowMap[it.name] = it.val }
return rowMap;
}
given the way the GDK stuff is, I'd expect to be able to do something like:
Map rowToMap(row) {
row.columns.collectMap{ [it.name,it.val] }
}
but I haven't seen anything in the docs... am I missing something? or am I just way too lazy?
I've recently came across the need to do exactly that: converting a list into a map. This question was posted before Groovy version 1.7.9 came out, so the method collectEntries didn't exist yet. It works exactly as the collectMap method that was proposed:
Map rowToMap(row) {
row.columns.collectEntries{[it.name, it.val]}
}
If for some reason you are stuck with an older Groovy version, the inject method can also be used (as proposed here). This is a slightly modified version that takes only one expression inside the closure (just for the sake of character saving!):
Map rowToMap(row) {
row.columns.inject([:]) {map, col -> map << [(col.name): col.val]}
}
The + operator can also be used instead of the <<.
Check out "inject". Real functional programming wonks call it "fold".
columns.inject([:]) { memo, entry ->
memo[entry.name] = entry.val
return memo
}
And, while you're at it, you probably want to define methods as Categories instead of right on the metaClass. That way, you can define it once for all Collections:
class PropertyMapCategory {
static Map mapProperty(Collection c, String keyParam, String valParam) {
return c.inject([:]) { memo, entry ->
memo[entry[keyParam]] = entry[valParam]
return memo
}
}
}
Example usage:
use(PropertyMapCategory) {
println columns.mapProperty('name', 'val')
}
Was the groupBy method not available when this question was asked?
If what you need is a simple key-value pair, then the method collectEntries should suffice. For example
def names = ['Foo', 'Bar']
def firstAlphabetVsName = names.collectEntries {[it.charAt(0), it]} // [F:Foo, B:Bar]
But if you want a structure similar to a Multimap, in which there are multiple values per key, then you'd want to use the groupBy method
def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]
Also, if you're use google collections (http://code.google.com/p/google-collections/), you can do something like this:
map = Maps.uniqueIndex(list, Functions.identity());
ok... I've played with this a little more and I think this is a pretty cool method...
def collectMap = {Closure callback->
def map = [:]
delegate.each {
def r = callback.call(it)
map[r[0]] = r[1]
}
return map
}
ExpandoMetaClass.enableGlobally()
Collection.metaClass.collectMap = collectMap
Map.metaClass.collectMap = collectMap
now any subclass of Map or Collection have this method...
here I use it to reverse the key/value in a Map
[1:2, 3:4].collectMap{[it.value, it.key]} == [2:1, 4:3]
and here I use it to create a map from a list
[1,2].collectMap{[it,it]} == [1:1, 2:2]
now I just pop this into a class that gets called as my app is starting and this method is available throughout my code.
EDIT:
to add the method to all arrays...
Object[].metaClass.collectMap = collectMap
I can't find anything built in... but using the ExpandoMetaClass I can do this:
ArrayList.metaClass.collectMap = {Closure callback->
def map = [:]
delegate.each {
def r = callback.call(it)
map[r[0]] = r[1]
}
return map
}
this adds the collectMap method to all ArrayLists... I'm not sure why adding it to List or Collection didn't work.. I guess that's for another question... but now I can do this...
assert ["foo":"oof", "42":"24", "bar":"rab"] ==
["foo", "42", "bar"].collectMap { return [it, it.reverse()] }
from List to calculated Map with one closure... exactly what I was looking for.
Edit: the reason I couldn't add the method to the interfaces List and Collection was because I did not do this:
List.metaClass.enableGlobally()
after that method call, you can add methods to interfaces.. which in this case means my collectMap method will work on ranges like this:
(0..2).collectMap{[it, it*2]}
which yields the map: [0:0, 1:2, 2:4]
What about something like this?
// setup
class Pair {
String k;
String v;
public Pair(def k, def v) { this.k = k ; this.v = v; }
}
def list = [ new Pair('a', 'b'), new Pair('c', 'd') ]
// the idea
def map = [:]
list.each{ it -> map.putAt(it.k, it.v) }
// verify
println map['c']