Transforming into Map using Java 8 and lambdas - java

I have
List<Gift> gifts = new ArrayList<>();
gifts .add(new Gift().withType(INF, CHD));
gifts .add(new Gift().withType(ADT, CHD));
gifts .add(new Gift().withType(INF, ADT));
Gift has a method List<Type> getTypes();
and now I'd like to transform gifts list into something like
Map<Type,List<Gift>>.
I'd like to do it with Java 8 and lambdas in one line. Is it possible?
public class Gift {
public List<Type> getTypes() {
return types;
}
public Gift withType(Type... types) {
this.types = Arrays.asList(types);
return this;
}
List<Type> types = new ArrayList<>();
}
public enum Type {
ADT,
CHD,
INF;
}
Previous old code (it looks awfully). That's all what I have.
Map<Type, List<Gift>> byTypes = new HashMap<>();
for (Gift gift : gifts) {
for (Type type : gift.getTypes()) {
List<Gift> giftList = byTypes.get(type);
if (giftList == null) {
giftList = new ArrayList<>();
}
giftList.add(gift);
byTypes.put(type,giftList);
}
}

Using Guava's Multimap:
ListMultimap<Type, Gift> multimap = ArrayListMultimap.create();
gifts.forEach(g -> g.getTypes().forEach(t -> multimap.put(t, g)));
Map<Type, Collection<Gift>> map = multimap.asMap();

Ok, I found solution which satify me :-) I wrote my collector :-D
Map<Type, List<Gift>> collect1 = gifts.stream().collect(new TypeToManyGiftCollector());
public class TypeToManyGiftCollector
implements Collector<Gift, Map<Type, List<Gift>>, Map<Type, List<Gift>>> {
#Override
public Supplier<Map<Type, List<Gift>>> supplier() {
return () -> new HashMap<Type, List<Gift>>() {{
for (Type type : Type.values()) {
put(type, new ArrayList<Gift>());
}
}};
}
#Override
public BiConsumer<Map<Type, List<Gift>>, Gift> accumulator() {
return (Map<Type, List<Gift>> map, Gift gift) -> {
gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
};
}
#Override
public BinaryOperator<Map<Type, List<Gift>>> combiner() {
return (Map<Type, List<Gift>> map1, Map<Type, List<Gift>> map2) ->
{
for (Type type : Type.values()) {
map1.get(type).addAll(map2.get(type));
}
return map1;
};
}
#Override
public Function<Map<Type, List<Gift>>, Map<Type, List<Gift>>> finisher() {
return Function.identity();
}
#Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
}
}

Reintroducing your intermediate Pair class P, but in a slightly different way. I think we might expect that in later iterations of Java, this is going to become much more common place and easier to do with the introduction of a built-in Pair type, and the implementation of Value Objects.
package play;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static play.Type.*;
public class Play {
static class P<K,V> { K k; V v; P(K kk,V vv) {k=kk;v=vv;}}
public static void main(String[] args) throws IOException {
List<Gift> gifts = new ArrayList<>();
gifts .add(new Gift().withType(INF, CHD));
gifts .add(new Gift().withType(ADT, CHD));
gifts .add(new Gift().withType(INF, ADT));
Map<Type,List<Gift>> m = gifts.stream()
.flatMap((g)->g.getTypes().stream().map((t)->new P<>(t,g)))
.collect(
Collectors.groupingBy(
(p)->p.k,
// Does not work with <code>new EnumMap<>(Type.class)</code>
// I dont know why, ...
()->new EnumMap(Type.class),
Collectors.mapping(
(p)->p.v,
Collectors.toList()
)
)
);
System.out.println("Map: "+m.toString());
}
}
My only problem in this code is that I cannot explain the need for a 'Untyped' Map as the Map factory. Feel free to explain if you know why ...

