Putting Map content into another Map, based on condition - java

I've tried searching for ways of doing this and they don't seem to solve my problem.
I have a Map:
Map<String,Element> elements = new HashMap<>();
I would like to put it into another Map; given a simple condition:
if (key.charAt(0) == 'W') //Does the start of the key have a "W"
How can I do this?

You can utilize Java 8 to perform the following:
elements.keySet().removeIf(key -> !key.startsWith("W"));
It removes every entry whose key does not start with W.
If you want to retain the first Map, simply create another Map first:
Map<String, Element> newElements = new HashMap<>(elements);
newElements.keySet().removeIf(key -> !key.startsWith("W"));

This worked just fine for me:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String args[])
{
Map<String,String> elements = new HashMap<>();
Map<String,String> elements2 = new HashMap<>();//the second map
elements.put("A", new String("A"));
elements.put("B", new String("B"));
elements.put("W1", new String("W1"));
elements.put("W2", new String("W2"));
for (Map.Entry<String, String> entry : elements.entrySet())
{
if(entry.getKey().charAt(0) == 'W')
elements2.put(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : elements2.entrySet())
{
//printing the elements of the second map
System.out.println(entry.getKey() + "/" + entry.getValue());
}
}
}
giving the following output:
W1/W1
W2/W2

It depends on what your use case is. I'm going to assume you're using java 8.
If you want a new map with the filtered entries you could do
Map<String,Element> newElements = elements.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("W"))
.collect(Collectors.toMap(Entry::getKey(), Entry::getValue()));
If you already have the other map and you just want to add the entries to it you could use:
//assuming Map<String,Element> newElements = some appropriately initialized map
elements.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("W"))
.forEach(entry -> newElements.put(entry.getKey(), entry.getValue());
You could also pass the result of the first option to putAll on the second map if you'd rather. Though in that case I'd be partial to the second option.

Related

Get Submap from Map using PredicateMap

I wanted to get a submap from predicateMap:
I have tried this:
public class first {
public static void main(String[] args)
{
TreeMap<String, String> myMap = new TreeMap<String, String>();
Predicate onlyStrings = new InstanceofPredicate( String.class );
myMap.put("Key1","1");
myMap.put("Key2","2");
myMap.put("Key3","3");
System.out.println("Before using submap: "+ myMap );
Predicate pred1 = new EqualPredicate( "1" );
Predicate pred2 = new EqualPredicate( "2" );
Predicate rule = new OrPredicate( pred1, pred2 );
Map map = PredicatedMap.decorate( myMap, onlyStrings, rule );
System.out.println("Before using submap: "+ map );
}
I am not able to get the desired submap which is the following:
Initial Map: {key1=1, key2=2, key3=3}
Output (submap): {key2=2, key3=3}
Can someone please help with this
It doesn't seems PredicatedMap do what you want to achive. It looks more like a validator when adding new values to map.
If you want to extract some values from a map base on predicate, Stream API from JDK should be enough.
If doesn't bother you to modify initial list:
myMap.entrySet().removeIf( e -> !(e.getValue().equals("1") || e.getValue().equals("2")));
If you want to keep initial list and create a new one:
Map<String, String> collect = myMap.entrySet().stream().filter(x -> x.getValue().equals("1") || x.getValue().equals("2"))
.collect(Collectors.toMap(e -> e.getKey(),e -> e.getValue()));
If you have a bigger list of value that you want to keep, you can create a set of them:
Set<String> values = Set.of("1","2");
and filter base on this set:
collect = myMap.entrySet().stream().filter(x -> values.contains(x.getValue()))
.collect(Collectors.toMap(e -> e.getKey(),e -> e.getValue()));
Or for the case with modifying initial list:
myMap.entrySet().removeIf( e -> !values.contains(e.getValue()));
Looks a bit clear if you extract values to keep as a set in my opinion.

How to put together multiple Maps <Character, Set<String>> without overriding Sets

In my project I am using two maps Map<Character, Set<String>>.
map1 - is temporally holding needed values
map2 - is summing all data from map1 after each loop
for example i got:
map2 = (B; Beryllium, Boron, Bromine)
map2 = (H; Hellum, Hydrogen, Hafnium)
now new map1 is:
map1 = (B; Bismuth)
map1 = (O; Oxygen)
In my code adding Oxygen as new entry is ok, but adding new entry for B ends by overraidding existing data in values and leave me only Bismuth.
My code:
while (iterator.hasNext()) {
Set<String> words = new TreeSet<>();
String word = iterator.next();
char[] wordChars = word.toCharArray();
//some code
words.add(word);
map1.put(wordChars[i], words);
}
map2.putAll(map1);
I tought about using .merge but I have no idea how to use it with Sets as values, and I cannot use simple Strings with concat.
You can use Map#merge like this:
Map<String, Set<String>> map1; // [key="B";values=["Beryllium", "Boron", "Bromine"]]
Map<String, Set<String>> map2; // [key="B";values=["Bismuth"] key="I";values=["Iron"]]
for (Entry<String, Set<String>> entry : map2.entrySet()) {
map1.merge(entry.getKey(), entry.getValue(), (s1, s2) -> {s1.addAll(s2); return s1;});
}
//map1 = [key="B";values=["Beryllium", "Boron", "Bromine", "Bismuth"] key="I";values=["Iron"]]
Map::compute is probably what you're looking for. This gives you a way to map any existing value (if there is one), or provide one if not.
For example, in your case something like the following would probably suffice:
oldMap.compute("B", current -> {
if (current == null) {
// No existing entry, so use newMap's one
return newMap.get("B");
} else {
// There was an existing value, so combine the Sets
final Set<String> newValue = new HashSet<>(current);
newValue.addAll(newMap.get("B"));
return newValue;
}
});
There's also MultiValueMap and Multimap from spring and guava respectively (if you're ok bringing in dependencies) which cover this case with less work already.
Temporary map1 will not be needed in this case. Get the set for that character, if null create a new set. Add the word to that set and put in the map:
while (iterator.hasNext()) {
String word = iterator.next();
//some code
Set<String> words = map2.get(word.charAt(0));
if(words == null) {
words = new TreeSet<>();
}
words.add(word);
map2.put(word.charAt(0), words);
}
When using the merge() function, if the specified key is not already associated with a value or the value is null, it associates the key with the given value.
Otherwise, i.e if the key is associated with a value, it replaces the value with the results of the given remapping function. So in order to do not overwrite the old value you must write your remapping function so that it combines the old and new values.
To do so replace this line :
map2.putAll(map1);
with
map1.forEach( (key, value)->{
map2.merge(key, value, (value1,value2) -> Stream.of(value1,value2)
.flatMap(Set::stream)
.collect(Collectors.toSet()));
});
This will iterate over map1 and add echh key which is not present into map2 and associate it with the given value and for each key which is already present it combines the old values and new values.
Alternative you can also work with Map.computeIfPresent and Map.putIfAbsent
map1.forEach( (key, value)->{
map2.computeIfPresent(key, (k,v) -> Stream.of(v,value).flatMap(Set::stream).collect(Collectors.toSet()));
map2.putIfAbsent(key, value);
});

Removing duplicate linked value from ArrayList java

we are facing a challenge in implementing one of our client requirement, using java as code technology.
we need to format the input given by the system, to display the data in a userfriendly format.
below is the data as input to our program.
its a java map with key as string and value as a list of strings
OP1004=[],
OP1006=[OP1004]
OP1005=[OP1003]
OP1009=[OP1006, OP1044, OP1046, OP1004],
OP1016=[OP1008, OP1009, OP1044, OP1005, OP1004],
output we are expecting as below.
OP1004=[],
OP1006=[OP1004]
OP1005=[OP1003]
OP1009=[OP1006, OP1044, OP1046], //here 1004 is deleted
OP1016=[OP1008, OP1009, OP1005, OP1004], //here 1044 is deleted
here, if we observe closely, we want to delete the repeated values from the list, that is
if we go thru the bottom, that is OP1016 contains the list as OP1008, OP1009 etc.. where OP1009 also has the list as OP1006, OP1044 etc.. where OP1006 again has the list as OP1004
so here we want to delete OP1004 from OP1009 because its already mapped to other(OP1006) OPID which is part of OP1009.
actually we are displaying this in a hierachy/flowchart diagram, so we want to delete duplicate navigation to the items.
Please help us in providing solution. appreciate your help in advance.
Thanks
Your problem boils down to checking whether elements of a list are present in other lists but these list are present in a map so you need to maintain the key-value pair.
This is how you can achieve that.
Loop over map and get the key and list of values, add a condition to check whether list is empty and contains more than 1 values.
Loop over same map again, get the key and add a condition to check whether the first loop key is equal to second loop key this is to avoid checking for same list. Add one more condition to check whether list is empty and contains more than 1 values.
Now you can remove elements from a list if those elements are present in another list by using List.removeAll() method.
Sample code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) {
List<String> firstList = new ArrayList<String>();
firstList.add("");
List<String> secondList = new ArrayList<String>();
secondList.add("OP1004");
List<String> thirdList = new ArrayList<String>();
thirdList.add("OP1003");
List<String> fourthList = new ArrayList<String>();
fourthList.add("OP1006");
fourthList.add("OP1044");
fourthList.add("OP1046");
List<String> fifthList = new ArrayList<String>();
fifthList.add("OP1008");
fifthList.add("OP1009");
fifthList.add("OP1044");
fifthList.add("OP1005");
fifthList.add("OP1004");
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("OP1004", firstList);
map.put("OP1006", secondList);
map.put("OP1005", thirdList);
map.put("OP1009", fourthList);
map.put("OP1016", fifthList);
for (Map.Entry<String, List<String>> keyAndValue: map.entrySet()) {
String key = keyAndValue.getKey();
List<String> values = keyAndValue.getValue();
if (values.isEmpty() || (values.size() < 2)){
continue;
}
for (Map.Entry<String, List<String>> mapKeyAndValue: map.entrySet()) {
String key1 = mapKeyAndValue.getKey();
if (key.equals(key1)){
continue;
}
List<String> values2 = mapKeyAndValue.getValue();
if (values2.isEmpty() || (values2.size() < 2)){
continue;
}
values2.removeAll(values);
}
}
for (Map.Entry<String, List<String>> keyAndValue: map.entrySet()) {
System.out.println("Key is " + keyAndValue.getKey() + " Values are " + keyAndValue.getValue());
}
}
}
Check Output Here
Key is OP1004 Values are []
Key is OP1006 Values are [OP1004]
Key is OP1005 Values are [OP1003]
Key is OP1009 Values are [OP1006, OP1044, OP1046]
Key is OP1016 Values are [OP1008, OP1009, OP1005, OP1004]
Note - I assumed that you are using HashMap as you didn't specify what kind of map you are using and if you want the map to be ordered then use LinkedHashMap
as HashMap does not store elements in order.
A simple solution would be to change the
Map<String, List<String>> to Map <String<Set<String>>
Let me explain it in a better way:
List list = map.get(str);
Set<String> set = new HashSet<>();
set.addAll(list);
list.clear();
list.addAll(set);
now you can use it in the way you want..
Let me know if you didnt understand any part of it
Pseudo-code: if key exists then remove it from any values (of other keys)
for (String key : map.keySet()){ // iterate through all keys
for (Map.Entry<String, List> mapEntry : map.entrySet()){ // again iterate but this time get Map.Entry
if (!mapEntry.getKey().equals(key)){ // if entry is for other key
((List)mapEntry.getValue()).remove(key); // then remove key from list
// if this map cannot be modified you can keep key, mapEntry in another map here...
}
}
}
Create a set to hold all the values previously displayed. If an item can not be added to this set, then do not add it at all.
Map<String, String[]> original = ...
Set<String> used = new HashSet<>();
Map<String, String[]> reduced = original
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> Arrays.stream(entry.getValue())
.filter(used::add)
.toArray(String[]::new)));

