Java HashMap key fits to a pattern? - java

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

Related

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);
});

How to Map Values to a Nested TreeMap

Nested TreeMap:
TreeMap<String,TreeMap<String,TreeMap<String,String>>> map = new TreeMap<>();
Trying to map things to the TreeMap:
add("1","1","1","111",map);
map.add("1","1","1","111");
map.put("1", ("1",("1","111")));
I am trying to map things to the nested TreeMap as seen above, but nothing I have tried has worked. What is the proper way to do what I am attempting?
You'll have to do it one step at a time, e.g. the simple case assuming all child maps already exist (note that we get the existing maps):
map.get("1").get("1").put("1", "111");
However, this isn't the case based on your description, and so each step of the way you'll have to create a new entry if one doesn't exist, so it becomes more complicated, as you have to look up the current map and then create/add:
// from your example:
String key1 = "1";
String key2 = "1";
String key3 = "1";
String value = "111";
// insert if doesn't exist yet:
TreeMap<String,TreeMap<String,String>> map1 = map.get(key1);
if (map1 == null) {
map1 = new TreeMap<String,TreeMap<String,String>>();
map.put(key1, map1);
}
// and again:
TreeMap<String,String> map2 = map1.get(key2);
if (map2 == null) {
map2 = new TreeMap<String,String>();
map1.put(key2, map2);
}
// and now we're set up and ready to go:
map2.put(key3, map3);
Since it's kind of cumbersome, it generally helps to write a utility function to do this for you.
Alternatively, if it is appropriate for your application, you could consider collapsing your entire structure into a single map and using a more complex key, for example:
static class ComplicatedKey implements Comparable<ComplicatedKey> {
String key1;
String key2;
String key3;
public ComplicatedKey (String key1, String key2, String key3) { ... }
// implement equals and compareTo appropriately.
}
Then:
TreeMap<ComplicatedKey,String> map = ...;
map.put(new ComplicatedKey("1", "1", "1"), "111");
Yet another option is to roll your own multi-level tree, you could even use a TreeMap internally in each of your nodes to maintain lists of child nodes.

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;
}

remove similar (redundant) strings from Arraylist

