Java 8 way to convert collection into one key multiple values map - java

Any way to perform the below code using Java 8.
final Map<String, Collection<ProductStrAttributeOverrideRulesModel>> attributeRulesMap = new HashMap<String, Collection<ProductStrAttributeOverrideRulesModel>>();
for (final ProductStrAttributeOverrideRulesModel rule : rules)
{
final String key = rule.getProductStrAttributeOverride().getProductStrTypeField().getAttributeDescriptorQualifier();
if (attributeRulesMap.containsKey(key))
{
final Collection<ProductStrAttributeOverrideRulesModel> currentRules = attributeRulesMap.get(key);
currentRules.add(rule);
}
else
{
final Collection<ProductStrAttributeOverrideRulesModel> list = new LinkedList<ProductStrAttributeOverrideRulesModel>();
list.add(rule);
attributeRulesMap.put(key, list);
}
}
if it is only
final Map<String, ProductStrAttributeOverrideRulesModel> attributeRulesMap
than i can do like following but i need to arrange the whole collection inside a map based on key and each key can have multiple values stored in collection.
Map<String, ProductStrAttributeOverrideRulesModel> result =
choices.stream().collect(Collectors.toMap(ProductStrAttributeOverrideRulesModel::getProductStrAttributeOverride.getProductStrTypeField.getAttributeDescriptorQualifier,
Function.identity()));

You could use groupingBy :
Map<String,List<ProductStrAttributeOverrideRulesModel>>
map =
choices.stream()
.collect(Collectors.groupingBy(rule -> rule.getProductStrAttributeOverride().getProductStrTypeField().getAttributeDescriptorQualifier()));
And if you don't want a List, you can pass a second argument to groupingBy and specify whatever Collection you want. For example :
Map<String,Collection<ProductStrAttributeOverrideRulesModel>>
map =
choices.stream()
.collect(Collectors.groupingBy(rule -> rule.getProductStrAttributeOverride().getProductStrTypeField().getAttributeDescriptorQualifier(),
Collectors.toCollection(HashSet::new)));

Note that it doesn’t always have to be a Stream operation. Your code would also benefit from using the “diamond operator” (though not new to Java 8) and from using new collection operations, i.e. computeIfAbsent, which allows to elide the entire conditional inside the loop and its code duplication. Putting both together, you’ll get:
final Map<String, Collection<ProductStrAttributeOverrideRulesModel>>
attributeRulesMap = new HashMap<>();
for(final ProductStrAttributeOverrideRulesModel rule: rules)
{
final String key = rule.getProductStrAttributeOverride()
.getProductStrTypeField().getAttributeDescriptorQualifier();
attributeRulesMap.computeIfAbsent(key, x->new LinkedList<>()).add(rule);
}
You could also replace the loop by a forEach invocation, if you wish:
rules.forEach(rule -> attributeRulesMap.computeIfAbsent(
rule.getProductStrAttributeOverride()
.getProductStrTypeField().getAttributeDescriptorQualifier(),
x->new LinkedList<>()).add(rule)
);
though it’s debatable whether this is an improvement over the classical loop here…

Related

Java HashMap key fits to a pattern?

I have a Map dataset, and I want to iterate through the keys and search for matches.
So I want to find the maps element, where the key fits to this pattern:
String searchedKey = "A?C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", MyObject(1));
myMap.put("CDF", MyObject(2));
myMap.put("ADS", MyObject(3));
for (Map.Entry<String,MyObject> entry : myMap.entrySet()) {
// in this case, I want to find the first element, because it's key fits the searchedKey, where ? can be anything
}
How can I do this?
Thanks!
You could do something like this to return a list of found MyObjects. Note I changed ? to . for any character.
String searchedKey = "A.C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", new MyObject(1));
myMap.put("CDF", new MyObject(2));
myMap.put("ARS", new MyObject(3));
myMap.put("VS", new MyObject(4));
myMap.put("AQC", new MyObject(3));
myMap.put("DS", new MyObject(3));
myMap.put("ASC", new MyObject(10));
List<Map.Entry<String,MyObject>> list = myMap.entrySet().stream()
.filter(e -> e.getKey().matches(searchedKey))
.collect(Collectors.toList());
list.forEach(System.out::println);
Prints
ASC=10
ABC=1
AQC=3
The MyObject class
class MyObject {
int val;
public MyObject(int v) {
this.val = v;
}
public String toString() {
return val + "";
}
}
You could use Regex-Patterns that allow to search Strings for matchings of a logical sequence using String#matches(String).
Here is a page that might help you create and test a regex for your needs. You might also have to construct your pattern flexible during runtime, depending on how your search works.
Tho keep in mind that a HashMap does not keep the order in which the keys were inserted. keySet() does not return them in a fixed order. If you need them ordered, you could use a LinkedHashMap

