Create a String-parsing method building a Map in a generic way? - java

Preface: This is not an actual problem that I have, it just came to my mind in a "What if... ...how would I do that?" fashion.
When I have Strings consisting of several key-value pairs (like 123=456;321=654;89=90), I can make a Map from that ({123=456, 321=654, 89=90}) pretty easily with a method like this:
public static Map<Integer, Integer> makeMap(String theString) {
String[] chunks = theString.split(";");
Map<Integer, Integer> result = new HashMap<>(chunks.length);
for (String chunk : chunks) {
String[] chunksChunks = chunk.split("=");
int key = Integer.parseInt(chunksChunks[0]);
int value = Integer.parseInt(chunksChunks[1]);
result.put(key, value);
}
return result;
}
Is there any elegant way to "widen" this method to be a generic method, accepting e.g. all (wrappers for) primitive types?
It would be possible to write...
public static <K extends Object, V extends Object> Map<K, V> makeMapGeneric(String theString) {
// ???
}
...but I have no idea how I would do the "castings" to the keys and values.
As far as I know, the primitive types do not have any common makeXYfromString(String ...) method, just explicit Integer.parseInt, Double.parseDouble and so on, and they do not have a common superclass/interface that I could restrict K and V to.
Giving the classes as argument (makeMapGeneric(String theString, Class<K> keyClass, Class<V> valueClass)) and writing something like K key = keyClass.cast(keyString);, isn't possible since you cannot cast a String to eg. an int, just parse it.
Is there any elegant solution possible?

I took a tought on it for a few minutes and i came up with this solution
public static <K, V> Map<K, V> makeMap(String input, Function<String, K> keyFunc, Function<String, V> valFunc) {
return Arrays.stream(input.split(";"))
.map(s -> s.split("="))
.collect(Collectors.toMap(s -> keyFunc.apply(s[0]), s -> valFunc.apply(s[1])));
}
You need to pass a two functions which will transform the string to the right value.
Use it like this:
Map<Integer, Integer> x = makeMap("123=456;321=654;89=90", Integer::parseInt, Integer::parseInt);

You could provide a Function to you method:
<K, V> Map<K, V> makeMapGeneric(String theString, Function<String, K> keyFn, Function<String, V> valueFn) {
String key = "123";
String value = "456";
K parsedKey = keyFn.apply(key);
V parsedValue = valueFn.apply(key);
}
Now you can call it with a Function that converts String to K (and V):
Map<Integer, Double> result =
makeMapGeneric("123=456", Integer::parseInt, Double::parseDouble);

Related

Function that takes various arguments of various type and returns certain object in functional Java

Let's say I've got some lambda expressions as below:
Function<String, List<String>> flines = fileName -> {
//Puts all lines from file into List and returns a list
};
Function<List<String>, String> join = listOfLines -> {
//Concatenates all lines from list and returns them as String
};
Function<String, List<Integer>> collectInts = str -> {
//Returns all Integer occurences from given String
};
And I want to create a function, let's say resultType convertBy(Function<T,S>...args)
So I can combine arguments and return cerain result:
List<String> lines = fileConv.convertBy(flines) //returns list of lines
String text = fileConv.convertBy(flines, join); //returns s String from concatenated Strings
List<Integer> ints = fileConv.convertBy(flines, join, collectInts); //returns list of Integers from string
Integer sumints = fileConv.convertBy(flines, join, collectInts, sum); ////returns list of Integers from string and sums it
Is it somehow possible to do in Java?
EDIT: I CAN'T use overloading
When using generics, you need to declare the type variables involved. Since defining a method which chains calls using a variable number of generic functions (with varargs) would require a variable number of type variables, that's not possible to do.
It would not be possible, at compile time, to guarantee that each of functions given with varargs would use types so that they are compatible when chaining the calls.
You can do it, but not in a type-safe way. Any mismatch on the input/output types of the functions will result in a ClassCastException at runtime.
private static <T, U> U convertBy(T arg, Function... functions) {
Object result = arg;
for (Function f : functions) {
result = f.apply(result);
}
return (U) result;
}
#Test
public void test() {
Function<String, Integer> f1 = s -> s.length();
Function<Integer, Double> f2 = i -> i*2.0;
Double d = convertBy("test", f1, f2);
assertThat(d).isEqualTo(8.0);
}
You can, however, manually define variants of that method that does the chaining by overloading it:
private static <T, U> U convertBy(T arg, Function<T, U> func1) {
return func1.apply(arg);
}
private static <T, U, V> V convertBy(T arg, Function<T, U> func1, Function<U, V> func2) {
return func2.apply(func1.apply(arg));
}
private static <T, U, V, X> X convertBy(T arg, Function<T, U> func1, Function<U, V> func2, Function<V, X> func3) {
return func3.apply(func2.apply(func1.apply(arg)));
}

