How to collect a stream into a TreeMap - java

I know this is a noob a question, but I couldn't find a simpe answer anywhere else. Question is: I need to write a method that returns a SortedMap, so a tree map should work just fine.
I have a HashMap< String, Skill>, the Skill class has both the methods getName and getNumApplicants and I need to return a SortedMap<String, Long>, with the name of the skill as a key and the number of applicants as value. This is where I stand:
private Map<String,Skill> skillMap = new HashMap<>();
public SortedMap<String, Long> skill_nApplicants() {
return skillMap.values().stream().collect(...);
}
This is the Skill class
public class Skill {
private String name;
private List <Position> reqPosition = new ArrayList<>();
private Long numApplicants;
public void plusOneApplicant() {
this.numApplicants++;
}
public Long getNumApplicants() {
return numApplicants;
}
public Skill(String name) {
super();
this.name = name;
this.numApplicants = 0L;
}
public String getName() {
return name;
}
public List<Position> getPositions() {
return reqPosition;
}
public void addReqPosition(Position p) {
this.reqPosition.add(p);
return;
}
}
I know this should be very easy, I just have a very hard time in understanding this all thing.

Don't collect the data to a HashMap first, then convert to a TreeMap. Collect the data directly to a TreeMap by using the overloaded toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) method that allows you to specify which Map to create (4th parameter).
public SortedMap<String, Long> skill_nApplicants() {
return skillMap.values().stream().collect(Collectors.toMap(
Skill::getName,
Skill::getNumApplicants,
Math::addExact, // only called if duplicate names can occur
TreeMap::new
));
}

This is how you can do it
public SortedMap<String, Long> skill_nApplicants(Map<String, Skill> skillMap) {
Map<String, Long> result = skillMap.values().stream().collect(Collectors.toMap(Skill::getName, Skill::getNumApplicants));
return new TreeMap<>(result);
}

If, in your stream, you don't have two (or more) values which should be mapped with the same key, then you can avoid to use a Collector at all (and thus you don't need to think about a merge function).
All you need to do is to simply add each skill to the map with a forEach:
public SortedMap<String, Long> skill_nApplicants() {
Map<String, Long> result = new TreeMap<>();
skillMap.values()
.forEach((skill) -> result.put(skill.getName(), skill.getNumApplicants());
return result;
}
You can wrap result with Collections.unmodifiableSortedMap if you want to return an unmodifiable map.

Related

Java streams: Add to map but avoid mutation

I often find myself in a situation where I need to create a Map of objects from a Set or List.
The key is usually some String or Enum or the like, and the value is some new object with data lumped together.
The usual way of doing this, for my part, is by first creating the Map<String, SomeKeyValueObject> and then iterating over the Set or List I get in and mutate my newly created map.
Like the following example:
class Example {
Map<String, GroupedDataObject> groupData(final List<SomeData> list){
final Map<String, GroupedDataObject> map = new HashMap<>();
for(final SomeData data : list){
final String key = data.valueToGroupBy();
map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data()));
}
return map;
}
}
class SomeData {
private final String valueToGroupBy;
private final Object data;
private final String displayName;
public SomeData(final String valueToGroupBy, final String displayName, final Object data) {
this.valueToGroupBy = valueToGroupBy;
this.data = data;
this.displayName = displayName;
}
public String valueToGroupBy() {
return valueToGroupBy;
}
public Object data() {
return data;
}
public String displayName() {
return displayName;
}
}
class GroupedDataObject{
private final String key;
private final List<Object> datas;
private GroupedDataObject(final String key, final List<Object> list) {
this.key = key;
this.datas = list;
}
public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) {
final List<Object> list = new ArrayList<>();
if(groupedDataObject != null){
list.addAll(groupedDataObject.datas());
}
list.add(data);
return new GroupedDataObject(key, list);
}
public String key() {
return key;
}
public List<Object> datas() {
return datas;
}
}
This feels very unclean. We create a map, and then mutate it over and over.
I've taken a liking to java 8s use of Streams and creating non-mutating data structures (or rather, you don't see the mutation). So is there a way to turn this grouping of data into something that uses a declarative approach rather than the imperative way?
I tried to implement the suggestion in https://stackoverflow.com/a/34453814/3478016 but I seem to be stumbling. Using the approach in the answer (the suggestion of using Collectors.groupingBy and Collectors.mapping) I'm able to get the data sorted into a map. But I can't group the "datas" into one and the same object.
Is there some way to do it in a declarative way, or am I stuck with the imperative?
You can use Collectors.toMap with a merge function instead of Collectors.groupingBy.
Map<String, GroupedDataObject> map =
list.stream()
.collect(Collectors.toMap(SomeData::valueToGroupBy,
d -> {
List<Object> l = new ArrayList<>();
l.add(d.data());
return new GroupedDataObject(d.valueToGroupBy(), l);
},
(g1,g2) -> {
g1.datas().addAll(g2.datas());
return g1;
}));
The GroupedDataObject constructor must be made accessible in order for this to work.
If you avoid the GroupedDataObject and simply want a map with a key and a list you can use Collectors.groupingBy that you have been looking into.
Collectors.groupingBy will allow you to do this:
List<SomeObject> list = getSomeList();
Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod));
This will require SomeKey to have proper implementations of equals and hashValue
Sometimes streams are not the way to go. I believe this is one of those times.
A little refactoring using merge() gives you:
Map<String, MyTuple> groupData(final List<SomeData> list) {
Map<String, MyTuple> map = new HashMap<>();
list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()),
(a, b) -> {a.addAll(b.getDatas()); return a;});
Assuming a reasonable class to hold your stuff:
class MyTuple {
String displayName;
List<Object> datas = new ArrayList<>();
// getters plus constructor that takes 1 data and adds it to list
}