I'm trying to remove similar strings from an ArrayList but I'm getting this error:
CurrentModificationException
and here is my method where I pass my original arrayList (old) and get a new list without redundant strings.
ArrayList<String> removeRed(ArrayList<String> old) throws IOException
{
ArrayList<String> newList = new ArrayList<String>();
for (int i=0; i< old.size(); i++)
{
if(newList.size() < 1)
{
newList.add(old.get(0));
} else{
for(Iterator<String> iterator = newList.iterator(); iterator.hasNext();) {
while(iterator.hasNext())
{
if(!ChopMD((String) iterator.next()).equals(ChopMD(old.get(i))))
{
newList.add(old.get(i));
Log.e("new algo", "" + old.get(i) );
}
}
}
}
}}
Note that my ChopMD() returns a particular string and it works fine.
It works fine for the first few strings, this it throws that exception. Any suggestion to resolve this issue would be appreciated it. Thanks.
If you have no problems with using the standard library (always preferable, why reinvent the wheel) try
List<String> uniques = new ArrayList<String>(new HashSet<String>(oldList));
The HashSet will only contain unique strings and the ArrayList constructor takes any Collection (including a HashSet) to build a list from.
Judging from your comments it seems like you are trying to implement an Associative Array with unique keys using an ArrayList. The better approach is to use a Map implementation like HashMap to pair IDs with their associated Strings.
Map<Integer, String> map = new HashMap<>();
map.put(1, "This string corresponds to ID=1");
map.put(3, "Donald Ducks Nephews");
map.put(7, "Is a Prime");
Then to get a value associated with an ID:
int key = someObject.getID();
String value = map.get(key);
All the Map implementations use unique keys so there is no need for you to check for redundant IDs, if you try to add a new (key,value) pair the value associated with the ID will be replaced if the map contains the key.
map.put(1, "New String");
String s = map.get(1); //s will no longer be "This string corresponds to ID=1"
If you don't want this behavior you have the choice of either subclassing one of the Map implementations to ignore .put(key, value) if the map contains key,value or delegating .put(key,value) to some other class.
Subclassing:
public class UniqueValueHashMap<K,V> extends HashMap<K, V>{
#Override
public V put(K key, V value) {
if (containsKey(key))
return null;
return super.put(key, value);
}
Delegating
public class SomeClass {
private Map<Integer, String> map = new HashMap<>();
// ...stuff this class does
public String put(int key, String value) {
if (map.containsKey(key))
return null;
return map.put(key, value);
}
// ...more stuff this class does
}
Delegation is the better approach, notice how you can change the map implementation (using maybe a TreeMap instead of HashMap) without introducing a new class where you override the .put(key,value) of TreeMap.
You can iterate much easier by this
for (String oldString : old){
for (String newString : newList){
}
}
Also you can use Set to have unique strings
Set<String> newList = new HashSet<String>();
Your error is because you are changing the list WHILE it is still iterated.

Concatenating two hashmaps without removing common entires from both the maps

I have two hashmaps, in particular vocabs of two languages say english and german.I would like to concatenate both these map to return a single map.I tried :
hashmap.putall()
But, removed some of the entries which are common in both maps and replace it by single entry only.But i want to keep both the vocabs intact just concatenate those. Is there any method to do it? if not any other way to do. I would prefer any methods in hashmap.
[EDIT]
To make more clear, lets see two maps
at the 500 um die 500
0 1 2 0 1 2
resutls into
at the 500 um die 500
0 1 2 3 4 5
You'll have to write your own custom "putAll()` method then. Something like this would work:
HashMap<String> both = new HashMap<String>(english);
for(String key : german.keySet()) {
if(english.containsKey(key)) {
both.put(key, english.get(key)+german.get(key));
}
}
This first copies the English HashMap. Then puts in all the German words, concatenating if there is a duplicate key. You might want some kind of separator character like a / in between so you can later extract the two.
There isn't anything like that in the Java main library itself, you will have to use something provided by third parties like Google Guava's Multimap, it does exactly what you want, or build something like this manually.
You can download the Guava library at the project's website. Using a multimap is the same as using a map, as in:
Multimap<String,String> both = new ArrayListMultimap <String,String>();
both.putAll( german );
both.putAll( english);
for ( Entry<String,String> entry : both.entrySet() ) {
System.out.printf( "%s -> %s%n", entry.getKey(), entry.getValue() );
}
This code will print all key-value pairs including the ones that are present on both maps. So, if you have me->me at both german and english they would be printed twice.
You cannot do that directly with any Map implementation, since in a map, each key is unique.
A possible workaround is to use Map<Key, List<Value>>, and then do the concatenation of your maps manually. The advantage of using a List for the concatenated map, is that it will be easy to retrieve each of the individual values without any extra fiddling.
Something like that would work:
public Map<Key, List<Value>> concat(Map<Key, Value> first, Map<Key, Value> second){
Map<Key, List<Value>> concat = new HashMap<Key, List<Value>>();
putMulti(first, concat);
putMulti(second, concat);
return concat;
}
private void putMulti(Map<Key, Value> content, Map<Key, List<Value>> dest){
for(Map.Entry<Key, Value> entry : content){
List<Value> vals = dest.get(entry.getKey());
if(vals == null){
vals = new ArrayList<Value>();
dest.put(entry.getKey(), vals);
}
vals.add(entry.getValue());
}
}
Similar to #tskuzzy's answer
Map<String, String> both = new HashMap<String, String>();
both.putAll(german);
both.putAll(english);
for (String e : english.keySet())
if (german.containsKey(e))
both.put(e, english.get(e) + german.get(e));
Slight improvisation of #tskuzzy and #Peter's answer here. Just define your own StrangeHashMap by extending HashMap.
public class StrangeHashMap extends HashMap<String, String> {
#Override
public String put(String key, String value) {
if(this.containsKey(key)) {
return super.put(key, super.get(key) + value);
} else {
return super.put(key, value);
}
}
}
You can use it as so:
Map<String, String> map1 = new HashMap<String, String>();
map1.put("key1", "Value1");
map1.put("key2", "Value2");
Map<String, String> map2 = new HashMap<String, String>();
map2.put("key1", "Value2");
map2.put("key3", "Value3");
Map<String, String> all = new StrangeHashMap();
all.putAll(map1);
all.putAll(map2);
System.out.println(all);
The above prints the below for me:
{key3=Value3, key2=Value2, key1=Value1Value2}
Given the new elements in the question, it seems that what you actually need to use is lists. In this case, you can just do:
List<String> english = ...;
List<String> german = ...;
List<String> concat = new ArrayList<String>(english.size() + german.size());
concat.addAll(english);
concat.addAll(german);
And there you are. You can still use concat.get(n) to retreive the value nth value in the concatenated list.

Categories

Resources