Method info( java.util.HashMap ) not found in class'org.apache.log.Logger'

log.info(m.differenceValue(jsonElement1,jsonElement2));
calling function from beanshell. The code implemented in jar file.
public static <K, V> Map<String,Object> differenceValue(JsonElement json1, JsonElement json2){
Gson g = new Gson();
Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
Map<String,Object> firstMap = g.fromJson(json1, mapType);
Map<String, Object> secondMap = g.fromJson(json2, mapType);
return(mapDifference(firstMap,secondMap));
}
public static <K, V> Map<K, V> mapDifference(Map<? extends K, ? extends V> left, Map<? extends K, ? extends V> right) {
Map<K, V> difference = new HashMap<K, V>();
difference.putAll(left);
difference.putAll(right);
difference.entrySet().removeAll(right.entrySet());
return difference;
}
Is is working fine in eclipse but in jmeter it is throwing
error:Method info( java.util.HashMap ) not found in class'org.apache.log.Logger'
try log.info(m.differenceValue(jsonElement1,jsonElement2).toString());
according to documentation, that might work for HashMap (depending on what you have for the keys & values there)
public String toString()
Returns a string representation of this map.
The string representation consists of a list of key-value mappings in
the order returned by the map's entrySet view's iterator, enclosed in
braces ("{}"). Adjacent mappings are separated by the characters ", "
(comma and space). Each key-value mapping is rendered as the key
followed by an equals sign ("=") followed by the associated value.
Keys and values are converted to strings as by String.valueOf(Object).
You are trying to pass a Map to Logger while it accepts only Strings for info(), warn(), etc. methods to you will need to cast the Map to String somehow.
Also I don't think you have generics support in Beanshell, consider switching to JSR223 Elements and Groovy language instead.

How to use an objects attribute as method parameter to set a map key?

I would like to have a method that maps a List to a NavigableMap. The method call expects an parameter that is used as map key. This parameter is an attribute of the list objects.
Something like this, so both calls are ok:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject:.getString());
NavigableMap<Date, MyObject> dateKeyMap = asNavMap(list, MyObject::getDate());
I dont know how to define the second parameter (MyObject::getDate()). Do I have to use a lambda expression (p -> p.getDate()) or something like Predicate or Function?
I've tried to derive a solution from Approach 8 (or simular) from http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html, but I don't know how to do.
This is what I have done so far:
The concrete implementation:
public class ConcreteConverter {
public static NavigableMap<Integer, Pair<Integer, String>> asNavMap(List<Pair<Integer, String>> pairs) {
NavigableMap<Integer, Pair<Integer, String>> navMap = new TreeMap<>();
for (Pair<Integer, String> pair : pairs) {
navMap.put(pair.getKey(), pair);
}
return navMap;
}
public static void main(String[] args) {
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> map = ConcreteConverter.asNavMap(pairs);
}
}
class Pair<K, V> {
K key;
V val;
// constructor, getter, setter
}
Here I stuck (??? is an attribute of the Pair object):
public static <K, V> NavigableMap<K, V> asNavMap(List<V> items, ???) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items) {
navMap.put(???, item);
}
return navMap;
}
Please notice I have barely experiences writing generic methods or using lambda functions/interfaces.
Any help is appreciated.
Edit 1
As Nick Vanderhofen mentioned I didn't clarify the search for a generic solution.
You can do that with a Function. You keep the code you wanted:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject::getKey);
The method asNavMap can then take a Function:
private NavigableMap<String,MyObject> asNavMap(List<MyObject> list, Function<MyObject, String> getKey) {
//the actual mapping goes here
}
The getKey method you are specifying can either be a simple getter on the MyObject:
public String getKey(){
return key;
}
Or you could create a static method to get the same result:
public static String getKey(MyObject myObject){
return myObject.getKey();
}
To apply the function you can just use the apply method:
String key = getKey.apply(someObject);
For the actual mapping implementation you can keep your for loop, or you could rewrite it using java 8 and re-use the Function that you got as a parameter in the collector. However, since you want a TreeMap, the syntax is quite verbose:
items.stream().collect(Collectors.toMap(mapper, Function.identity(), (a,b) -> a, TreeMap::new));
Just figured out a working solution!
Still reading http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#approach7 I've tried to use Function, and now this is my solution:
public static <K, V> NavigableMap<K, V> asNavigableMap(List<V> items, Function<V, K> mapper) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items)
navMap.put(mapper.apply(item), item);
return navMap;
}
And these calls work:
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> navI2P1 = GenericConverter.asNavigableMap(pairs, Pair::getKey);
NavigableMap<String, Pair<Integer, String>> navI2P2 = GenericConverter.asNavigableMap(pairs, Pair::getVal);
It was hard for me to understand the Function functional interface and the apply method.
Thanks to anyone!