Java 8 Stream API toMap converting to TreeMap

public class Message {
private int id;
private User sender;
private User receiver;
private String text;
private Date senddate;
..
}
I have
List<Message> list= new ArrayList<>();
I need to transform them to
TreeMap<User,List<Message>> map
I know how to do transform to HashMap using
list.stream().collect(Collectors.groupingBy(Message::getSender));
But I need TreeMap with:
Key - User with newest message senddate first
Value - List sorted by senddate newest first
Part of User class
public class User{
...
private List<Message> sendMessages;
...
public List<Message> getSendMessages() {
return sendMessages;
}
}
User comparator:
public class Usercomparator implements Comparator<User> {
#Override
public int compare(User o1, User o2) {
return o2.getSendMessages().stream()
.map(message -> message.getSenddate())
.max(Date::compareTo).get()
.compareTo(o1.getSendMessages().stream()
.map(message1 -> message1.getSenddate())
.max(Date::compareTo).get());
}
}
You can use overloaded groupingBy method and pass TreeMap as Supplier:
TreeMap<User, List<Message>> map = list
.stream()
.collect(Collectors.groupingBy(Message::getSender,
() -> new TreeMap<>(new Usercomparator()), toList()));
If your list is sorted then just use this code for sorted map.
Map<String, List<WdHour>> pMonthlyDataMap = list
.stream().collect(Collectors.groupingBy(WdHour::getName, TreeMap::new, Collectors.toList()));