Create Map with provided set of keys and default values for them

I have a method that receives List<String> keys and does some computations on these and at the end it returns Map<String, String> that has keys from that List and values computed or not. If value cannot be computed I want to have empty String for that key.
The clearest way would be to create Map containing all the keys with default values (empty String in that case) at the start of computations and then replace computed values.
What would be the best way to do so? There is no proper initializer in Collections API I think.
The easiest answer came to me seconds ago:
final Map<String, String> labelsToReturn = keys.stream().collect(toMap(x -> x, x -> ""));
solves the problem perfectly.
Just use stream and toMap with key / value mapping to initialize the map.
I advise you to use Stream API Collectors.toMap() method. There is a way:
private static void build(List<String> keys) {
Map<String, String> defaultValues = keys.stream().collect(
Collectors.toMap(key -> key, key -> "default value")
);
// further computations
}
Map<String,String> map = new HashMap<>();
String emptyString = "";
for(String key:keys){
Object yourcomputation = emptyString;
//asign your computation to new value base on key
map.put(key,yourcomputation);
}

Simplifying loop with Java 8

I have a method that adds maps to a cache and I was wondering what I could do more to simplify this loop with Java 8.
What I have done so far:
Standard looping we all know:
for(int i = 0; i < catalogNames.size(); i++){
List<GenericCatalog> list = DummyData.getCatalog(catalogNames.get(i));
Map<String, GenericCatalog> map = new LinkedHashMap<>();
for(GenericCatalog item : list){
map.put(item.name.get(), item);
}
catalogCache.put(catalogNames.get(i), map);};
Second iteration using forEach:
catalogNames.forEach(e -> {
Map<String, GenericCatalog> map = new LinkedHashMap<>();
DummyData.getCatalog(e).forEach(d -> {
map.put(d.name.get(), d);
});
catalogCache.put(e, map);});
And third iteration that removes unnecessary bracers:
catalogNames.forEach(objName -> {
Map<String, GenericCatalog> map = new LinkedHashMap<>();
DummyData.getCatalog(objName).forEach(obj -> map.put(obj.name.get(), obj));
catalogCache.put(objName, map);});
My question now is what can be further done to simplify this?
I do understand that it's not really necessary to do anything else with this method at this point, but, I was curios about the possibilities.
There is small issue with solution 2 and 3 they might cause a side effects
Side-effects in behavioral parameters to stream operations are, in
general, discouraged, as they can often lead to unwitting violations
of the statelessness requirement, as well as other thread-safety
hazards.
As an example of how to transform a stream pipeline that
inappropriately uses side-effects to one that does not, the following
code searches a stream of strings for those matching a given regular
expression, and puts the matches in a list.
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
.forEach(s -> results.add(s)); // Unnecessary use of side-effects!
So instead of using forEach to populate the HashMap it is better to use Collectors.toMap(..). I am not 100% sure about your data structure, but I hope it is close enough.
There is a List and corresponding Map:
List<Integer> ints = Arrays.asList(1,2,3);
Map<Integer,List<Double>> catalog = new HashMap<>();
catalog.put(1,Arrays.asList(1.1,2.2,3.3,4.4));
catalog.put(2,Arrays.asList(1.1,2.2,3.3));
catalog.put(3,Arrays.asList(1.1,2.2));
now we would like to get a new Map where a map key is element from the original List and map value is an other Map itself. The nested Map's key is transformed element from catalog List and value is the List element itself. Crazy description and more crazy code below:
Map<Integer, Map<Integer, Double>> result = ints.stream().collect(
Collectors.toMap(
el -> el,
el -> catalog.get(el).stream().
collect(Collectors.toMap(
c -> c.intValue(),
c -> c
))
)
);
System.out.println(result);
// {1={1=1.1, 2=2.2, 3=3.3, 4=4.4}, 2={1=1.1, 2=2.2, 3=3.3}, 3={1=1.1, 2=2.2}}
I hope this helps.
How about utilizing Collectors from the stream API? Specifically, Collectors#toMap
Map<String, Map<String, GenericCatalog>> cache = catalogNames.stream().collect(Collectors.toMap(Function.identity(),
name -> DummyData.getCatalog(name).stream().collect(Collectors.toMap(t -> t.name.get(), Function.identity(),
//these two lines only needed if HashMap can't be used
(o, t) -> /* merge function */,
LinkedHashMap::new));
This avoids mutating an existing collection, and provides you your own individual copy of a map (which you can use to update a cache, or whatever you desire).
Also I would disagree with arbitrarily putting end braces at the end of a line of code - most style guides would also be against this as it somewhat disturbs the flow of the code to most readers.

Get top 3 counts from list using stream

I am trying to convert java7 program into java 8. I want below output using stream API.
public List<String> getTopThreeWeatherCondition7() {
List<String> _Top3WeatherList = new ArrayList<String>();
Map<String, Integer> _WeatherCondMap = getWeatherCondition7();
List<Integer> _WeatherCondList = new ArrayList<Integer>(_WeatherCondMap.values());
Collections.sort(_WeatherCondList, Collections.reverseOrder());
List<Integer> _TopThreeWeathersList = _WeatherCondList.subList(0, 3);
Set<String> _WeatherCondSet = _WeatherCondMap.keySet();
Integer count = 0;
for (String _WeatherCond : _WeatherCondSet) {
count = _WeatherCondMap.get(_WeatherCond);
for (Integer _TopThreeWeather : _TopThreeWeathersList) {
if (_TopThreeWeather == count) {
_Top3WeatherList.add(_WeatherCond);
}
}
}
_WeatherCondList = null;
_WeatherCondMap = null;
_TopThreeWeathersList = null;
_WeatherCondSet = null;
return _Top3WeatherList;
}
I strongly suggests to adhere to Java coding conventions. Start variable names with a lower case letter instead of _+upper case letter. Second, don’t assign local variables to null after use. That’s obsolete and distracts from the actual purpose of the code. Also, don’t initialize variables with an unused default (like the count = 0). In this specific case, you should also declare the variable within the inner loop, where it is actually used.
Note also that you are comparing Integer references rather than values. In this specific case it might work as the objects originate from the same map, but you should avoid that. It’s not clear whether there might be duplicate values; in that case, this loop will not do the right thing. Also, you should not iterate over the keySet(), just to perform a get lookup for every key, as there is entrySet() allowing to iterate over key and value together.
Since you said, this code ought to be a “Java 7 program” you should mind the existence of the “diamond operator” (<>) which removes the need to repeat type arguments when creating new instances of generic classes.
Instead of sorting the values only and searching for the associated keys, you should sort the entries in the first place.
So a clean Java 7 variant of your original code would be:
static final Comparator<Map.Entry<String, Integer>> BY_VALUE_REVERSED=
new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return Integer.compare(o2.getValue(), o1.getValue());
}
};
public List<String> getTopThreeWeatherCondition7() {
List<String> top3WeatherList = new ArrayList<>();
Map<String, Integer> weatherCondMap = getWeatherCondition7();
List<Map.Entry<String, Integer>> entryList=new ArrayList<>(weatherCondMap.entrySet());
Collections.sort(entryList, BY_VALUE_REVERSED);
List<Map.Entry<String, Integer>> topThreeEntries = entryList.subList(0, 3);
for(Map.Entry<String, Integer> entry: topThreeEntries) {
top3WeatherList.add(entry.getKey());
}
return top3WeatherList;
}
This also handles duplicates correctly. Only if there is a tie on the third place, just one of the valid candidates will be chosen.
Only if you have a clean starting point, you may look, how this can benefit from Java 8 features
Instead of copying the content to a List to sort it, you can create a Stream right from the Map and tell the stream to sort
You can create a comparator much easier, or even use one of the new builtin comparators
You can chain the task of limiting the result to three elements, map to the key and collect to the result List right to the stream of the previous steps:
public List<String> getTopThreeWeatherCondition7() {
Map<String, Integer> weatherCondMap = getWeatherCondition7();
List<String> top3WeatherList =
weatherCondMap.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.limit(3)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
return top3WeatherList;
}