Java: Stacked (layered) maps behind a Map interface?

I need a Map impl which would consist of stacked maps, which I could push() and pop(), and the values would be "added" or "removed" if they belong to the map being pushed/popped. And the values would be searched top/bottom (or optionally bottom/top).
Is there an existing impl in JDK or elsewhere?
Example:
Stack
map4
foo => aaa
bar => 45
map3
bar => 22
map2
foo => ccc
baz => uuu
map1
For this, get("baz") would return "uuu", get("foo") would return "aaa", size() would return 3 etc.
It's something like JavaScript's prototypal inheritance.
There's one impl
I'm wishing for some more sophisticated impl, which wouldn't really go through all layers every time I call any method. Read methods are going to be more often than push()/pop(), so there could be some pre-computation during that.
You can have Stack as a wrapper. Have a Map<'String, Map> ( String here is for the name of the map). Expose push and pop as API. Interesting part of your question is, defining push and pop? How would the signature of these method actually look like? Actually, not very clear is to what you are trying to achieve?
So, there is no such builtin structure in the JDK, but it can be implemented using a LinkedList containing Maps.
LinkedList implements all three of List, Queue and Deque, maybe it's a little overkill, but ohwell...
A sample code would be as follows; however, the Map interface is not really obeyed (curious how you'd do .equals() and .hashCode() here? Not even talking about .clear()):
public final class StackedMap<K, V>
implements Map<K, V>
{
private final Map<K, V> NO_MAP = new HashMap<K, V>();
private final LinkedList<Map<K, V>> maps = new LinkedList<>();
private Map<K, V> currentMap = NO_MAP;
public void push(Map<K, V> map)
{
maps.push(map);
currentMap = map;
}
public Map<K, V> pop()
{
return currentMap = maps.pop();
}
#Override
public V get(K key)
{
V ret;
for (final Map<K, V> map: maps)
if ((ret = map.get(key)) != null)
break;
return ret;
}
// etc
}
Untested etc.

How to store the parts of the string in dictionary in java

I want to store string split-ted into two parts into dictionary in 0th and 1st position.
Suppose value is frequency:50 then frequency will be in 0th place and 50 will be in 1st place in dictionary
following is the C# code for same
data_dictionary = data.Split(',')
.ToDictionary(item => item.Split(':')[0], item => item.Split(':')[1]);
I want the solution for same in java,any help will be greatly appreciated
Sorry, without closures Java can't approach the elegance of your C# code. You may find something that uses anonymous classes to approximate this, but it will not be really worth it. So just write out a regular loop that puts stuff into a HashMap<String, String>. You can split the string the same way, using String#split(regex).
int delim = data.indexOf(':');
System.out.println( data.substring(0,delim) ); //frequency
System.out.println( data.substring(delim+1) ); //50
If you wanted something a bit closure-esque then it would probably have a function signature like:
interface KeyPairFunctor<T, K, V> {
Pair<K, V> getKeyValuePair(T item);
}
public static <T, K, V> Map<K, V> toMap(Iterable<T> items, KeyPairFunctor<T, K, V> functor) {
Map<K, V> m = new HashMap<K,V>();
for (T item : items) {
Pair<K, V> e = functor.getKeyValuePair(item);
m.put(e.getKey(), e.getValue());
}
return m;
}
public static void test() {
toMap(Arrays.asList(new String[] { "A:B" }), new KeyPairFunctor<String, String, String>() {
#Override
public Pair<String, String> getKeyValuePair(String item) {
String[] ss = item.split(":");
return new Pair(ss[0], ss[1]);
}});
}
Implementation of Pair is left as an exercise to the reader and I'm sure you can see how you can put your , split list in the first argument.

Categories

Resources