Map a List to DTO inside map - java - java

I have such collection: Map<Integer, List<MyObject>> collection
I would like to map the whole list of MyObject to MyObjectDTO and return the whole map with the mapped list.
So return will be:
Map<Integer, List<MyObjectDto>> collectionWithDtos
What is the easiest and quickest way? I've checked a couple of ways with streams but none of that produced results as I expected.
Thanks

This is a way to go with the following simple call:
Map<Integer, List<MyObjectDto>> mappedCollection = collection.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue()
.stream()
.map(myObject -> new MyObjectDto()) // mapping here
.collect(Collectors.toList())));
Basically, you want to collect it into the same-structured map with the same key. Stream the set of entries Set<Map.Entry<Integer, List<MyObject>>> and map it into a new map using Collectors.toMap(Function, Function) where:
Key is the same key: entry -> entry.getKey()
Value is the same value (List), except all MyObject objects are mapped into MyObjectDto, which can be performed with another stream.
As long as we don't know the structures of the objects to be mapped, you have to add it by yourself to the line with a comment.

With a for-loop and streams:
private Map<Integer, List<MyObjectDto>> mapNestedListToDto(Map<Integer, List<MyObject>> collection) {
Map<Integer, List<Dto>> collectionWithDtos = new HashMap<>();
for (Integer i : collection.keySet()) {
List<Dto> dtos = collection.get(i).stream().map(myObject -> mappingFunction(myObject)).collect(Collectors.toList());
mapped.put(i, dtos);
}
return collectionWithDtos;
}
Where mappingFunction() is the method that will actually convert an instance of MyObject to an instance of MyObjectDTO.

You can create a new HashMap where you will put the old keys with a new values ( values will be the Dto created based on the original objects )
class Person {
String name ;
public Person(String s) {
this.name=s;
}
#Override
public String toString() {
// TODO Auto-generated method stub
return name;
}
}
class PersonDto {
String name;
public PersonDto(Person p) {
this.name=p.name+"Dto";
}
#Override
public String toString() {
// TODO Auto-generated method stub
return name;
}
}
public class Main {
public static void main(String[] args) {
// your initial HashMap
Map<Integer, List<Person>> map = new HashMap<>();
Person x =new Person("X");
Person y =new Person("Y");
List<Person> lst1 = new ArrayList<>();
lst1.add(x);
List<Person> lst2 = new ArrayList<>();
lst2.add(y);
map.put(1, lst1);
map.put(2, lst2);
// create a new HashMap<Integer, List<MyObjectDto>>
Map<Integer, List<PersonDto>> mapDto = new HashMap<>();
// Simply use forEach
// in the next line instead of "new PersonDto(e)" you will put your mapping method
map.forEach((k,v)->{
mapDto.put(k, v.stream().map(e-> new PersonDto(e)).collect(Collectors.toList()));
});
System.out.println(map);
System.out.println(mapDto);
}
}
Output :
{1=[X], 2=[Y]}
{1=[XDto], 2=[YDto]}

Related

How can I sort a Map<String, List<CustomObject>>?