converting static 2D String array to HashMap

What is the easiest way to convert a 2D array of Strings into a HashMap?
For example, take this:
final String[][] sheetMap = { /* XSD Name, XSL Sheet Name */
{"FileHeader", "FileHeader"},
{"AccountRecord", "AccountRecord"},
{"DriverCardRecord", "DriverCardRecord"},
{"AssetCardRecord", "AssetCardRecord"},
{"SiteCardRecord", "SiteCardRecord"}
};
This is most likely going to be loaded from a file and will be much bigger.
final Map<String, String> map = new HashMap<String, String>(sheetMap.length);
for (String[] mapping : sheetMap)
{
map.put(mapping[0], mapping[1]);
}
If you just want to initialize your map in a convenient way, you can use double brace initialization:
Map<String, String > sheetMap = new HashMap<String, String >() {{
put( "FileHeader", "FileHeader" );
put( "AccountRecord", "AccountRecord" );
put( "DriverCardRecord", "DriverCardRecord" );
put( "AssetCardRecord", "AssetCardRecord" );
put( "SiteCardRecord", "SiteCardRecord" );
}};
As a slightly cleaner alternative to tradeJmark answer:
String[][] arr = // your two dimensional array
Map<String, String> arrMap = Arrays.stream(arr).collect(Collectors.toMap(e -> e[0], e -> e[1]));
// Sanity check!
for (Entry<String, String> e : arrMap.entrySet()) {
System.out.println(e.getKey() + " : " + e.getValue());
}
Wait; if this is going to be loaded from a file, don't go through the intermediate array step! You would have had to load it all first before creating the array or you wouldn't know the size for the array. Just create a HashMap and add each entry as you read it.
The existing answers work well, of course, but in the interest of continually updating this site with new info, here's a way to do it in Java 8:
String[][] arr = {{"key", "val"}, {"key2", "val2"}};
HashMap<String, String> map = Arrays.stream(arr)
.collect(HashMap<String, String>::new,
(mp, ar) -> mp.put(ar[0], ar[1]),
HashMap<String, String>::putAll);
Java 8 Streams are awesome, and I encourage you to look them up for more detailed info, but here are the basics for this particular operation:
Arrays.stream will get a Stream<String[]> to work with.
collect takes your Stream and reduces it down to a single object that collects all of the members. It takes three functions. The first function, the supplier, generates a new instance of an object that collects the members, so in our case, just the standard method to create a HashMap. The second function, the accumulator, defines how to include a member of the Stream into the target object, in your case we simply want to put the key and value, defined as the first and second value from each array, into the map. The third function, the combiner, is one that can combine two of the target objects, in case, for whatever reason, the JVM decided to perform the accumulation step with multiple HashMaps (in this case, or whatever other target object in another case) and then needs to combine them into one, which is primarily for asynchronous execution, although that will not typically happen.
More concise with streams would be:
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.util.Map;
...
public static Map<String, String> asMap(String[][] data) {
return stream(data).collect(toMap( m->m[0], m->m[1] ));
}
...
Java 8 way
public static Map<String, String> convert2DArrayToMap(String[][] data){
return Arrays.stream(data).collect(Collectors.toMap(m -> m[0], m -> m[1]));
}
with loop
public static Map<String, String> convert2DArrayToMap(String[][] data)
{
Map<String, String> map = new HashMap<>();
for (String[] m : data)
{
if (map.put(m[0], m[1]) != null)
{
throw new IllegalStateException("Duplicate key");
}
}
return map;
}

Categories

Resources