I found other solution with a reduce method. In my opinion smaller one and easier to understand.
HashMap<Type, List<Gift>> result = gifts.stream().reduce(
new HashMap<Type, List<Gift>>() {{
asList(Type.values()).stream().forEach(type -> put(type, new ArrayList<>()));
}}
,
(map, gift) -> {
gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
return map;
}
,
(map1, map2) -> {
asList(Type.values()).stream().forEach(type -> map1.get(type).addAll(map2.get(type)));
return map1;
}
);

Related

Dynamic datatype in generics java

I have a usecase where client is sending a List<Function>. Task is to iterate and execute this function and keep it in a TypedSafeMap.
pseudo client code:
Function<String, Integer> firstFn = x -> x.length();
Function<String, String> secondFn = x -> x.substring(0);
client.runTheseFunctions(Arrays.asList(firstFn, secondFn));
Inside runtTheseFunctions in the code, task is to execute these functions and keep it in a TypedSafeMap where the key is the datatype of the type of the result of the function and value is the return of functions.apply();
The code below
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
lst.stream().forEach( x -> {
typedSafeMap.put(????, x.apply(o));
//The key is nothing but the datatype of the x.apply(o).
//How do I add this in runtime here. Generics is all compile time safety.
});
}
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
lst.stream().collect(Collectors.toMap(f -> f.apply(o).getClass(), f -> f.apply(o)));
}
You can implement your "runTheseFunctions" method as shown below:
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
Map<Class<?>, Object> typedSafeMap = new HashMap<>();
lst.stream().forEach(x -> {
Object value = x.apply(o);
typedSafeMap.put(value.getClass(), value);
});
System.out.println(typedSafeMap);
}
In case the List of Functions contains two or more Functions with the same outputtype (for instance: String getFirstName, String getLastName, toMap will fail. So an alternative is:
var map = list.stream().collect(groupingBy(
f -> f.apply(e).getClass(),
mapping(f -> f.apply(e), toList())
));
Here is an example of what you want to achieve, and you can use for your tests. I assumed an trivial implementation of Employee class, just to give you an idea:
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
class Employee {
String name;
public Employee(String name) {
this.name = name;
}
public int length() {
return name.length();
}
public String substring(int index) {
return name.substring(index);
}
}
public class Test {
public static void main(String[] args) {
Employee e = new Employee("Marco");
Function<Employee, Integer> firstFn = x -> x.length();
Function<Employee, String> secondFn = x -> x.substring(0);
runTheseFunctions(Arrays.asList(firstFn, secondFn), e);
}
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
Map<Class, Object> typedSafeMap = new HashMap<>();
lst.stream().forEach(x -> {
Object result = x.apply(o);
typedSafeMap.put(x.apply(o).getClass(), x.apply(o));
// The key is nothing but the datatype of the x.apply(o).
// How do I add this in runtime here. Generics is all compile time safety.
});
typedSafeMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " - " + entry.getValue()));
}
}
And here is the output:
class java.lang.String - Marco
class java.lang.Integer - 5
Enhancing #Yonas answer:
private static Map<?, ? extends Object> runTheseFunctions(List<Function<String, ? extends Object>> list, String o) {
return list.stream()
.map(f -> f.apply(o))
.collect(Collectors.toMap(result -> result.getClass(), Function.identity()));
}
This will call the f.apply(o) only once.

returning Hashmap having different mappings in stream

