I have a String - Array map that looks like this
dishIdQuantityMap[43]=[Ljava.lang.String;#301d55ce
dishIdQuantityMap[42]=[Ljava.lang.String;#72cb31c2
dishIdQuantityMap[41]=[Ljava.lang.String;#1670799
dishIdQuantityMap[40]=[Ljava.lang.String;#a5b3d21
What I need to do is
Create a new map, where key - only numbers extracted from String like this ( key -> key.replaceAll("\\D+","");
Value - first value from array like this value -> value[0];
Filter an array so that only this paris left where value > 0
I've spent an hour trying to solve it myself, but fail with .collect(Collectors.toMap()); method.
UPD:
The code I've done so far. I fail to filter the map.
HashMap<Long, Integer> myHashMap = request.getParameterMap().entrySet().stream()
.filter(e -> Integer.parseInt(e.getValue()[0]) > 0)
.collect(Collectors.toMap(MapEntry::getKey, MapEntry::getValue));
You can do it by using stream and an auxiliary KeyValuePair class.
The KeyValuePair would be as simple as:
public class KeyValuePair {
public KeyValuePair(String key, int value) {
this.key = key;
this.value = value;
}
private String key;
private int value;
//getters and setters
}
Having this class you can use streams as bellow:
Map<String, Integer> resultMap = map.entrySet().stream()
.map(entry -> new KeyValuePair(entry.getKey().replaceAll("Key", "k"), entry.getValue()[0]))
.filter(kvp -> kvp.value > 0)
.collect(Collectors.toMap(KeyValuePair::getKey, KeyValuePair::getValue));
In the example I'm not replacing and filtering exactly by the conditions you need but, as you said you are having problems with the collector, you probably just have to adapt the code you currently have.
I addressed this problem in the following order:
filter out entries with value[0] > 0. This step is the last on your list, but with regards to performance, it's better to put this operation at the beginning of the pipeline. It might decrease the number of objects that have to be created during the execution of the map() operation;
update the entries. I.e. replace every entry with a new one. Note, this step doesn't require creating a custom class to represent a key-value pair, AbstractMap.SimpleEntry has been with us for a while. And since Java 9 instead of instantiating AbstractMap.SimpleEntry we can make use of the static method entry() of the Map interface;
collect entries into the map.
public static Map<Long, Integer> processMap(Map<String, String[]> source) {
return source.entrySet().stream()
.filter(entry -> Integer.parseInt(entry.getValue()[0]) > 0)
.map(entry -> updateEntry(entry))
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
}
private static Map.Entry<Long, Integer> updateEntry(Map.Entry<String, String[]> entry) {
return Map.entry(parseKey(entry.getKey()), parseValue(entry.getValue()));
}
The logic for parsing keys and values was extracted into separate methods to make the code cleaner.
private static Long parseKey(String key) {
return Long.parseLong(key.replaceAll("\\D+",""));
}
private static Integer parseValue(String[] value) {
return Integer.parseInt(value[0]);
}
public static void main(String[] args) {
System.out.println(processMap(Map.of("48i;", new String[]{"1", "2", "3"},
"129!;", new String[]{"9", "5", "9"})));
}
Output
{48=1, 129=9}
Related
I have a default map of key value given like that
private Map<String, Integer> output = Stream.of(MyEnumCode.values())
.collect(Collectors.toMap(e -> e.code, e -> 0));//default EN1:0,ENS:0..
Note my enum is in this format
public enum MyEnum{
EN1("EN1"), ENS("ENS"), ENT("ENT").....;
String code;
MyEnum(String code) {
this.code = code;
}
After that there is a part of code witha switch statement that based on condition determines the MyEnumCodeValue and it is reiterated again to count the occurrencies of these enumvalues like that:
output.merge(MyEnumCode.code, 1, Integer::sum); //-->outuput like EN1:3,ENS:1 (based on occurrencies)
so by default I read this enumcodes and collect them to a map to output by default by 0.
is there another way to achieve the same result written in another way?
Thanks in advance!
You can avoid the first statement where you are adding all the enum values in the map.
private Map<String, Integer> output = Stream.of(MyEnumCode.values())
.collect(Collectors.toMap(e -> e.code, e -> 0));//default EN1:0,ENS:0..
Instead of doing this, you can do something like this.
private Map<String, Integer> output = new HashMap<>();
and at the time of putting values in the map, you can use,
output.put(enumKey, output.getOrDefault(enumKey, 1));
But the output can be diff for both the maps. Like this map will contain only those keys that are having occurrence more then one.
Update:
private Map<String, Integer> output = Stream.of(MyEnumCode.values())
.collect(Collectors.toMap(e -> e.code, e -> 0));
//Some statements
//....
//....
output.put(enumKey, output.getOrDefault(enumKey, 0) + 1);
Here output map will be having occurrence of all the enum keys be it 0 or not 0.
Say I have a HashMap and I want to insert the same value to a list of keys. How can I do this with Java 8 without iterating through all the keys and inserting the value? This is more of a Java Streams question.
Here is the straight forward way of doing it. This is a sample code that I wrote to demonstrate what I wanted to achieve.
public void foo(List<String> keys, Integer value) {
Map<String, Integer> myMap = new HashMap<>();
for (String key : keys) {
myMap.put(key, value);
}
}
Is there a simpler way of doing the above using Java 8 streams? How can I avoid the for loop using Java 8 streams. Thanks!
[Edit-1] A better code snippet below.
public void foo() {
Map<String, Integer> myMap = new HashMap<>();
List<String> keys = getKeysFromAnotherFunction();
Integer value = getValueToBeInserted(); // Difficult to show my actual use case. Imagine that some value is getting computed which has to be inserted for the keys.
for (String key : keys) {
myMap.put(key, value);
}
List<String> keys2 = getNextSetOfKeys();
Integer newValue = getValueToBeInserted();
for (String key : keys2) {
myMap.put(key, newValue);
}
}
Using collector, something like:
Map<String, Integer> myMap = keys.stream()
.collect(Collectors.toMap(key -> key,
val -> value, (a, b) -> b));
I think that your question is about factoring out some piece of code more than converting traditional for loops into stream constructs.
Suppose you have the following generic utility method:
public static <K, V, M extends Map<K, V>> M fillMap(
Supplier<List<K>> keysFactory,
Supplier<V> singleValueFactory,
Supplier<M> mapFactory) {
M map = mapFactory.get();
List<K> keys = keysFactory.get();
V singleValue = singleValueFactory.get();
keys.forEach(k -> map.put(k, singleValue));
return map;
}
Then, you could use the above method as follows:
Map<String, Integer> myMap = fillMap(() -> getKeysFromAnotherFunction(),
() -> getValueToBeInserted(),
HashMap::new); // create HashMap
myMap = fillMap(() -> getNextSetOfKeys(),
() -> getValueToBeInserted(),
() -> myMap); // use previously created map
There are variants for the code above, i.e., the method could receive a Map<K, V> instance instead of a Supplier<Map<K, V>>, or it might even be overloaded to support both variants.
I am having a List of HashMap i.e List<Map<String, Object>> result. I want to check if some key value is present on this list or not.
For Example:
Name Count
Marvel 10
DC 0
I want that my list of map should contain at least one entry for the above table for that I am using anyMatch
Assert.assertTrue(
result.stream().anyMatch(
ag -> ( "Marvel".equals(ag.get("Name")) && 10==(int)ag.get("Count"))
));
How can I use multiple Predicates ? I can have a list of 1000 HashMap, I just want to check if any two Hash-map from my list contains those two entries.
Your data structure seems wrong for a start. A List<Map<String, Object>>, where each map represents a row and has only one key and one value? You just need a Map<String, Integer>. The string is the name, the integer is the count. Remember to give it a meaningful name, e.g. comicUniverseToSuperheroFrequency.
With that said, Predicate has an and method which you can use to chain conditions together. It might look something like this:
public static void main(String[] args)
{
Map<String, Integer> comicUniverseToSuperheroFrequency = /*something*/;
boolean isMarvelCountTen = comicUniverseToSuperheroFrequency.entrySet().stream()
.anyMatch(row -> isMarvel().and(isTen()).test(row));
}
private static Predicate<Map.Entry<String, Integer>> isMarvel()
{
return row -> "Marvel".equals(row.getKey());
}
private static Predicate<Map.Entry<String, Integer>> isTen()
{
return row -> row.getValue() == 10;
}
Below is the sample code, Suggest me what is wrong here as per my knowledge, I am passing a new key every time still it says duplicate key.
public class CollectorsDemo5 {
public static void main(String[] args) {
List<String> listOfCities=new ArrayList<String>();
listOfCities.add("Istanbul");
listOfCities.add("Istanbul");
listOfCities.add("Budapest");
listOfCities.add("Delhi");
listOfCities.add("Amsterdam");
listOfCities.add("Canberra");
listOfCities.add("Canberra");
Map<Integer, String> map = listOfCities.stream()
.collect(Collectors.toMap(listOfCities::indexOf, Function.identity()));
map.forEach((k,v)->System.out.println("key:"+k +" value:"+v));
}
}
Some of list elements are duplicate.
Hence indexOf will return the same value -> Throw duplicate key exception.
What you can do is to do a normal for loop:
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < listOfCities.size(); i++) {
map.put(i, listOfCities.get(i));
}
You get that exception because you have duplicate values in your stream. You can produce the result you desire with IntStream:
Map<Integer, String> map = IntStream.range(0, listOfCities.size())
.mapToObj(Function.identity())
.collect(Collectors.toMap(Function.identity(), listOfCities::get));
Using a simple for loop would be the way to go here. If you really want to use Streams
IntStream.range(0, listOfCities.size())
.collect(HashMap::new, (map, index) -> map.put(index, listOfCities.get(index)), HashMap::putAll);
This produces
{0=Istanbul, 1=Istanbul, 2=Budapest, 3=Delhi, 4=Amsterdam, 5=Canberra, 6=Canberra}
The above creates a new HashMap to which we add the city name keyed by the index. The last combiner is only applicable when using a parallel stream and is used to combine the multiple maps.
What would be the fastest way to get the common values from all the sets within an hash map?
I have a
Map<String, Set<String>>
I check for the key and get all the sets that has the given key. But instead of getting all the sets from the hashmap, is there any better way to get the common elements (value) from all the sets?
For example, the hashmap contains,
abc:[ax1,au2,au3]
def:[ax1,aj5]
ijk:[ax1,au2]
I want to extract the ax1 and au2 alone, as they are the most common values from the set.
note: not sure if this is the fastest, but this is one way to do this.
First, write a simple method to extract the frequencies for the Strings occurring across all value sets in the map. Here is a simple implementation:
Map<String, Integer> getFrequencies(Map<String, Set<String>> map) {
Map<String, Integer> frequencies = new HashMap<String, Integer>();
for(String key : map.keySet()) {
for(String element : map.get(key)) {
int count;
if(frequencies.containsKey(element)) {
count = frequencies.get(element);
} else {
count = 1;
}
frequencies.put(element, count + 1);
}
}
return new frequencies;
}
You can simply call this method like this: Map<String, Integer> frequencies = getFrequencies(map)
Second, in order to get the most "common" elements in the frequencies map, you simply sort the entries in the map by using the Comparator interface. It so happens that SO has an excellent community wiki that discusses just that: Sort a Map<Key, Value> by values (Java). The wiki contains multiple interesting solutions to the problem. It might help to go over them.
You can simply implement a class, call it FrequencyMap, as shown below.
Have the class implement the Comparator<String> interface and thus the int compare(String a, String b) method to have the elements of the map sorted in the increasing order of the value Integers.
Third, implement another method, call it getCommon(int threshold) and pass it a threshold value. Any entry in the map that has a frequency value greater than threshold, can be considered "common", and will be returned as a simple List.
class FrequencyMap implements Comparator<String> {
Map<String, Integer> map;
public FrequencyMap(Map<String, Integer> map) {
this.map = map;
}
public int compare(String a, String b) {
if (map.get(a) >= map.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
public ArrayList<String> getCommon(int threshold) {
ArrayList<String> common = new ArrayList<String>();
for(String key : this.map.keySet()) {
if(this.map.get(key) >= threshold) {
common.add(key);
}
}
return common;
}
#Override public String toString() {
return this.map.toString();
}
}
So using FrequencyMap class and the getCommon method, it boils down to these few lines of code:
FrequencyMap frequencyMap = new FrequencyMap(frequencies);
System.out.println(frequencyMap.getCommon(2));
System.out.println(frequencyMap.getCommon(3));
System.out.println(frequencyMap.getCommon(4));
For the sample input in your question this is the o/p that you get:
// common values
[ax1, au6, au3, au2]
[ax1, au2]
[ax1]
Also, here is a gist containing the code i whipped up for this question: https://gist.github.com/VijayKrishna/5973268