Let's say my query result is like this
MyClass(name=AAA, action=action1, price=5),
MyClass(name=BBB, action=action13, price=7),
MyClass(name=AAA, action=action31, price=2)
and want to create a map grouped by name, where value will be a set of (k,v) on action and price, something like below
(AAA=((action1, 5),(action31, 2)), BBB=(action13, 7))
so been trying as below, but I'm getting error -> "non-static method cannot be referred from static content" when trying to use Map.Entry
Stream<Object> trying = results
.stream()
.flatMap(it -> {
Map<String,String> innerMap = new HashMap<>();
innerMap.put(it.getAction(),it.getPrice());
Map<String,Map<String,String>> upperMap = new HashMap<>();
upperMap.put(it.getName(), innerMap);
return upperMap.entrySet().stream();
}
)
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
You could provide a Collector along with groupingBy to produce the inner map:
(Assume static import for Collectors.groupingBy and Collectors.toMap)
results.stream()
.collect(groupingBy(MyClass::getName,
toMap(MyClass::getAction,MyClass::getPrice)));
You need to process them in a complete oops way. Here is one clean, and readable way:-
public class Builder {
public static void main(String[] args) throws IOException {
List<MyClass> list = Arrays.asList(new MyClass("AAA", "action1", 5)
, new MyClass("BBB", "action13", 7), new MyClass("AAA", "action31", 2));
Map<String, List<Pairs>> listList = new HashMap<String, List<Pairs>>();
list.stream().map(l -> {
List<Pairs> pairs = listList.getOrDefault(l.name, new ArrayList<>());
pairs.add(new Pairs(l.action, l.price));
listList.put(l.name, pairs);
return pairs;
}).collect(Collectors.toList());
System.out.println(listList);
// {AAA=[Pairs{action='action1', price=5}, Pairs{action='action31', price=2}], BBB=[Pairs{action='action13', price=7}]}
}
}
class Pairs {
String action;
int price;
public Pairs(String action, int price) {
this.action = action;
this.price = price;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Pairs{");
sb.append("action='").append(action).append('\'');
sb.append(", price=").append(price);
sb.append('}');
return sb.toString();
}
}
class MyClass {
String name;
String action;
int price = 5;
public MyClass(String name, String action, int price) {
this.name = name;
this.action = action;
this.price = price;
}
}
My suggestion will be to try using a map of type Map<String, List<MyClass>>
this way it will be more manageable.
Here an idea how it can be coded.
import java.util.*;
public class MyClass {
String name;
String action;
int price;
public MyClass(String name, String action, int price) {
this.name = name;
this.action = action;
this.price = price;
}
#Override
public String toString() {
return "{" + name + ", " + action + ", " + price +"}";
}
public static void main(String[] args) {
Map<String, List<MyClass>> map = new HashMap<>();
MyClass[] objs = {
new MyClass("AAA", "action1", 5),
new MyClass("BBB", "action2", 7),
new MyClass("AAA", "action3", 2)
};
for(MyClass myClass: objs) {
if(!map.containsKey(myClass.name)) {
map.put(myClass.name, new ArrayList<MyClass>());
}
map.get(myClass.name).add(myClass);
}
System.out.println(map);
}
}
Why? Having a Map<String, Map<String, String>> is a typing disaster. Java is nominal; the language works better if you use what it's good at, which is not that.
Your problem description is ambiguous. Imagine the following input:
MyClass(name=AAA, action=action1, price=5),
MyClass(name=BBB, action=action13, price=7),
MyClass(name=AAA, action=action1, price=2)
What should the output be?
An exception
(AAA=((action1, 2)), BBB=(action13, 7))
(AAA=((action1, 5)), BBB=(action13, 7))
(AAA=((action1, [2, 5])), BBB=(action13, [7]))
That last one is somewhat sensible, because the others, particularly #2 and #3, are utterly arbitrary. Even if you want one of those, hopefully it helps you to realize that it won't be an easy, understandable series of functional operations to get there, and most likely you don't want to use streams at all - because not using them would lead to more readable code.
The signatures, in any case, do not match up with what you wrote; I assume you messed up (you seem to have set the type of the price variant as a String which seems a little crazy. In this code I'll represent it as an Integer, representing atomic units (eurocents, satoshis, etc).
If you really want this (And I highly doubt you do!), then let's break down the steps:
You first want to group by, giving you the name (AAA) as group key, and then a list of MyClass instances.
You then want to group-by again, grouping all MyClass instances with the same action name together, in order to end up with that [2, 5] integer list.
You also want to map your MyClass instance to just its price somewhere along this process.
Perusing the API of the stream stuff, specifically, Collectors, gives us the call you want:
groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.
So let's get to it, assuming you want the 4th option:
#lombok.Value public class MyClass {
String name, action;
int price;
}
List<MyClass> results = List.of(
new MyClass("AAA", "action1", 5),
new MyClass("BBB", "action2", 4),
new MyClass("AAA", "action1", 3),
new MyClass("AAA", "action3", 2));
Map<String, Map<String, List<MyClass>>> answer =
results.stream()
.collect(Collectors.groupingBy(MyClass::getName,
Collectors.groupingBy(MyClass::getAction)));
System.out.println(answer);
That's a good first step, but instead of a list of MyClass instances, you wanted just a List of integers. We can do that, too:
Map<String, Map<String, List<Integer>>> answer =
results.stream()
.collect(Collectors.groupingBy(MyClass::getName,
Collectors.groupingBy(MyClass::getAction,
Collectors.collectingAndThen(Collectors.toList(),
listOfMc -> listOfMc.stream().map(MyClass::getPrice).collect(Collectors.toList())
))));
And this gets you:
{AAA={action1=[5, 3], action3=[2]}, BBB={action2=[4]}}
Exactly what you wanted, I think.
But, then look back at that code and realize that somewhere, somehow, you made some wrong choices :P - that's quite a far sight removed from easily readable code!
I have the following code:
HashMap<String, HashSet<Person>> index = new HashMap<String, HashSet<Person>>();
public static void indexDB(String base)
{
for(Person i: listB)
{
if(name.equals(base))
{
}
}
listB is an array with Person elements.
So, if a Person's name matches the String base, they are getting attached to a pair of key-value in the index HashMap. The HashSet for each key contains the Persons that their name matches the String base. How can this be done?
Also, I have a method like:
public void printPersons(String sth)
{
}
that I want it to print the persons contained in the HashSet of the key called each time.
Thank you
Use putIfAbsent to insert an empty hash set place holder.
Then add new person to existing set:
HashMap<String, HashSet<Person>> index = new HashMap<String, HashSet<Person>>();
public static void indexDB(String base)
{
for(Person i: listB)
{
if(name.equals(base))
{
index.putIfAbsent(base, new HashSet<>());
index.get(base).add(i)
}
}
Note: In order to correctly add person to set, you have to implement equals()/hashCode() for your Person class, since Set use equals() to determine uniqueness
Instead of creating HashSet object in every iteration, create it only when name matches like in the below code -
public static void indexDB(String base)
{
for(Person i: listB)
{
if(index.containsKey(base)){
HashSet<Person> existingHS = index.get(base);
existingHS.add(i);
index.put(base,existingHS);
}else{
HashSet<Person> hs = new HashSet<Person>();
hs.add(i);
index.put(base,hs);
}
}
Do this
HashMap<String, HashSet<Person>> index = new HashMap<String, HashSet<Person>>();
public static void indexDB(String base)
{
HashSet<Person> h = new HashSet<String>();
for(Person i: listB)
{
//I assume it is i.name here
if(i.name.equals(base))
{
h.add(i);
}
}
index.put(base,h);
}
And for printing, do this
public void printPersons(String sth)
{
Map mp = index.get(sth);
Iterator it = mp.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
}
}
I have a class A having private members like below.
class A {
String type;
}
The above class will come as list objects List . Now i have Another class B
Class B {
String type1;
String type2;
String type3;
String type4;
String type5;
String type6;
String type7;
String type8;
}
So Now How i have to iterate through List and get the (type(s)) data one by one and then put it into Class B as single object like first one will go to Type1 , second one ---> type2, third one--- > type3 ....., so on.
Note : There will be only 8 types in class A as a list . so we will have exactly 8 types in Class B and i have thought on reflection so please tell how do i use it, if this is the only option.
Jasper supports Map, so I would suggest you can store the values in a SortedMap, such as a TreeMap rather than a class with public variables.
Then you would add your type names like this:
Map<String, String> b = new TreeMap<>();
b.put("type1", "Foo");
b.put("type2", "Bar");
b.put("type3", "Baz");
...
Or if you're populating from a list of A:
List<A> list = ...;
Map<String, String> b = new TreeMap<>();
for (int i = 0; i < list.size(); i++) {
b.put("type" + (i + 1), list.get(i).type;
}
To iterate over all the names in the map, you could then use:
for (Map.Entry<String, String> entry: b.entrySet()) {
String key = entry.key();
String value = entry.value();
System.out.println(key + " = " + value);
}
If you can change the class B as:
class B
{
String[] types;
}
Then we can do:
int i = 0;
for(A aObject:aList){
bObject.types[i] = aObject.type;
}
Else we can add all types like this:
bObject.type1 = aList.get(0).type;
bObject.type2 = aList.get(1).type;
and so on.
I've created a Map with ten people's details, I want to then print out the names.
At the moment I have to print out each one individually, I want to have about 200 people so I wanted to know if it would be possible to print the names out in a loop.
(simplified code)
public class PersonDetails implements Serializable {
private String name;
private int age;
...
public PersonDetails(int Age, String Name) {
name = Name;
age = Age;
...
}
}
public class Test implements Serializable {
Map<String, PersonDetails> people = new TreeMap<String, PersonDetails>();
public Test() {
//10 people added
for(int x = 0; x<10; x++){
addPerson();
}
PersonDetails p0 = people.get("0");
String p0name = p0.getName();
System.out.println(p0name);
PersonDetails p1 = people.get("1");
String p1name = p1.getName();
System.out.println(p1name);
PersonDetails p2 = people.get("2");
String p2name = p2.getName();
System.out.println(p2name);
PersonDetails p3 = people.get("3");
String p3name = p3.getName();
System.out.println(p3name);
...
(I would like to loop this)
}
OUTPUT:
Connor
Phil
Nick
Mike
Sarah
Tom
Jenny
Eric
Jerry
Dave
Is it possible to have the same output and loop these outputs? I have tried an ArrayLists but I can't get it to work with this problem, and I am not sure if it is possible.
Thanks
Yes it is:
for (PersonDetails p: people.keySet())
{
System.out.println(p.getName());
}
You should specify the types of the people member variable when declaring and initializing. I think the key is a String, given get("0"), so:
Map<String, PersonDetails> people = new TreeMap<String, PersonDetails>();
To use an ArrayList:
List<PersonDetails> people = new ArrayList<PersonDetails>();
people.add(new PersonDetails(42, "Martin"));
for (PersonDetails p: people)
{
System.out.println(p.getName());
}
Map<String, PersonDetails> people = new HashMap<String, PersonDetails>();
// add to the Map this way
people.put(person.getName(), person);
for (Person p : people.keySet()) {
System.out.print(String.format("%s ", p.getName());
}
System.out.println();
You get the idea.
I need to join two Lists in Java. I've a list which has a VO that has a name and description. I've another list which has same VO type that has name and Address. The "name" is same. I need to create a List with this VO which has both name, address and description.
The VO structure is
public class PersonDetails{
private String name;
private String description;
private String address;
//getters and setters
}
Can someone please suggest me the best way to implement it?
It depends:
If the lists contain both exactly the same data, you can sort them both by name, iterate over them en set the missing property.
If not, I would put the first list in a Map, with the name as key. Then iterate over the second list, look in the map for the VO and set the value.
After that, just get all the value's out of the map again as a List.
public List<Vo> merge(List<Vo> list1, List<Vo> list2) {
Map<String, Vo> tempMap = new HashMap<String, Vo>();
for (Vo v : list1) {
tempMap.put(v.name, v);
}
for (Vo vv : list2) {
//The if is in case the 2 lists aren't filled with the same objects
if (tempMap.containsKey(vv.name)) {
tempMap.get(vv.name).description = vv.description;
} else {
tempMap.put(vv.name, vv);
}
}
return new ArrayList<Vo>(tempMap.values());
}
If the lists contain both EXACT the same VO (equal by name), you can use this.
public List<Vo> merge(List<Vo> list1, List<Vo> list2) {
Collections.sort(list1, new Comparator<Vo>() {
public int compare(Vo o1, Vo o2) {
return o1.name.compareTo(o2.name);
}
});
Collections.sort(list2, new Comparator<Vo>() {
public int compare(Vo o1, Vo o2) {
return o1.name.compareTo(o2.name);
}
});
for(int i = 0; i < list1.size(); i++){
list1.get(i).description = list2.get(i).description;
}
return list1;
}
Put all elements of the first list in a map, then merge the contents of the second list into it:
final List<PersonDetails> listWithAddress =
new ArrayList<PersonDetails>();
final List<PersonDetails> listWithDescription =
new ArrayList<PersonDetails>();
// fill both lists with data
final Map<String, PersonDetails> map =
// map sorted by name, change to HashMap otherwise
// (or to LinkHashMap if you need to preserve the order)
new TreeMap<String, PersonDetails>();
for(final PersonDetails detailsWithAddress : listWithAddress){
map.put(detailsWithAddress.getName(), detailsWithAddress);
}
for(final PersonDetails detailsWithDescription : listWithDescription){
final PersonDetails retrieved =
map.get(detailsWithDescription.getName());
if(retrieved == null){
map.put(detailsWithDescription.getName(),
detailsWithDescription);
} else{
retrieved.setDescription(detailsWithDescription.getDescription());
}
}
I would put each list of the source VO lists in a map by name, create a set of both keys, iterate it and add a target VO to the result list.
In the example code, VO is the target VO, VOD is the source VO with description only, VOA is the source VO with address only:
List<VOD> descriptions = ...;
List<VOA> addresses = ...;
Map<String,String> description ByName = new HashMap<String,String>();
for (VOD description : descriptions) {
descriptionByName.put(description.name, description.description);
}
Map<String,String> addressByName = new HashMap<String,String>();
for (VOA address: addresses ) {
addressByName.put(address.name, address.address);
}
Set<String> allNames = new HashSet<String>();
allNames.addAll(descriptionByName.keySet());
allNames.addAll(addressByName.keySet());
List<VO> result = new ArrayList<VO>();
for (String name : allNames) {
VO one = new VO();
one.name = name;
one.address = addressByName.get(name)
one.description = descriptionByName.get(name)
result.add(one);
}
Go from list to HashMap, use the name string as key. So you can merge you data quite efficient.
List<VO> list = new ArrayList<VO>();
for (VO vo1 : vo1List) {
for (VO vo2 : vo2List) {
if (vo1.getName().equals(vo2.getName())) {
VO newVo = new VO();
newVO.setName(vo1.getName());
newVO.setDescription(vo1.getDescription());
newVO.setAddress(vo2.getAddress);
list.add(newVO);
break;
}
}
}
It's best that you sort both lists on name beforehand, it makes the double iteration faster.