I am learning some cool stuff about Java StreamAPI and got stuck'd into one problem:
I have a use case where I want to return newly create hashmap using stream. I am using the traditional way of defining a HashMap in the function and adding up values to it.
I was more interested in knowing some better ways to achieve so
public Map<String,String> constructMap(List<CustomObject> lists){
Map<String,String> newMap = new HashMap<>();
lists.stream().filter(x->x!=null).forEach(map -> newMap.putAll(map.getSomeMapping(studentId));
return newMap;
}
Can I achieve this using reduceAPI or any other way without having to create a custom hashmap (directly return the stream one liner)?
Edit:
for Example:
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = new ArrayList();
lists.add(c1); lists.add(c2);
The getter in class CustomObject is: getSomeMapping(input)
which return Map<BookID, Book>
Expected output:
{"bookId1" : "book1", "bookId2" : "book2"}
Edit2:
One more thing to clarify, the CustomObject class does not have any other getters defined. The only function I have access to is getSomeMapping(input) which returns a mapping
thank you for any help.
Assuming CustomObject has the following structure and getter getSomeMapping which returns a map:
class CustomObject {
private Map<String, String> someMapping;
public CustomObject(String key, String value) {
this.someMapping = new HashMap<>();
someMapping.put(key, value);
}
public Map<String, String> getSomeMapping() {
return someMapping;
}
}
Then constructMap will use already mentioned Collectors.toMap after flattening the entries in someMapping:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.map(CustomObject::getSomeMapping)
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1, // merge function to handle possible duplicates
LinkedHashMap::new
));
}
Test
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = Arrays.asList(c1, c2);
Map<String, String> result = constructMap(lists);
System.out.println(result);
Output:
{bookId1=book1, bookId2=book2}
You can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) to create a LinkedHashMap using the bookId as the key, and bookName as the value.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
class CustomObject {
private String bookId;
private String bookName;
public CustomObject(String bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public String getBookId() {
return bookId;
}
public String getBookName() {
return bookName;
}
// Other stuff e.g. equals, hashCode etc.
}
public class Main {
public static void main(String[] args) {
List<CustomObject> list = List.of(new CustomObject("bookId1", "book1"), new CustomObject("bookId2", "book2"));
System.out.println(constructMap(list));
}
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName, (a, b) -> a, LinkedHashMap::new));
}
}
Output:
{bookId1=book1, bookId2=book2}
Note: The mergeFunction, (a, b) -> a resolves the collision between values associated with the same key e.g. in this case, we have defined it to select a out of a and b having the same key. If the order of elements does not matter, you can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) as shown below:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName));
}
A sample output:
{bookId2=book2, bookId1=book1}
To turn a stream into a map you're better off using collect(). For instance:
public Map<String,String> toMap(List<Entry<String,String>> entries) {
return entries.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
Or if your keys are non-unique and you want the values to be combined as a list:
public Map<String,List<CustomObject>> toMap(List<CustomObject> entries) {
return entries.stream().collect(Collectors.groupingBy(CustomObject::getKey));
}
Look into [Collectors.toMap()] 1. This can return the items as a new Map.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(CustomObject::getMapKey(), CustomObject::getMapValue()));
getMapKey and getMapValue are here methods returning the key and value of the CustomObject for the map. Instead of using simple getters it might also be necessary to execute some more advanced logic.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(l -> {...; return key;}, l -> { ...; return value;}));
Let's assume your CustomObject class has getters to retrieve a school id with a name. You could do it like this. I declared it static as it does not appear to depend on instance fields.
public static Map<String,String> constructMap(List<CustomObject> lists){
return lists.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getName, CustomObject::getID));
}
This presumes that names and Id's are one-to-one, as this does not handle duplicate keys.

Collection.sort using anonymous class Comparator

I did not find needed information, so I decided to create a new question.
I have a little test app where I want to sort my Map by Values. But I can't understand why I can't do it in the following way:
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class Test {
public int test(int [] array) {
Map<Integer, Integer> map = new HashMap<>();
map.put(1,4);
map.put(2,3);
map.put(5,1);
map.put(7,0);
map.put(4,4);
map.put(9,1);
Collections.sort(map.entrySet(), new Comparator<Map.Entry<Integer, Integer>>() {
#Override
public int compare(Map.Entry<Integer, Integer> t, Map.Entry<Integer, Integer> t1) {
return t.getValue().compareTo(t1.getValue());
}
});
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
sum += entry.getValue();
}
return sum;
}
}
and Main class:
public class Main {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.test(arr));
}
}
This app shoud return 14 in this case. But I have this message on
Collections.sort(...) part:
sort (java.util.List, java.util.Comparator) in Collections cannot be applied to
(java.util.Set>,
anonymous
java.util.Comparator>)
  reason: no instance(s) of type variable(s) T exist so that