I have done a lot of research for this question, but I have not found a way to sort a map of custom object lists (Map<String, List<CustomObj>>), basing the comparison on CustomObj attributes (as SORT_BY_NAME, SORT_BY_DATE, etc).
A motivating example of my question is:
I have a custom object: Person (with attribute as Name, DateOfBith, etc ...);
I have a Map of Person object List as: Map<String, List<Person>>. The map key is a String used for other purposes;
I would like to create a comparator and a sorting method that sorts the map in ascending order based on comparisons between the attributes of the Person object (name, date, etc ..)
For simplicity I report the real code but adapted to a simplified case of Person object, because it would already represent the concept of entity.
Person.java -> Custom object
public class Person {
private String name;
private Date dateOfBirth;
...
// Empty and Full attrs Constructors
...
// Getter and Setter
...
// Comparator by name
public static Comparator<Person> COMPARE_BY_NAME = Comparator.comparing(one -> one.name);
// Comparator by date
public static Comparator<Person> COMPARE_BY_DATE = Comparator.comparing(one -> one.dateOfBirth);
}
Sorter.java -> Sorter object
public class Sorter {
// List Comparator of Person by Date
public static final Comparator<? super List<Person>> COMPARATOR_BY_DATE = (Comparator<List<Person>>) (p1, p2) -> {
for (Persontab person1: p1) {
for (Person person2: p2) {
return Person.COMPARE_BY_DATE.compare(person1, person2);
}
}
return 0;
};
// List Comparator of Person by Name
public static final Comparator<? super List<Person>> COMPARATOR_BY_NAME = (Comparator<List<Person>>) (p1, p2) -> {
for (Persontab person1: p1) {
for (Person person2: p2) {
return Person.COMPARE_BY_NAME.compare(person1, person2);
}
}
return 0;
};
// Sorting method
public Map<String, List<Person>> sort(Map<String, List<Person>> map, Comparator<? super List<Person>> comparator) {
return map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(comparator))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new));
}
}
Main.java -> Start code
public class MainApp {
public static void main(String[] args) {
Map<String, List<Person>> exampleMap = new HashMap<>();
List<Person> personList = new ArrayList<>();
personList.add(new Person("name1", new Date("2022-01-01")));
personList.add(new Person("name12", new Date("2022-01-05")));
personList.add(new Person("name13", new Date("2022-01-03")));
map.put("2022-01", personList);
personList.clear();
personList.add(new Person("name14", new Date("2021-02-01")));
personList.add(new Person("name3", new Date("2021-02-05")));
personList.add(new Person("name4", new Date("2021-02-03")));
map.put("2021-02", personList);
Sorter sorter = new Sorter();
// Example of sorting by date
map = sorter.sort(exampleMap, Sorter.COMPARATOR_BY_DATE);
// In this case the sorting works correctly, or rather it sorts the items by date as I expect
// Example of sorting by name
map = sorter.sort(exampleMap, Sorter.COMPARATOR_BY_NAME);
// In this case, I don't think sorting works correctly. Sort each list of elements for each key in ascending order. But it doesn't sort the map elements.
/* I expect to have the following map when sort by date:
"2021-02": [
Person("name14", new Date("2021-02-01")),
Person("name4", new Date("2021-02-03")),
Person("name3", new Date("2021-02-05"))
],
"2022-01": [
Person("name14", new Date("2021-02-01")),
Person("name13", new Date("2022-01-03")),
Person("name12", new Date("2022-01-05"))
]
}
}
First, let's reiterate: a HashMap is unordered so you need something else. Your Sorter.sort() method actually collects the values into a LinkedHashMap which provides an iteration order based on insert order and would be ok for your use case. Just to be clear (also for the sake of others): this doesn't sort the map itself but creates a new LinkedHashMap.
Now to your comparators: if you want to compare 2 lists you probably want to compare elements at equal indices. Thus your comparator needs to be something like this:
Comparator<List<Person>> = (l1, l2) -> {
Iterator<Person> itr1 = l1.iterator();
Iterator<Person> itr2 = l2.iterator();
while( itr1.hasNext() && itr2.hasNext() ) {
Person p1 = itr1.next();
Person p2 = itr1.next();
int result = Person.COMPARE_BY_DATE.compare(p1, p2);
if( result != 0 ) {
return result;
}
}
return 0;
};
However, the lists might have different lengths as well so you might want to handle that too:
Comparator<List<Person>> = (l1, l2) -> {
//iterators and loop here
//after the loop it seems all elements at equal indices are equal too
//now compare the sizes
return Integer.compare(l1.size(), l2.size());
}
By changing the type of Map used in my code above, as suggested by #Thomas, in TreeMap<>, I found the solution to the problem as follows:
I first merged all the Person object lists into one list. This was then sorted by the chosen criterion, for example Person.COMPARE_BY_NAME;
I created an algorithm that would re-group the sorted lists, in according to the criteria of my project, in a map. The key of this map corresponds to the concatenation of the month + year of the Person object.
The algorithm is reported at the bottom of the comment;
I sort the map based on the chosen attribute, for example Sorter.COMPARATOR_BY_NAME;
Di seguito il codice รจ come segue:
Merge all List<Person> in one -> main or somewhere before the Map was created
...
//
List<Person> newPersonList = new ArrayList<>();
newPersonList.addAll(oldPersonList1);
newPersonList.addAll(oldPersonList2);
...
Main or somewhere before the Map was created
...
groupList(Person.COMPARE_BY_NAME, Sorter.COMPARATOR_BY_NAME);
...
GroupPerson -> method to group the merged List<Person> in a TreeMap<String, List<Person>>
public Map<String, List<Person>> groupList(final Comparator<? super Person> itemComparator, final Comparator<? super List<Person>> listComparator)
// Sort Person list by comparator before create TreeSet
newPersonList.sort(itemComparator);
Map<String, List<Person>> personMapGrouped = new TreeMap<>();
// Here, create a Map of list
for (Person person: newPersonList) {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MM", Locale.getDefault());
final String groupKey = dateFormat.format(person.getDateOfBirth());
if (personMapGrouped.containsKey(groupKey)) {
// The key is already in the TreeMap; add the Person object against the existing key.
final List<Person> personListGrouped = personMapGrouped.get(groupKey);
if (personListGrouped!= null) {
personListGrouped.add(person);
}
} else {
// The key is not there in the TreeMap; create a new key-value pair
final List<Person> personListGrouped = new ArrayList<>();
personListGrouped.add(person);
personMapGrouped.put(groupKey, personListGrouped);
}
}
// Here sort the Map by params passed
final TabPersonSorter sorter = new TabPersonSorter();
personMapGrouped = sorter.sort(personMapGrouped, listComparator);
}
In this case, using the lists created in the main above, the results obtained are:
"List<Person> mergedList": [
Person("name1", new Date("2022-01-01")),
Person("name3", new Date("2021-02-05")),
Person("name4", new Date("2021-02-03")),
Person("name12", new Date("2022-01-05")),
Person("name13", new Date("2022-01-03")),
Person("name14", new Date("2021-02-01"))
]
"Map<String, List<Person>> mergedMap": {
"2022-01": [
Person("name1", new Date("2022-01-01")),
Person("name12", new Date("2022-01-05")),
Person("name13", new Date("2022-01-03"))
],
"2021-02": [
Person("name3", new Date("2021-02-05")),
Person("name4", new Date("2021-02-03"))
],
"2022-02": [
Person("name14", new Date("2021-02-01"))
]
}
Obviously if the grouping in the map were not bound by such a restrictive date as only year + month, the sorting would have the desired effect in distinct groups.
In fact, in the case of the sorting by date, this is respected very well.

Merge list of Objects by property

I have been searching around different groupBy and stream threads but cannot find the answer to my problem. Basically I have this object:
public class Object {
string name;
string type;
}
And I return a list of these from the database. What I would like to then do is iterate through the list of objects and remove duplicate names and save a list of the second properties under one object, in a new object that looks like this:
public class NewObject {
String name;
List<String> types;
}
You can do it like this.
use groupingBy to create a map of name, List of type
use the entryset of the map to create the new object.
I added the appropriate constructors and getters in the classes.
List<OldObject> list = ...
List<NewObject> newList = list.stream()
.collect(Collectors.groupingBy(OldObject::getName,
Collectors.mapping(OldObject::getType,
Collectors.toList())))
.entrySet().stream()
.map(e -> new NewObject(e.getKey(), e.getValue()))
.toList();
}
class OldObject {
String name;
String type;
public String getName() {
return name;
}
public String getType() {
return type;
}
}
class NewObject {
String name;
List<String> types = new ArrayList<>();
public NewObject(String name, List<String> types) {
this.types = types;
this.name = name;
}
}
For your added enjoyment, I thought I would also offer the following:
create the map
conditionally create a new object if the name key is not present
in either case, add the type to the list instance of the NewObject instance in the map which is returned by the computeIfAbsent method.
When finished, just assign the values to your Collection.
Map<String, NewObject> map = new HashMap<>();
for (OldObject ob : list) {
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.add(ob.getType());
}
Collection<NewObject> newLista = map.values();
Caveats:
values returns a Collection, not a list so you would need to use that or pass the Collection to a list constructor of some sort (e.g. ArrayList).
the requires the addition of a add method in the NewObject class.
you could also have a getter that returns the type list directly and do.
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.getTypeList().add(ob.getType());
Check out these additions to the Map interface
I understand you don't want to remove duplicates but actually merge them.
I replaced your Object class name with OldObject to avoid confusion with the actual Java Object class.
Collecting to Map<String, List<String>> and then converting to List<NewObject>
You could write your own Collector (see later in the answer), but the easiest way of writing what you need would be to use the current groupingBy and toList Collectors and then using the resulting Map and create your newObject instance based on it to then again collect to a new List:
oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collectors.mapping(OldObject::getType, Collectors.toList())))
.entrySet().stream()
// Assuming a NewObject constructor that receives a String name and a List<String> types
.map(e -> new NewObject(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Custom Collectors
If you are interested in learning how to make your own Collector, I made an example using a custom Collector as well:
oldObjectList.stream().collect(Collector.of(
// Supplier
() -> new ConcurrentHashMap<String, NewObject>(),
// Accumulator
(map, oldObject) -> {
// Assuming a NewObject constructor that gets a name and creates a new empty List as types.
map.computeIfAbsent(oldObject.getName(), name -> new NewObject(name)).getTypes().add(oldObject.getType());
},
// Combiner
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, (v1, v2) -> {
v1.getTypes().addAll(v2.getTypes());
return v1;
}));
return map1;
},
// Finisher
map -> new ArrayList<>(map.values())
));
You can even combine groupingBy and a custom Collector as a merge function:
new ArrayList<>(oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collector.of(
// Supplier
// Assuming a NewObject constructor with no arguments that creates a new empty List as types.
NewObject::new,
// Accumulator
(newObject, oldObject) -> {
newObject.setName(oldObject.getName());
newObject.getTypes().add(oldObject.getType());
},
// Combiner
(newObject1, newObject2) -> {
newObject1.getTypes().addAll(newObject2.getTypes());
return newObject1;
}
))).values());
It may be possible to use Collectors.toMap to create NewObject instances immediately and append types as necessary within the merge function.
Assuming that there NewObject has custom constructor and overridden toString method:
class NewObject {
String name;
List<String> types = new ArrayList<>();
public NewObject(String name, String type) {
this.name = name;
this.types.add(type);
}
#Override
public String toString() {
return "{ name: " + name + "; types: " + types + "}";
}
}
and that there are helper methods to create an instance of NewObject from OldObject and merge types of two NewObject instances:
// MyClass
public static NewObject createNewObject(OldObject oo) {
return new NewObject(oo.getName(), oo.getType());
}
public static NewObject mergeTypes(NewObject no1, NewObject no2) {
no1.getTypes().addAll(no2.getTypes());
return no1;
}
The transformation may be implemented as follows:
List<OldObject> input = Arrays.asList(
new OldObject("n1", "type1"), new OldObject("n1", "type2"),
new OldObject("n2", "type1"), new OldObject("n3", "type2"),
new OldObject("n1", "type3"), new OldObject("n2", "type2")
);
List<NewObject> result = new ArrayList<>(
input
.stream()
.collect(Collectors.toMap(
OldObject::getName,
MyClass::createNewObject,
MyClass::mergeTypes,
LinkedHashMap::new // keep insertion order
))
.values());
result.forEach(System.out::println);
Output:
{ name: n1; types: [type1, type2, type3]}
{ name: n2; types: [type1, type2]}
{ name: n3; types: [type2]}

creating Map from List is not giving expected result

I have the two list objects as shown below, from which i'm creating the map object.
List<Class1> list1;
List<Class2> list2;
HashMap<String,String> map = new HashMap<>();
for(Class1 one : list1){
if(one.isStatus()){
map.put(one.getID(),one.getName());
}
}
//iterating second list
for(Class2 two : list2){
if(two.isPerformed()){
map.put(two.getID(),two.getName());
}
}
The above code works fine , want the above to be written using streams.
Below is the sample code using streams().
map = list1.stream().filter(one.isStatus()).collect(toMap(lst1 -> lst1.getID(), lst1.getName());
map = list2.stream().filter(...);
But the "map" is not giving the expected result when written using stream() API.
Stream concatenation Stream.concat may be applied here to avoid map.putAll
Map<String, String> map = Stream.concat(
list1.stream()
.filter(Class1::isStatus)
.map(obj -> Arrays.asList(obj.getID(), obj.getName())),
list2.stream()
.filter(Class2::isPerformed)
.map(obj -> Arrays.asList(obj.getID(), obj.getName()))
) // Stream<List<String>>
.collect(Collectors.toMap(
arr -> arr.get(0), // key - ID
arr -> arr.get(1),
(v1, v2) -> v1 // keep the first value in case of possible conflicts
));
The code above uses a merge function (v1, v2) -> v1 to handle possible conflicts when the same ID occurs several times in list1 and/or list2 to keep the first occurrence.
However, the following merge function allows joining all the occurrences into one string value (v1, v2) -> String.join(", ", v1, v2).
I'm not sure what expected result you're not seeing but I created a minimal working example that you should be able to adapt for your own use case.
public class Main {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
Map<Integer, String> personMap = personList.stream()
.filter(Person::isStatus)
.collect(Collectors.toMap(person -> person.id, person -> person.name));
}
private static class Person {
public String name;
public int id;
public boolean isStatus() {
return true;
}
}
}
Try this,
List<Class1> list1;
List<Class2> list2;
Map<String, String> map1 = list1.stream().filter(Class1::isStatus).collect(Collectors.toMap(Class1::getId, Class1::getName));
Map<String, String> map2 = list2.stream().filter(Class2::isPerformed).collect(Collectors.toMap(Class2::getId, Class2::getName));
map1.putAll(map2);

Hashmap not returning all values [duplicate]

Is it possible for us to implement a HashMap with one key and two values. Just as HashMap?
Please do help me, also by telling (if there is no way) any other way to implement the storage of three values with one as the key?
You could:
Use a map that has a list as the value. Map<KeyType, List<ValueType>>.
Create a new wrapper class and place instances of this wrapper in the map. Map<KeyType, WrapperType>.
Use a tuple like class (saves creating lots of wrappers). Map<KeyType, Tuple<Value1Type, Value2Type>>.
Use mulitple maps side-by-side.
Examples
1. Map with list as the value
// create our map
Map<String, List<Person>> peopleByForename = new HashMap<>();
// populate it
List<Person> people = new ArrayList<>();
people.add(new Person("Bob Smith"));
people.add(new Person("Bob Jones"));
peopleByForename.put("Bob", people);
// read from it
List<Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs[0];
Person bob2 = bobs[1];
The disadvantage with this approach is that the list is not bound to exactly two values.
2. Using wrapper class
// define our wrapper
class Wrapper {
public Wrapper(Person person1, Person person2) {
this.person1 = person1;
this.person2 = person2;
}
public Person getPerson1() { return this.person1; }
public Person getPerson2() { return this.person2; }
private Person person1;
private Person person2;
}
// create our map
Map<String, Wrapper> peopleByForename = new HashMap<>();
// populate it
peopleByForename.put("Bob", new Wrapper(new Person("Bob Smith"),
new Person("Bob Jones"));
// read from it
Wrapper bobs = peopleByForename.get("Bob");
Person bob1 = bobs.getPerson1();
Person bob2 = bobs.getPerson2();
The disadvantage to this approach is that you have to write a lot of boiler-plate code for all of these very simple container classes.
3. Using a tuple
// you'll have to write or download a Tuple class in Java, (.NET ships with one)
// create our map
Map<String, Tuple2<Person, Person> peopleByForename = new HashMap<>();
// populate it
peopleByForename.put("Bob", new Tuple2(new Person("Bob Smith",
new Person("Bob Jones"));
// read from it
Tuple<Person, Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs.Item1;
Person bob2 = bobs.Item2;
This is the best solution in my opinion.
4. Multiple maps
// create our maps
Map<String, Person> firstPersonByForename = new HashMap<>();
Map<String, Person> secondPersonByForename = new HashMap<>();
// populate them
firstPersonByForename.put("Bob", new Person("Bob Smith"));
secondPersonByForename.put("Bob", new Person("Bob Jones"));
// read from them
Person bob1 = firstPersonByForename["Bob"];
Person bob2 = secondPersonByForename["Bob"];
The disadvantage of this solution is that it's not obvious that the two maps are related, a programmatic error could see the two maps get out of sync.
No, not just as a HashMap. You'd basically need a HashMap from a key to a collection of values.
If you're happy to use external libraries, Guava has exactly this concept in Multimap with implementations such as ArrayListMultimap, HashMultimap, LinkedHashMultimap etc.
Multimap<String, Integer> nameToNumbers = HashMultimap.create();
System.out.println(nameToNumbers.put("Ann", 5)); // true
System.out.println(nameToNumbers.put("Ann", 5)); // false
nameToNumbers.put("Ann", 6);
nameToNumbers.put("Sam", 7);
System.out.println(nameToNumbers.size()); // 3
System.out.println(nameToNumbers.keySet().size()); // 2
Another nice choice is to use MultiValuedMap from Apache Commons. Take a look at the All Known Implementing Classes at the top of the page for specialized implementations.
Example:
HashMap<K, ArrayList<String>> map = new HashMap<K, ArrayList<String>>()
could be replaced with
MultiValuedMap<K, String> map = new MultiValuedHashMap<K, String>();
So,
map.put(key, "A");
map.put(key, "B");
map.put(key, "C");
Collection<String> coll = map.get(key);
would result in collection coll containing "A", "B", and "C".
Take a look at Multimap from the guava-libraries and its implementation - HashMultimap
A collection similar to a Map, but which may associate multiple values with a single key. If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.
I use Map<KeyType, Object[]> for associating multiple values with a key in a Map. This way, I can store multiple values of different types associated with a key. You have to take care by maintaining proper order of inserting and retrieving from Object[].
Example:
Consider, we want to store Student information. Key is id, while we would like to store name, address and email associated to the student.
//To make entry into Map
Map<Integer, String[]> studenMap = new HashMap<Integer, String[]>();
String[] studentInformationArray = new String[]{"name", "address", "email"};
int studenId = 1;
studenMap.put(studenId, studentInformationArray);
//To retrieve values from Map
String name = studenMap.get(studenId)[1];
String address = studenMap.get(studenId)[2];
String email = studenMap.get(studenId)[3];
HashMap<Integer,ArrayList<String>> map = new HashMap<Integer,ArrayList<String>>();
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
list.add("xyz");
map.put(100,list);
If you use Spring Framework. There is: org.springframework.util.MultiValueMap.
To create unmodifiable multi value map:
Map<String,List<String>> map = ...
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
Or use org.springframework.util.LinkedMultiValueMap
The easiest way would be to use a google collection library:
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
public class Test {
public static void main(final String[] args) {
// multimap can handle one key with a list of values
final Multimap<String, String> cars = ArrayListMultimap.create();
cars.put("Nissan", "Qashqai");
cars.put("Nissan", "Juke");
cars.put("Bmw", "M3");
cars.put("Bmw", "330E");
cars.put("Bmw", "X6");
cars.put("Bmw", "X5");
cars.get("Bmw").forEach(System.out::println);
// It will print the:
// M3
// 330E
// X6
// X5
}
}
maven link: https://mvnrepository.com/artifact/com.google.collections/google-collections/1.0-rc2
more on this: http://tomjefferys.blogspot.be/2011/09/multimaps-google-guava.html
Just for the record, the pure JDK8 solution would be to use Map::compute method:
map.compute(key, (s, strings) -> strings == null ? new ArrayList<>() : strings).add(value);
Such as
public static void main(String[] args) {
Map<String, List<String>> map = new HashMap<>();
put(map, "first", "hello");
put(map, "first", "foo");
put(map, "bar", "foo");
put(map, "first", "hello");
map.forEach((s, strings) -> {
System.out.print(s + ": ");
System.out.println(strings.stream().collect(Collectors.joining(", ")));
});
}
private static <KEY, VALUE> void put(Map<KEY, List<VALUE>> map, KEY key, VALUE value) {
map.compute(key, (s, strings) -> strings == null ? new ArrayList<>() : strings).add(value);
}
with output:
bar: foo
first: hello, foo, hello
Note that to ensure consistency in case multiple threads access this data structure, ConcurrentHashMap and CopyOnWriteArrayList for instance need to be used.
Yes and no. The solution is to build a Wrapper clas for your values that contains the 2 (3, or more) values that correspond to your key.
Yes, this is frequently called a multimap.
See: http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Multimap.html
Using Java Collectors
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
where Department is your key
String key= "services_servicename"
ArrayList<String> data;
for(int i = 0; i lessthen data.size(); i++) {
HashMap<String, String> servicesNameHashmap = new HashMap<String, String>();
servicesNameHashmap.put(key,data.get(i).getServiceName());
mServiceNameArray.add(i,servicesNameHashmap);
}
I have got the Best Results.
You just have to create new HashMap like
HashMap<String, String> servicesNameHashmap = new HashMap<String, String>();
in your for loop. It will have same effect like same key and multiple values.
import java.io.*;
import java.util.*;
import com.google.common.collect.*;
class finTech{
public static void main(String args[]){
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("1","11");
multimap.put("1","14");
multimap.put("1","12");
multimap.put("1","13");
multimap.put("11","111");
multimap.put("12","121");
System.out.println(multimap);
System.out.println(multimap.get("11"));
}
}
Output:
{"1"=["11","12","13","14"],"11"=["111"],"12"=["121"]}
["111"]
This is Google-Guava library for utility functionalities. This is the required solution.
I could not post a reply on Paul's comment so I am creating new comment for Vidhya here:
Wrapper will be a SuperClass for the two classes which we want to store as a value.
and inside wrapper class, we can put the associations as the instance variable objects for the two class objects.
e.g.
class MyWrapper {
Class1 class1obj = new Class1();
Class2 class2obj = new Class2();
...
}
and in HashMap we can put in this way,
Map<KeyObject, WrapperObject>
WrapperObj will have class variables: class1Obj, class2Obj
You can do it implicitly.
// Create the map. There is no restriction to the size that the array String can have
HashMap<Integer, String[]> map = new HashMap<Integer, String[]>();
//initialize a key chosing the array of String you want for your values
map.put(1, new String[] { "name1", "name2" });
//edit value of a key
map.get(1)[0] = "othername";
This is very simple and effective.
If you want values of diferent classes instead, you can do the following:
HashMap<Integer, Object[]> map = new HashMap<Integer, Object[]>();
Can be done using an identityHashMap, subjected to the condition that the keys comparison will be done by == operator and not equals().
I prefer the following to store any number of variables without having to create a separate class.
final public static Map<String, Map<String, Float>> myMap = new HashMap<String, Map<String, Float>>();
I am so used to just doing this with a Data Dictionary in Objective C. It was harder to get a similar result in Java for Android. I ended up creating a custom class, and then just doing a hashmap of my custom class.
public class Test1 {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addview);
//create the datastring
HashMap<Integer, myClass> hm = new HashMap<Integer, myClass>();
hm.put(1, new myClass("Car", "Small", 3000));
hm.put(2, new myClass("Truck", "Large", 4000));
hm.put(3, new myClass("Motorcycle", "Small", 1000));
//pull the datastring back for a specific item.
//also can edit the data using the set methods. this just shows getting it for display.
myClass test1 = hm.get(1);
String testitem = test1.getItem();
int testprice = test1.getPrice();
Log.i("Class Info Example",testitem+Integer.toString(testprice));
}
}
//custom class. You could make it public to use on several activities, or just include in the activity if using only here
class myClass{
private String item;
private String type;
private int price;
public myClass(String itm, String ty, int pr){
this.item = itm;
this.price = pr;
this.type = ty;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getType() {
return item;
}
public void setType(String type) {
this.type = type;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
We can create a class to have multiple keys or values and the object of this class can be used as a parameter in map.
You can refer to https://stackoverflow.com/a/44181931/8065321
Apache Commons collection classes can implement multiple values under same key.
MultiMap multiMapDemo = new MultiValueMap();
multiMapDemo .put("fruit", "Mango");
multiMapDemo .put("fruit", "Orange");
multiMapDemo.put("fruit", "Blueberry");
System.out.println(multiMapDemo.get("fruit"));
Maven Dependency
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>

Sort a list of maps by value in java

I have a list of maps. Which I cannot predict the key of each map. key can be an any two digits number which will change acordingly to the request.But values contains unique values from request to request.Now I need to sort this list of maps according to the value of each key. I've already tried something like bellow,
List<Map<String,String>> listOfMaps = new ArrayList<>();
Map<String,String> map1 = new HashMap<>();
Map<String,String> map2 = new HashMap<>();
map1.put("key1","value1");
map2.put("key2","value1");
listOfMaps.add(map1);
listOfMaps.add(map2);
sort(listOfMaps);
Collections.sort(listOfMaps, new Comparator<Map<String, String>>() {
public int compare(final Map<String, String> o1, final Map<String, String> o2) {
return o1.get("key").compareTo(o2.get("key"));
}
});
Since "key" can be differ from map to map, seems I couldnt use the above code.
I've also tried these examples.
How to Sort a List of Maps by two Categories?
Sorting list of maps based on a value
Since I'm cannot predict the key, I couldnt find an answer from these post.Can any one help with this?.
The below example should work, under the assumption that each map element contains only one entry:
List<Map<String, String>> list = new ArrayList<>();
//Add entries
Collections.sort(list, new Comparator<Map<String, String>>() {
public int compare(Map<String, String> o1, Map<String, String> o2) {
Collection<String> values1 = o1.values();
Collection<String> values2 = o2.values();
if(!values1.isEmpty() && !values2.isEmpty()){
return values1.iterator().next().compareTo(values2.iterator().next());
}else{
return 0;
}
}
});
While it is occasionally necessary to create a Map with only one key and one value (Collections.singletonMap is used for this), it doesn't look like Map is appropriate for your situation. It makes much more sense to use a custom class with two fields, something like this:
// This is immutable. You may prefer to make fields non-final and add setters.
// You may also want to override equals() and hashCode().
final class StringPair {
private final String key;
private final String value;
StringPair(String key, String value) {
this.key = key;
this.value = value;
}
#Override
public String toString() {
return "[" + key + ", " + value + "]";
}
}
Then you can do:
List<StringPair> list = new ArrayList<>();
list.add(new StringPair("K2", "V2"));
list.add(new StringPair("K1", "V1"));
Collections.sort(list, new Comparator<StringPair>()
#Override
public int compare(StringPair o1, StringPair o2) {
return o1.value.compareTo(o2.value);
}
});
System.out.println(list); // Prints [[K1, V1], [K2, V2]]

Categories

Resources