Related
I have a map like this. Map<long,List<Student>> studentMap
Key is a number 1,2,3,4...
Student object is :
public class Student {
private long addressNo;
private String code;
private BigDecimal tax;
private String name;
private String city;
// getter and setters`
}
What i want to do is to convert it Map<long,List<StudentInfo>> studentInfoMap object and group id, addressNo and code fields.I want key are same for both maps.
I can group the map by using these codes but summingDouble is not working for BigDecimal.Also I cannot convert my studentMap to studentInfoMap.:(
studentInfoMap.values().stream()
.collect(
Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.summingDouble(StudentInfo::getTax))));
My studentInfo object is :
public class StudentInfo {
private long addressNo;
private String code;
private BigDecimal tax;
// getter and setters`
}
For a one-to-one conversion from Student to StudentInfo:
class StudentInfo {
public static StudentInfo of(Student student) {
StudentInfo si = new StudentInfo();
si.setAddressNo(student.getAddressNo());
si.setCode(student.getCode());
si.setTax(student.getTax());
return si;
}
}
To convert from one Map to the other:
Map<Long,List<Student>> studentMap = ...
Map<Long,List<StudentInfo>> studentInfoMap = studentMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, //same key
entry -> entry.getValue().stream()
.map(StudentInfo::of) //conversion of Student to StudentInfo
.collect(Collectors.toList()) //or simply `.toList()` as of Java 16
));
Now your grouping....
From the JavaDoc for java.util.stream.Stream<T> public abstract <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector):
The following will classify Person objects by city:
Map<String, List<Person>> peopleByCity
= personStream.collect(Collectors.groupingBy(Person::getCity));
The following will classify Person objects by state and city, cascading two Collectors together:
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)));
Note how the last example produces a Map with another Map as its values.
Now, summingDouble over StudentInfo::getTax produces a BigDecimal, not a Map. Replacing with groupingBy will work to classify Students that have the same amount for getTax:
Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>> summary =
studentInfoMap.values().stream()
.flatMap(List::stream) //YOU ALSO NEED THIS
.collect(
Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.groupingBy(StudentInfo::getTax)))
);
Edit: Retaining the 1,2,3,4 original keys
To retain the original keys you can iterate or stream the original entrySet, which contains both key and value:
Map<Long,Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>>> summaryWithKeys =
studentInfoMap.entrySet().stream() //NOTE streaming the entrySet not just values
.collect(
Collectors.toMap(Map.Entry::getKey, //Original Key with toMap
entry -> entry.getValue().stream() //group the value-List members
.collect(Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.groupingBy(StudentInfo::getTax))))
));
Just as an exercise, if you want a flat map (Map<MyKey,List>) you need a composite key MyKey
As per my comment, if you are looking to have a single flat Map, you could design a composite key, which would need to implement both equals() and hashCode() to contract. For example, this is what Lombok would generate for StudentInfo (yes, its easier to depend on lombok and use #EqualsAndHashCode):
public boolean equals(final Object o) {
if(o == this) return true;
if(!(o instanceof StudentInfo)) return false;
final StudentInfo other = (StudentInfo) o;
if(!other.canEqual((Object) this)) return false;
if(this.getAddressNo() != other.getAddressNo()) return false;
final Object this$code = this.getCode();
final Object other$code = other.getCode();
if(this$code == null ? other$code != null : !this$code.equals(other$code)) return false;
final Object this$tax = this.getTax();
final Object other$tax = other.getTax();
if(this$tax == null ? other$tax != null : !this$tax.equals(other$tax)) return false;
return true;
}
protected boolean canEqual(final Object other) {return other instanceof StudentInfo;}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long $addressNo = this.getAddressNo();
result = result * PRIME + (int) ($addressNo >>> 32 ^ $addressNo);
final Object $code = this.getCode();
result = result * PRIME + ($code == null ? 43 : $code.hashCode());
final Object $tax = this.getTax();
result = result * PRIME + ($tax == null ? 43 : $tax.hashCode());
return result;
}
You might then use StudentInfo as the composite key as follows:
Map<Long, List<Student>> studentMap = ...
Map<StudentInfo,List<Student>>> summaryMap = studentMap.values().stream()
.collect(Collectors.groupingBy(StudentInfo::of))
));
This means that you now have a nested map referenced by the composite key. Students that have exactly the same addressNo, code and tax will be part of the List referenced by each such key.
Edit: Retaining original keys
Similarly, if you wanted to retain the original keys, you could either add them into the composite key, or similar as above:
Map<Long, List<Student>> studentMap = ...
Map<Long, Map<StudentInfo,List<Student>>>> summaryMap = studentMap.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.groupingBy(StudentInfo::of)))
));
Map<Integer, Object> map = new HashMap<>();
map.put(1, studentInfoMap.values().stream().map(
student -> student.getAddressNo()
).collect(Collectors.toList()));
map.put(2, studentInfoMap.values().stream().map(
student -> student.getCode()
).collect(Collectors.toList()));
// and so ...
To convert the map from Student to StudentInfo, whilst keeping the same keys, you could do something like this:
Set<Long> keys = studentMap.keySet();
List<Long> keylist = new ArrayList<>(keys);
Map<Long, List<StudentInfo>> studentInfoMap = new HashMap<>();
for(int i = 0; i < keys.size(); i++){
long key = keylist.get(i);
List<Student> list = studentMap.get(key);
List<StudentInfo> result = new ArrayList<>();
// Create the new StudentInfo object with your values
list.forEach(s -> result.add(new StudentInfo(s.name())));
// Put them into the new Map with the same keys
studentInfoMap.put(key, result);
}
Is there a code for finding a specific value in a hashmap?
I want to use a for loop to convert values in a hashmap into an int.
for (int i = 0; i < items; i++) {
cost = Integer.parseInt(myHashmap);
}
can I even use .parseInt on a hashmap or is there another way to convert a place in a hashmap into a int?
Like String[3] is there a code to find a specific place in a hashmap?
To iterate over all values of a map, use the values method:
Map<Long, String> map = ...;
for (final String value = map.values()) {
System.out.println(value);
}
To find a specific value, iterate all values, check against your predicate and return if found:
String findValue(final Map<Long, String> map, final Predicate<String> condition) {
for (final String value = map.values()) {
if (condition.test(value)) {
return value;
}
}
return null;
}
To find the key for a given value, iterate the entry set of the map:
Long findKey(final Map<Long, String> map, final String value) {
for (final Map.Entry<Long, String> entry = map.entrySet()) {
if (Objects.equals(entry.getValue(), value)) {
return entry.getKey();
}
}
return null;
}
Of course, once you have a value (or a key), you can use it any way you like. That includes passing it as argument to Integer.parseInt.
myHashmap.values() will return all the values of the Map. Integer.parseInt(value) parses the String argument as a signed decimal integer object.
public static void main(String[] args) {
Map<String, String> myHashmap = new HashMap<>();
myHashmap.put("A", "10");
myHashmap.put("B", "20");
myHashmap.put("C", "30");
myHashmap.values().forEach(value -> {
System.out.println(Integer.parseInt(value));
// Rest of the logic
});
}
In my WebApplication I have to check many incoming query parameters from the requestBody. In order not to write the same code in every method, I want to write a function that returns a boolean. When all required parameters are received and the values of the entrySet are not null the method should return true (otherwise false), i can use the incoming query parameters later on in the programm.
Therefore I pack all incoming parameters into a HashMap. Additionally I put a specific list into the method, which provides the required parameters(keys) for checking.
Example Map of queryParams:
Map queryParams = new HashMap();
queryParams.put("id", "1");
queryParams.put("name", "Jane");
queryParams.put("lastname", "Doe");
Example Array:
String[] keys = {"id", "name", "lastname"};
Last version of method:
public static Boolean checkRequestParams(Request request, String[] keys) {
Map params = (JsonUtil.fromJson(request.body(), HashMap.class));
Iterator it = params.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
for (int i = 0; i < keys.length; i++) {
if (pair.getKey().equals(keys[i])) {
return true;
}
}
The Array provides the keys which are the QueryParams the client sent. No i want to compare them and check if the keys in the Hashmap equals to the given keys in the array and if the values of the keys in the Map are not null.
I have tried many variations. Either I got nullPointerExceptions or I always got a null return.
I might be wrong, but as I understood you want to do validate the following condition:
The HashMap keys must belong to the following list of keywords {"id", "name", "lastname"}.
No value from the HashMap should be equal to null.
You might use something similar to this:
map.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null)
So we iterate over the entrySet and check if entry key belong to the defined set and if value is not null.
Here is a more detailed example:
Set<String> keys = Set.of("id", "name", "lastname");
Map<String,List<Integer>> map = Map.of("id", List.of(1,2,3), "name", List.of(4,5,6));
map.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null);
Map<String,List<Integer>> map1 = Map.of("id", List.of(1,2,3), "not in the keys", List.of(4,5,6));
map1.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null);
Please note that I am using collections factory methods to create Map,List and Set which has been added to java-9, but stream api is available since java-8.
As for your code, you will always get true, because as soon as there is an entrySet which satisfies the condition the method will return result.
for (int i = 0; i < keys.length; i++) {
if (pair.getKey().equals(keys[i])) {
return true; // one single match found return true.
}
}
You can try to reverse the condition and return false as soon as there is a mismatch.
for (int i = 0; i < keys.length; i++) {
if (!pair.getKey().equals(keys[i]) || pair.getValue() == null) {
return false; // mismatch found, doesn't need to verify
// remaining pairs.
}
}
return true; // all pairs satisfy the condition.
I hope you find this useful.
Just using vanilla Java you could try something like this.
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ValidatorExample {
public boolean checkRequestParams(Map<String, Object> request, List<String> keys) {
return isEqualCollection(request.keySet(), keys)
&& !containsAnyNull(request.values());
}
private boolean isEqualCollection (Collection<?> a,Collection<?> b){
return a.size() == b.size()
&& a.containsAll(b)
&& b.containsAll(a);
}
private boolean containsAnyNull(Collection<?> collection){
return collection.contains(null);
}
public static void main(String[] args) {
ValidatorExample validatorExample = new ValidatorExample();
List<String> keys = Arrays.asList("id", "name", "lastname");
Map<String, Object> parametersOk = new HashMap<>();
parametersOk.put("id", "idValue");
parametersOk.put("name", "nameValue");
parametersOk.put("lastname", "lastnameValue");
// True expected
System.out.println(validatorExample.checkRequestParams(parametersOk, keys));
Map<String, Object> parametersWithInvalidKey = new HashMap<>();
parametersWithInvalidKey.put("id", "id");
parametersWithInvalidKey.put("name", "nameValue");
parametersWithInvalidKey.put("lastname", "lastnameValue");
parametersWithInvalidKey.put("invalidKey", "invalidKey");
// False expected
System.out.println(validatorExample.checkRequestParams(parametersWithInvalidKey, keys));
Map<String, Object> parametersWithNullValue = new HashMap<>();
parametersWithNullValue.put("id", null);
parametersWithNullValue.put("name", "nameValue");
parametersWithNullValue.put("lastname", "lastnameValue");
// False expected
System.out.println(validatorExample.checkRequestParams(parametersWithNullValue, keys));
}
}
But I would recommend you to use a validation framework if your project allows it for a more accurate validation.
Should not return immediately if a match is found as we want to test 'all required' parameters. Try something like:
String[] keys = {"id, "name", "lastname"};
public static Boolean checkRequestParams(Request request, String[] keys) {
Map params = (JsonUtil.fromJson(request.body(), HashMap.class));
for (int i = 0; i < keys.length; i++) {
Iterator it = params.entrySet().iterator();
boolean found = false;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (pair.getKey().equals(keys[i])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
You are returning true on the first matching key, whereas you want to check whether all keys are present. Further, your code is incomplete, hence, it is impossible to give full diagnostics.
But anyway, there’s no sense in iterating over map here. Just use
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
for(String key: keys) {
if(params.get(key) == null) return false;
}
return true;
}
This will ensure that each key is present and not mapping to null (as “not mapping to null” already implies being present).
When not considering the possibility of an explicit mapping to null, you could check the presence of all keys as simple as
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
return params.keySet().containsAll(Arrays.asList(keys));
}
Alternatively, you could consider a map invalid if any mapped value is null, even if its key is not one of the mandatory keys. Then, it would be as simple as
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
return params.keySet().containsAll(Arrays.asList(keys))
&& !params.values().contains(null);
}
I am trying to implement a logic where I have a POJO class which has 7 attributes.
I have added these POJO classes into the map depends upon the value of the attributes.
Below is the implementation
Map<String,List<PriceClass>> map = new HashMap();
for (PriceClass price : prices) {
if (price.getAttribute1() !=null) {
if (map.get("attribute1") !=null) {
map.get("attribute1").add(price);
} else {
map.set("attibute1",Collections.singletonList(price))
}
} else if(price.getAttribute2()!=null) {
if (map.get("attribute12") !=null) {
map.get("attribute2").add(price);
} else {
map.set("attibute2",Collections.singletonList(price))
}
} else if (price.getAttribute3() !=null) {
.
.
.
} else if (price.getAttribute7() !=null) {
//update the map
}
}
My question is rather than writing these many if loops are there any generalize implementations I can try here.
You may use
Map<String,List<PriceClass>> map = new HashMap<>();
for(PriceClass price: prices) {
HashMap<String,Object> options = new HashMap<>();
options.put("attibute1", price.getAttribute1());
options.put("attibute2", price.getAttribute2());
options.put("attibute3", price.getAttribute3());
options.put("attibute4", price.getAttribute4());
options.put("attibute5", price.getAttribute5());
options.put("attibute6", price.getAttribute6());
options.put("attibute7", price.getAttribute7());
options.values().removeIf(Objects::isNull);
options.keySet().forEach(attr -> map.computeIfAbsent(attr, x -> new ArrayList<>())
.add(price));
}
or generalizing the process:
Prepare a unmodifiable map once
static final Map<String, Function<PriceClass,Object>> ATTR;
static {
Map<String, Function<PriceClass,Object>> a = new HashMap<>();
a.put("attibute1", PriceClass::getAttribute1);
a.put("attibute2", PriceClass::getAttribute2);
a.put("attibute3", PriceClass::getAttribute3);
a.put("attibute4", PriceClass::getAttribute4);
a.put("attibute5", PriceClass::getAttribute5);
a.put("attibute6", PriceClass::getAttribute6);
a.put("attibute7", PriceClass::getAttribute7);
ATTR = Collections.unmodifiableMap(a);
}
and use either
Map<String,List<PriceClass>> map = new HashMap<>();
for(PriceClass price: prices) {
HashMap<String,Object> options = new HashMap<>();
ATTR.forEach((attr,func) -> options.put(attr, func.apply(price)));
options.values().removeIf(Objects::isNull);
options.keySet().forEach(attr -> map.computeIfAbsent(attr, x -> new ArrayList<>())
.add(price));
}
or
Map<String,List<PriceClass>> map = prices.stream()
.flatMap(price -> ATTR.entrySet().stream()
.filter(e -> e.getValue().apply(price) != null)
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), price)))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
A likely optimal solution would be similar to one I have suggested earlier today.
Use the Map<String, Optional<?>> to store the Optional values of the checked attributes with a key of the future output map key.
Map<String, Optional<?>> options = new HashMap<>();
options.put("attribute1", Optional.ofNullable(price.getAttribute1()));
// ...
options.put("attribute3", Optional.ofNullable(price.getAttribute2()));
// ...
Using the iteration of the indices would let you perform the update of a map.
Map<String,List<Price>> map = new HashMap();
for (int i=1; i<7; i++) { // attributes 1..7
String attribute = "attribute" + i; // attribute1...attribute7
options.get(attribute).ifPresent(any -> // for non-nulls
map.put( // put to the map
attribute, // attribute as key remains
Optional.ofNullable(map.get(attribute)) // gets the existing list
.orElse(new ArrayList<>()) // or creates empty
.add(price))); // adds the current Price
}
Moreover, I bet your intention was a bit different. There is no method Map::set
map.set("attibute1",Collections.singletonList(price))
Didn't you mean to put a List<Price> with one item to the very same key instead?
map.put("attibute1", Collections.singletonList(price))
For this reason you can use the way I posted above.
What about using e.g. Enum to define 7 different objects each of them is responsible for concrete attribute:
// this is client code, looks pretty easy
Map<String, List<PriceClass>> map = new HashMap<>();
for (PriceClass price : prices)
PriceAttribute.add(map, price);
// all logic is hidden within special Enum
enum PriceAttribute {
ATTRIBUTE1("attribute1", PriceClass::getAttribute1),
ATTRIBUTE2("attribute2", PriceClass::getAttribute2),
ATTRIBUTE3("attribute3", PriceClass::getAttribute3),
ATTRIBUTE4("attribute4", PriceClass::getAttribute4),
ATTRIBUTE5("attribute5", PriceClass::getAttribute5),
ATTRIBUTE6("attribute6", PriceClass::getAttribute6),
ATTRIBUTE7("attribute7", PriceClass::getAttribute7);
private final String key;
private final Function<PriceClass, ?> get;
PriceAttribute(String key, Function<PriceClass, ?> get) {
this.key = key;
this.get = get;
}
public static void add(Map<String, List<PriceClass>> map, PriceClass price) {
for (PriceAttribute attribute : values()) {
if (attribute.get.apply(price) != null) {
map.computeIfAbsent(attribute.key, key -> new ArrayList<>()).add(price);
break;
}
}
}
}
Following is repetitive code:
if(map.get("attribute1") !=null)
{
map.get("attribute1").add(price);
}
else
{
map.set("attibute1",Collections.singletonList(price))
}
It could be refactored into a method, and called from the parent method which could make this look a bit cleaner.
Additionally you can also try
prices.removeAll(Collections.singleton(null)) and then run loop through it, to avoid one "If" condition.
Following code snippet can be added as a method.
if(map.get("attribute1") !=null) {
map.get("attribute1").add(price);
} else {
map.set("attibute1",Collections.singletonList(price))
}
to
private static void addPrice(String attributeName, Price price){
if(map.get(attributeName) !=null) {
map.get(attributeName).add(price);
} else {
map.set(attributeName,Collections.singletonList(price))
}
}
Also, map should be created static to be used in this case.
That wasn't be strict answer to the question but I want to improve your code.
You call get twice. Instead of this:
if(map.get("attribute1") !=null) {
map.get("attribute1").add(price);
} else {
map.set("attibute1",Collections.singletonList(price))
}
Use this:
final List<PriceClass> attribute1 = map.get("attribute1");
if (attribute1 != null) {
attribute1.add(price);
} else {
map.set("attibute1", Collections.singletonList(price))
}
Second you use Collections.singletonList which create immutable list so if you try to add something to it (and you do it) you'll get exception. You should use
new ArrayList<PriceClass>(Arrays.asList(price)) or
Stream.of(price).collect(toList())
If this is the real code you have, so create an array of attribute
Attribute[] attributesOf(Price price){
Attribute[] a = new Attribute[7];
a[0] = price.getAttribute1();
a[1] = price.getAttribute2();
...
a[6] = price.getAttribute7();
}
when you have more attributes, just modify this method. Then your code can be refactor to
for(PriceClass price : prices){
Attribute[] attributes = attributesOf(price);
for(int i=0;i<attributes.length;i++){
String key = "attribute" + (i+1);
if(attributes[i] != null){
map.get(key).add(price);
}
else{
map.set(key, Collections.singletonList(price));
}
}
}
But if you code are different, like price.getGrossAmount(), price.getNetAmount(), price.getTax() you have to find the general contract to define the type of array.
Nevertheless, you have to understand that good data structure will make your code simple and perform well. Maybe you don't need to define attribute in the price class. Maybe you don't need the price class, but you can use BigDecimal as the price value instead.
I don't know your context well, I don't know what are you doing. I think you might have a better answer from someone else if you show the real code as well as its context.
Acc. to your question, it seems like if attibute1 is present in a PriceClass object, it will go to the attibute1 key. This means if all objects in prices list has attibute1, the whole list will go the attibute1 key.
With the above assumption, here is a java-8 solution containing streams.
public static void main(String[] args){
Map<String,List<PriceClass>> map = new HashMap<>();
List<PriceClass> prices =new ArrayList<>();
prices.add(new PriceClass(1,2,3,4,5,6,7));
prices.add(new PriceClass(null,12,13,14,15,16,17));
map = prices.stream()
.map(priceClass -> Arrays
.stream(PriceClass.class.getDeclaredFields())
.map(field -> getFieldValue(field, priceClass))
.filter(Objects::nonNull)
.findFirst()
.orElse(null)
)
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey,
Collectors.mapping(AbstractMap.SimpleEntry::getValue,
Collectors.toList())));
System.out.println(map);
}
private static AbstractMap.SimpleEntry<String,PriceClass> getFieldValue(Field field, PriceClass priceClass){
Optional<Integer> value = Optional.empty();
try {
value = Optional.ofNullable((Integer)field.get(priceClass));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (value.isPresent()) {
return new AbstractMap.SimpleEntry<>(field.getName(), priceClass);
}
return null;
}
Output:
{attribute1=[PriceClass{attribute1=1, attribute2=2, attribute3=3, attribute4=4, attribute5=5, attribute6=6, attribute7=7}],
attribute2=[PriceClass{attribute1=null, attribute2=12, attribute3=13, attribute4=14, attribute5=15, attribute6=16, attribute7=17}]}
I want to find keys in a map with a pattern matching.
Ex:-
Map<String, String> map = new HashMap<String, String>();
map.put("address1", "test test test");
map.put("address2", "aaaaaaaaaaa");
map.put("fullname", "bla bla");
From above map, I want to get the values of keys which has prefix of "address". So as in this example output should be the first two results ("address1" and "address2").
How can I achieve this dynamically?
You can grab the keySet of the map and then filter to get only keys that starts with "address" and add the valid keys to a new Set.
With Java 8, it's a bit less verbose:
Set<String> set = map.keySet()
.stream()
.filter(s -> s.startsWith("address"))
.collect(Collectors.toSet());
If you have Java 8 features, something like this should work:
Set<String> addresses = map.entrySet()
.stream()
.filter(entry -> entry.getKey().startsWith("address"))
.map(Map.Entry::getValue)
.collect(Collectors.toSet());
Something like this:
for (Entry<String, String> entry : map.entrySet()) {
if (entry.getKey().startsWith("address")) {
// do stuff with entry
}
}
I created an interface...
import java.util.Map;
#FunctionalInterface
public interface MapLookup {
<V> List<V> lookup(String regularExpression, Map<String,V> map);
}
And the implementation
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class MapLookupImpl implements MapLookup {
#Override
public <V> List<V> lookup(String regularExpression, Map<String, V> map) {
final Pattern pattern = Pattern.compile(regularExpression);
List<String> values = map.keySet()
.stream()
.filter(string -> pattern.matcher(string).matches())
.collect(Collectors.toList());
if(values!= null && !values.isEmpty()){
return values.stream().map((key) -> map.get(key)).collect(Collectors.toList());
}
return new ArrayList<>();
}
}
The test
public static void main(String[] args){
Map<String, Integer> map = new HashMap<>();
map.put("foo",3);
map.put("bar",42);
map.put("foobar",-1);
MapLookup lookup = new MapLookupImpl();
List<Integer> values = lookup.lookup("\\woo\\w*",map);
System.out.println(values);
}
The result
[-1, 3]
Or maybe that's overkill. I can see a repeated use for this, though.
For those who want the pre-java8 version:
public class PreJava8MapLookup implements MapLookup {
#Override
public <V> List<V> lookup(String regularExpression, Map<String, V> map) {
Matcher matcher = Pattern.compile(regularExpression).matcher("");
Iterator<String> iterator = map.keySet().iterator();
List<V> values = new ArrayList<>();
while(iterator.hasNext()){
String key = iterator.next();
if(matcher.reset(key).matches()){
values.add(map.get(key));
}
}
return values;
}
}
I came across a similar need and attempted implementing a POC for such a data structure. I came to the conclusion its much more practical to partition data in some manner :)
However, if you really have your mind set on implementing something like that you would need a structure more similar to a trie tree. Here is what I got (my apologies since the code is in Scala but it can easily be adapted and if you put your mind to it you can probably finish it and make it useable)
package component.datastructure
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
class RegExpLookup[T] {
private val root = new mutable.HashMap[Char, Node]
def put(key: String, value: T): Unit = {
addNode(key.toCharArray, 0, root, value)
println(root.toString)
}
private def addNode(key: Array[Char], charIdx: Int,
currentRoot: mutable.Map[Char, Node], value: T): Unit = {
if (charIdx < key.length - 1) {
if (currentRoot.contains(key(charIdx))) {
addNode(key, charIdx + 1, currentRoot(key(charIdx)).nodeRoot, value)
} else {
val node = Node(null, new mutable.HashMap[Char, Node])
currentRoot.put(key(charIdx), node)
addNode(key, charIdx + 1, node.nodeRoot, value)
}
} else {
currentRoot.put(key(charIdx), Node(value, null))
}
}
private def getAll(lastNode: Node, buffer: ArrayBuffer[T]): Unit = {
if (lastNode.value != null)
buffer.append(lastNode.value.asInstanceOf[T])
if (lastNode.nodeRoot != null)
lastNode.nodeRoot.values.foreach(e => {
getAll(e, buffer)
})
}
def get(key: String): Iterable[T] = {
val t = findLastNode(key.toCharArray, 0, root)
println("getting from " + root)
val isLast = t._2
if (isLast) {
val v = t._1.value
if (v != null)
return List(v.asInstanceOf[T])
else
return null
} else {
val buffer = new ArrayBuffer[T]()
getAll(t._1, buffer)
return buffer.toList
}
}
private def findLastNode(key: Array[Char], charIdx: Int,
root: mutable.Map[Char, Node]): (Node, Boolean) = {
if (charIdx < key.length - 2 && (key(charIdx + 1) != '*')) {
return (root(key(charIdx)), false)
} else if (charIdx < key.length - 1) {
return findLastNode(key, charIdx + 1, root(key(charIdx)).nodeRoot)
} else
return (root(key(charIdx)), true)
}
}
case class Node(value: Any, private[datastructure] val nodeRoot: mutable.HashMap[Char, Node]) {
}
Basically the idea is we look up every character in a subsequent map the complexity would now be the length of the key. Which, really, should be an acceptable limitation since compilation of a reg ex is likely O(N) anyways. Also in cases where you have shorter keys and many entries would yield much better performance then iterating over all the keys. If you swap the mutable.HashMap with some kind of own implementation with clever hashing and take advantage of the fact that a character is really an int, and in case of ASCII strings (which will likely be the key) actually a short. It would also be more difficult if you're looking up some more complex expression then something*, but still likely doable.
edit: a test
class MySpec extends PlaySpec {
val map = new RegExpLookup[String]()
"RegExpLookup" should {
"put a bunch of values and get all matching ones" in {
map.put("abc1", "123")
map.put("abc2", "456")
map.put("abc3", "789")
val result = map.get("abc*")
println(result)
val s = result.toSet
assert(s.contains("123"))
assert(s.contains("456"))
assert(s.contains("789"))
}
"put a single value and get it by exact key" in {
map.put("abc", "xyz")
val result = map.get("abc")
println(result)
assert(result.head.equals("xyz"))
}
}
}
You will have to loop through the Key Set and match the pattern
for(String key : map.keySet()) {
if(! key.startsWith("address")) {
continue;
}
// do whatever you want do as key will be match pattern to reach this code.
}
If you don't need big performance, browsing all the keys on your map (map.entrySet) to get the ones matching your pattern should be enough.
If you need good performance, a solution i have used to solve this kind of problem is to use an in-memory database such as H2: you put your data in a memory table, create unique index on the key and you will get good performance for the 2 cases:
Getting a value associated to the key (select value from in_mem_table where key = ?'), classic usage of an hashmap
Getting values associated to a "key pattern" (select value from in_mem_table where key like 'adress%')
One way is to create a function that searches all the map for keys starting with address but that would remove the advantage of the map, since the objective is probably to be fast.
Another way is to create a list or array containing all keys starting with address, but that is only worth if you just want the keys starting with address.
Now do you need to be able to search for anything or just a specific thing? And do you need the map or can it be another thing like an array or list?