Set> conforms to List
but if I change it to Collections.min(...) or Collections.max(...):
Collections.min(map.entrySet(), new Comparator<Map.Entry<Integer, Integer>>() {
#Override
public int compare(Map.Entry<Integer, Integer> t, Map.Entry<Integer, Integer> t1) {
return t.getValue().compareTo(t1.getValue());
}
});
there will be no issues.
Java Map can't be sorted by value. But you can create a list from Map.entrySet() or maybe you don't need a collection at all.
Using List and Comparator.
List<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
list.sort(Comparator.comparing(Map.Entry::getValue));
Using Stream
map.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getValue))
//do something here

Grouping keys by value's list common elements

Having such map:
K1 -> [V1, V2]
K2 -> [V2, V3]
K3 -> [V3]
K4 -> [V4]
as a result I want to have list of grouped keys which have at least one common element from value list. Solution should support transitive relations (G1 group):
G1 = [K1, K2, K3]
G2 = [K4]
I got stuck on error described here. How can it be achieved in Spark ?
My code looks like:
public class Grouping implements Serializable {
public void group(JavaSparkContext sc) {
List<Mapping> list = newArrayList();
list.add(new Mapping("K1", newArrayList("V1", "V2")));
list.add(new Mapping("K2", newArrayList("V2", "V3")));
list.add(new Mapping("K3", newArrayList("V3")));
list.add(new Mapping("K4", newArrayList("V4")));
JavaRDD<Tuple2<Mapping, String>> pairs = sc.parallelize(list).map(Mapping::toPairs).flatMap(p -> p);
JavaPairRDD<String, Iterable<Mapping>> valuesToMappings = pairs.groupBy(Tuple2::_2).
mapToPair(t -> new Tuple2<>(t._1, stream(t._2).map(tt -> tt._1).collect(toList())));
JavaRDD<Group> map = valuesToMappings.map(t -> new Group(traverse(newHashSet(t._2.iterator()), valuesToMappings)));
System.out.println(map.collect());
}
private Set<Mapping> traverse(Set<Mapping> mappings, JavaPairRDD<String, Iterable<Mapping>> valuesToMappings) {
Set<String> values = mappings.stream().map(Mapping::getValues).flatMap(Collection::stream).collect(toSet());
Set<Mapping> mappingsHavingValues = mappingsHavingValues(values, valuesToMappings);
while (!mappings.equals(mappingsHavingValues)) {
mappingsHavingValues = mappingsHavingValues(values, valuesToMappings);
}
return mappingsHavingValues;
}
private Set<Mapping> mappingsHavingValues(Set<String> values, JavaPairRDD<String, Iterable<Mapping>> valuesToMappings) {
Set<Mapping> result = newHashSet();
for (String value : values) {
List<Iterable<Mapping>> lookup = valuesToMappings.lookup(value);
result.addAll(newArrayList(lookup.get(0))); //here I get an exception
}
return result;
}
public <T> Stream<T> stream(Iterable<T> in) {
return StreamSupport.stream(in.spliterator(), false);
}
}
public class Mapping implements Serializable {
private String key;
private List<String> values;
public Mapping(String key, List<String> values) {
this.key = key;
this.values = values;
}
public String getKey() {
return key;
}
public List<String> getValues() {
return values;
}
public List<Tuple2<Mapping, String>> toPairs() {
return getValues().stream().map(v -> new Tuple2<>(this, v)).collect(toList());
}
}
public class Group {
private Set<Mapping> mappings;
public Group(Set<Mapping> mappings) {
this.mappings = mappings;
}
public Set<Mapping> getMappings() {
return mappings;
}
}
You are looking for connected components in a graph. org.apache.spark.graphx.lib.ConnectedComponents provides a distributed solution for this.

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