java - sorting a hashmap with date inside value object [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to sort a Map<Key, Value> on the values in Java?
Suppose am having a map like
Map<String, Student> studDetails = new HashMap<String, Student>();
And the map contains entries like
studDetails.put("1",student1);
studDetails.put("2",student2);
studDetails.put("3",student3);
studDetails.put("4",student4);
Student entity is Something like
Class Student{
private String studName;
private List<Group> groups;
}
Group entity will be like
Class Group{
private String groupName;
private Date creationDate;
}
OK, so what I need is when am displaying student details, it will be in the order of group creation date.
So if student is mapped to more than one group, we can take the creationDate of first group.
How can I given the soring on my HashMap studDetails using this scenario.?
Can anyone help..please..
HashMap is not sorted you should use a SortedMap implementation instead for example TreeMap.
Then you could create your own Comparator<String> which will sort by the groups attribute of the actual Student instance but you'll need the actual map for it because TreeMap sorts by the keys, so this is a possible but not nice solution.
So with TreeMap:
public class StudentGroupComparator implements Comparator<String> {
private Map<String, Student> sourceMap;
public StudentGroupComparator(Map<String, Student> sourceMap) {
this.sourceMap = sourceMap;
}
#Override
public int compare(String key1, String key2) {
// TODO: null checks
Student student1 = sourceMap.get(key1);
Student student2 = sourceMap.get(key2);
Date o1CreationDate = student1.groups.get().creationDate;
Date o2CreationDate = student2.groups.get().creationDate;
return o1CreationDate.compareTo(o2.creationDate);
}
}
SortedMap<String, Student> sortedMap = new TreeMap<String, Student>(new StudentGroupComparator(sourceMap));
sortedMap.putAll(sourceMap);
How can I given the soring on my HashMap studDetails using this scenario?
You can't, because HashMap is fundamentally unordered (or at least, the ordering is unstable and unhelpful).
Even for sorted maps like TreeMap, the sort order is based on the key, not the value.
Add Students Objects to List and Use Collections.sort(list, custom_comparetor).
Prepare one Custom comparator to sort Students Objects.
Try this code it might helpful
StudentComparator.java
class StudentComparator implements Comparator {
public int compare(Object stud1, Object stud2) {
List<Group> list1Grp = ((Student) stud1).getGroups();
List<Group> list2Grp = ((Student) stud2).getGroups();
Collections.sort(list1Grp, new GroupComparator());
Collections.sort(list2Grp, new GroupComparator());
return list1Grp.get(0).getCreationDate().compareTo(list2Grp.get(0).getCreationDate());
}
}
GroupComparator.java
public class GroupComparator implements Comparator {
public int compare(Object grp1, Object grp2) {
return ((Group) grp1).getCreationDate().compareTo(
((Group) grp2).getCreationDate());
}
}
main method
add student object to one new List
then use
Collections.sort(new_stud_list, new StudentComparator());
Add Comparable to Group
class Group implements Comparable {
private String groupName;
private Date creationDate;
public Date getCreationDate() {
return creationDate;
}
#Override
public int compareTo(Object t) {
Group g = (Group) t;
return getCreationDate().compareTo(g.getCreationDate());
}
}
Use TreeSet instead of List in Student for Groups
public class Student implements Comparable {
private String studName;
private TreeSet<Group> groups;
public TreeSet<Group> getGroups() {
return groups;
}
#Override
public int compareTo(Object t) {
Student t1 = (Student) t;
return groups.first().getCreationDate()
.compareTo(t1.getGroups().first().getCreationDate());
}
}
Now use
TreeSet<Student> studDetails = new TreeSet();
then add students. It will be ordered one. Hope you can take care of null pointer exceptions

Java: how to transform from List<T> to Map<f1(T), List(f2(T))> without iterating

I have a list of objects that I need to transform to a map where the keys are a function of each element, and the values are lists of another function of each element. Effectively this is grouping the elements by a function of them.
For example, suppose a simple element class:
class Element {
int f1() { ... }
String f2() { ... }
}
and a list of these:
[
{ f1=100, f2="Alice" },
{ f1=200, f2="Bob" },
{ f1=100, f2="Charles" },
{ f1=300, f2="Dave" }
]
then I would like a map as follows:
{
{key=100, value=[ "Alice", "Charles" ]},
{key=200, value=[ "Bob" ]},
{key=300, value=[ "Dave" ]}
}
Can anyone suggest a succinct way of doing this in Java without iterating? A combination of LambdaJ's group method with Guava's Maps.transform nearly gets there, but group doesn't generate a map.
Guava has Maps.uniqueIndex(Iterable values, Function keyFunction) and Multimaps.index(Iterable values, Function keyFunction), but they don't transform the values. There are some requests to add utility methods that do what you want, but for now, you'll have to roll it yourself using Multimaps.index() and Multimaps.transformValues():
static class Person {
private final Integer age;
private final String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
}
private enum GetAgeFunction implements Function<Person, Integer> {
INSTANCE;
#Override
public Integer apply(Person person) {
return person.getAge();
}
}
private enum GetNameFunction implements Function<Person, String> {
INSTANCE;
#Override
public String apply(Person person) {
return person.getName();
}
}
public void example() {
List<Person> persons = ImmutableList.of(
new Person(100, "Alice"),
new Person(200, "Bob"),
new Person(100, "Charles"),
new Person(300, "Dave")
);
ListMultimap<Integer, String> ageToNames = getAgeToNamesMultimap(persons);
System.out.println(ageToNames);
// prints {100=[Alice, Charles], 200=[Bob], 300=[Dave]}
}
private ListMultimap<Integer, String> getAgeToNamesMultimap(List<Person> persons) {
ImmutableListMultimap<Integer, Person> ageToPersons = Multimaps.index(persons, GetAgeFunction.INSTANCE);
ListMultimap<Integer, String> ageToNames = Multimaps.transformValues(ageToPersons, GetNameFunction.INSTANCE);
// Multimaps.transformValues() returns a *lazily* transformed view of "ageToPersons"
// If we want to iterate multiple times over it, it's better to create a copy
return ImmutableListMultimap.copyOf(ageToNames);
}
A re-usable utility method could be:
public static <E, K, V> ImmutableListMultimap<K, V> keyToValuesMultimap(Iterable<E> elements, Function<E, K> keyFunction, Function<E, V> valueFunction) {
ImmutableListMultimap<K, E> keysToElements = Multimaps.index(elements, keyFunction);
ListMultimap<K, V> keysToValuesLazy = Multimaps.transformValues(keysToElements, valueFunction);
return ImmutableListMultimap.copyOf(keysToValuesLazy);
}
I guess we could improve the generics in the signature by using Function<? extends E, K> or something, but I don't have the time to delve further...
Now with Java8 you can do it like:
static class Element {
final int f1;
final String f2;
Element(int f1, String f2) {
this.f1 = f1;
this.f2 = f2;
}
int f1() { return f1;}
String f2() { return f2; }
}
public static void main(String[] args) {
List<Element> elements = new ArrayList<>();
elements.add(new Element(100, "Alice"));
elements.add(new Element(200, "Bob"));
elements.add(new Element(100, "Charles"));
elements.add(new Element(300, "Dave"));
elements.stream()
.collect(Collectors.groupingBy(
Element::f1,
Collectors.mapping(Element::f2, Collectors.toList())
))
.forEach((f1, f2) -> System.out.println("{"+f1.toString() + ", value="+f2+"}"));
}
There has been some discussion in adding one API in Apache's CollectionUtils to transform a List to Map, but then I dont see any reason for not using a foreach contruct, Is there any problem that you are facing ? Transform will do the same thing which you can get easily by foreach, looping cannot be avoided.
EDIT:
Here is the link to discussion in Apache's forum http://apache-commons.680414.n4.nabble.com/Convert-List-to-Map-td747218.html
I don't know why you don't want to iterate. JDK does not support transform, but you can implement it yourself.
If you are worried about the performance, even if JDK had supported it, it would have also iterated it.

Looking for a "chained map" implementation in Java

I need a mapping from a list of keys to a value. I know I could write my own code like this:
Map<Person, Map<Daytime, Map<Food, Integer>>> eaten = ...;
Now I want to have some get and put methods like these:
Integer numberOfEggsIAteInTheMorning = eaten.get(me, morning, scrambledEggs);
eaten.put(me, evening, scrambledEggs, 1);
Do you know of an existing class that has this kind of API? I'm too lazy of writing it myself. ;)
If you look for a more generic approach, and you might have more than 2 or 3 'chain steps', I would suggest in applying some different structural approach, rather than sticking to using only basic collection classes. I have feeling that Composite Pattern could be the right choice if it's correctly applied.
EDIT: due to example requested
The full example would be somewhat time consuming, so let me just explain my idea with dirty Java/pseudocode mix (I'm not even sure if I've missed something!!!). Let's consider we have class BaseMap:
abstract class BaseMap {
public abstract Object getValue(Object.. keys);
public abstract void putValue(Object value, Object.. keys);
}
Then we could have ObjectMap that would be the 'leaf' of our composite structure:
class ObjectsMap extends BaseMap {
private Map<Object, Object> map = new [...]
public Object getValue(Object.. keys) {
// assert that keys.length == 1
return map.get(keys[0]);
}
public void putValue(Object value, Object.. keys) {
// assert that keys.length = 1
map.put(keys[0], value);
}
}
And the actual composite would be as such:
class CompositeMap extends BaseMap {
private Map<Object, BaseMap> compositeMaps = new [...]
public Object getValue(Object.. keys) {
// assert that keys.length > 1
return compositeMap.get(keys[0]).getValue(/* System.arrayCopy => subset of elements {keys_1, .. ,keys_max} */);
}
public void putValue(Object value, Object.. keys) {
// assert keys.length > 1
BaseMap newMap = null;
if (keys.length = 2) -> newMap = new ObjectsMap()
else newMap = new CompositeMap();
newMap.putValue(value, /*subset of keys {keys_1, .. , keys_max}*/);
}
}
You can use org.apache.commons.collections.keyvalue.MultiKey for that: Map<Multikey, Object>
It would be hard to implement a general chained map.
How would the declaration of the class look like? (You can't have a variable number of type parameters.
class ChainedMap<K1..., V>
Another option would be to have a ChainedMapUtil class that performs put / get recursively.
Here is an example of a recursive get. (Quite ugly solution though I must say.)
import java.util.*;
public class Test {
public static Object chainedGet(Map<?, ?> map, Object... keys) {
Object k = keys[0];
if (!map.containsKey(k)) return null;
if (keys.length == 1) return map.get(k);
Object[] tailKeys = Arrays.copyOfRange(keys, 1, keys.length);
return chainedGet((Map<?,?>) map.get(k), tailKeys);
}
public static void main(String[] arg) {
Map<String, String> m1 = new HashMap<String, String>();
m1.put("ipsum", "dolor");
Map<Integer, Map<String, String>> m2 =
new HashMap<Integer, Map<String, String>>();
m2.put(17, m1);
Map<String, Map<Integer, Map<String, String>>> chained =
new HashMap<String, Map<Integer, Map<String, String>>>();
chained.put("lorem", m2);
System.out.println(chainedGet(chained, "lorem", 17, "ipsum")); // dolor
System.out.println(chainedGet(chained, "lorem", 19, "ipsum")); // null
}
}
If you are going to write your own, I would suggest
eaten.increment(me, evening, scrambledEggs);
You could use a composite key
eaten.increment(Key.of(me, evening, scrambledEggs));
(TObjectIntHashMap supports increment and adjust)
You may not even need a custom key.
eaten.increment(me + "," + evening + "," + scrambledEggs);
It is fairly easy to decompose the key with split()
I once made a map using 3 keys just for fun.May be you can use it instead of using chained maps:
public class ThreeKeyMap<K1,K2,K3,V>{
class wrap{
K1 k1;
K2 k2;
K3 k3;
public wrap(K1 k1,K2 k2,K3 k3) {
this.k1=k1;this.k2=k2;this.k3=k3;
}
#Override
public boolean equals(Object arg0) {
// TODO Auto-generated method stub
wrap o=(wrap)arg0;
if(!this.k1.equals(o.k1))
return false;
if(!this.k2.equals(o.k2))
return false;
if(!this.k2.equals(o.k2))
return false;
return true;
}
#Override
public int hashCode() {
int result=17;
result=37*result+k1.hashCode();
result=37*result+k2.hashCode();
result=37*result+k3.hashCode();
return result;
}
}
HashMap<wrap,V> map=new HashMap<wrap, V>();
public V put(K1 k1,K2 k2,K3 k3,V arg1) {
return map.put(new wrap(k1,k2,k3), arg1);
}
public V get(Object k1,Object k2,Object k3) {
return map.get(new wrap((K1)k1,(K2)k2,(K3)k3));
}
public static void main(String[] args) {
ThreeKeyMap<Integer,Integer,Integer,String> birthDay=new ThreeKeyMap<Integer, Integer, Integer, String>();
birthDay.put(1, 1,1986,"Emil");
birthDay.put(2,4,2009, "Ansih");
birthDay.put(1, 1,1986,"Praveen");
System.out.println(birthDay.get(1,1,1986));
}
}
UPDATE:
As #Arturs Licis suggested.I looked up in net for composite pattern and I wrote a sample using it.I guess this is composite..Please comment if it is not so.
Person class:
public class Person {
private final String name;
private Map<Time, Food> map = new HashMap<Time, Food>();
public Person(String name) {
this.name = name;
}
void addTimeFood(Time time, Food food) {
map.put(time, food);
}
public String getName() {
return name;
}
Food getFood(Time time) {
Food tmp = null;
return (tmp = map.get(time)) == null ? Food.NoFood : tmp;
}
// main to test the person class
public static void main(String[] args) {
Person p1 = new Person("Jack");
p1.addTimeFood(Time.morning, Food.Bread);
p1.addTimeFood(Time.evening, Food.Chicken);
Person p2 = new Person("Jill");
p2.addTimeFood(Time.morning, Food.Egg);
p2.addTimeFood(Time.evening, Food.Rice);
Map<String, Person> map = new HashMap<String, Person>();
map.put(p1.getName(), p1);
map.put(p2.getName(), p2);
System.out.println(map.get("Jack").getFood(Time.evening));
}
#Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(name).append("\n");
b.append(map);
return b.toString();
}
}
Food class:
public enum Food {
Rice,
Egg,
Chicken,
Bread,
NoFood;
}
Time class:
public enum Time {
morning,
evening,
night
}

Categories

Resources