List all occurs of a value in a Map

I have a map:
public static Map<String, Integer> playersInArenas = new HashMap<String, Integer>();
How can I search for all strings (in left column) where Integer (right column) is for example 5?
You can use a loop and compare the value on each iteration:
// declaring map
Map<String, Integer> playersInArenas = new HashMap<String, Integer>();
playersInArenas.put("A", 5);
playersInArenas.put("B", 4);
playersInArenas.put("C", 5);
// "searching" strings
for (Entry<String, Integer> e : playersInArenas.entrySet()) {
if (e.getValue() == 5) {
System.out.println(e.getKey());
}
}
Note: Instead of printing the key you could store it, or do whatever you want with it.
try this
playersInArenas.values().retainAll(Collections.singleton(5));
Set<String> strings = playersInArenas.keySet();
If you are using java-8, you could also use the brand new Stream API.
Set<String> set = playersInArenas.entrySet()
.stream()
.filter(e -> e.getValue() == 5)
.map(e -> e.getKey())
.collect(Collectors.toSet());
What it does is:
get a Stream of all the entries of your map
apply a filter to only get the entries that have the value 5
map each entry to its key
collect the result in a Set
You could use a for each loop that iterates through the maps key set:
Map<String, Integer> playersInArenas = new HashMap<String,Integer>();
playersInArenas.put("hello", 5);
playersInArenas.put("Goodbye", 6);
playersInArenas.put("gret", 5);
for(String key : playersInArenas.keySet()){
//checks to see if the value associated with the current key
// is equal to five
if(playersInArenas.get(key) == 5){
System.out.println(key);
}

In Java 8 how do I transform a Map<K,V> to another Map<K,V> using a lambda?

I've just started looking at Java 8 and to try out lambdas I thought I'd try to rewrite a very simple thing I wrote recently. I need to turn a Map of String to Column into another Map of String to Column where the Column in the new Map is a defensive copy of the Column in the first Map. Column has a copy constructor. The closest I've got so far is:
Map<String, Column> newColumnMap= new HashMap<>();
originalColumnMap.entrySet().stream().forEach(x -> newColumnMap.put(x.getKey(), new Column(x.getValue())));
but I'm sure there must be a nicer way to do it and I'd be grateful for some advice.
You could use a Collector:
import java.util.*;
import java.util.stream.Collectors;
public class Defensive {
public static void main(String[] args) {
Map<String, Column> original = new HashMap<>();
original.put("foo", new Column());
original.put("bar", new Column());
Map<String, Column> copy = original.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> new Column(e.getValue())));
System.out.println(original);
System.out.println(copy);
}
static class Column {
public Column() {}
public Column(Column c) {}
}
}
Map<String, Integer> map = new HashMap<>();
map.put("test1", 1);
map.put("test2", 2);
Map<String, Integer> map2 = new HashMap<>();
map.forEach(map2::put);
System.out.println("map: " + map);
System.out.println("map2: " + map2);
// Output:
// map: {test2=2, test1=1}
// map2: {test2=2, test1=1}
You can use the forEach method to do what you want.
What you're doing there is:
map.forEach(new BiConsumer<String, Integer>() {
#Override
public void accept(String s, Integer integer) {
map2.put(s, integer);
}
});
Which we can simplify into a lambda:
map.forEach((s, integer) -> map2.put(s, integer));
And because we're just calling an existing method we can use a method reference, which gives us:
map.forEach(map2::put);
Keep it Simple and use Java 8:-
Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups =
mapAccountGroup.entrySet().stream()
.collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
e ->e.getValue())
);
The way without re-inserting all entries into the new map should be the fastest it won't because HashMap.clone internally performs rehash as well.
Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));
If you use Guava (v11 minimum) in your project you can use Maps::transformValues.
Map<String, Column> newColumnMap = Maps.transformValues(
originalColumnMap,
Column::new // equivalent to: x -> new Column(x)
)
Note: The values of this map are evaluated lazily. If the transformation is expensive you can copy the result to a new map like suggested in the Guava docs.
To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.
Here is another way that gives you access to the key and the value at the same time, in case you have to do some kind of transformation.
Map<String, Integer> pointsByName = new HashMap<>();
Map<String, Integer> maxPointsByName = new HashMap<>();
Map<String, Double> gradesByName = pointsByName.entrySet().stream()
.map(entry -> new AbstractMap.SimpleImmutableEntry<>(
entry.getKey(), ((double) entry.getValue() /
maxPointsByName.get(entry.getKey())) * 100d))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
If you don't mind using 3rd party libraries, my cyclops-react lib has extensions for all JDK Collection types, including Map. You can directly use the map or bimap methods to transform your Map. A MapX can be constructed from an existing Map eg.
MapX<String, Column> y = MapX.fromMap(orgColumnMap)
.map(c->new Column(c.getValue());
If you also wish to change the key you can write
MapX<String, Column> y = MapX.fromMap(orgColumnMap)
.bimap(this::newKey,c->new Column(c.getValue());
bimap can be used to transform the keys and values at the same time.
As MapX extends Map the generated map can also be defined as
Map<String, Column> y
From Java 9 onwards it is even easier to do the transformation within the map part of the stream. Is was already possible to use a new AbstractMap.SimpleImmutableEntry but the Map interface has an additional static method Map.entry which can also create an entry which can be used for this usecase.
Java 9+
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
Map<String, Column> x;
Map<String, Column> y = x.entrySet().stream()
.map(entry -> Map.entry((entry.getKey(), new Column(entry.getValue())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

Categories

Resources