I'm trying to use ojAlgo library in Java for Integer Optimization but I'm unable to provide it the objective function I intend to.
I'd like to minimize the function: (A - B.X)'(A - B.X), where A is a (n x 1) matrix, B is a (n x n) diagonal matrix and X is a (n x 1) matrix with the optimization variables. I want the result in X to consist of only integers .
I was able to set a different objective function which was to maximize B.X. How do I change it to (A - B.X)'(A - B.X)? Here is the code so far.
import org.apache.log4j.Logger;
import org.ojalgo.optimisation.Expression;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.Variable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.InputMismatchException;
import java.util.List;
public class AllocationOptimization {
protected Logger log = Logger.getLogger(AllocationOptimization.class);
// This is the objective function, since weight() is assigned to it. How to change this objective function to what I want?
private List<Variable> makeVariables(HashMap<String, BigDecimal> matrixB) {
List<Variable> result = new ArrayList<>();
for (String assetName : matrixB.keySet()) {
result.add(new Variable(assetName).weight(matrixB.get(assetName)));
}
return result;
}
private ExpressionsBasedModel createExpressionModel(List<Variable> variables) {
final ExpressionsBasedModel model = new ExpressionsBasedModel();
for (Variable v : variables) {
model.addVariable(v);
}
return model;
}
private void addExpressionConstraints(ExpressionsBasedModel model, List<Variable> variables,
HashMap<String, BigDecimal> matrixB,
HashMap<String, BigDecimal> wantedAbsoluteSharesMap,
BigDecimal idealTotalPrice) {
Expression expression = model.addExpression("C1").upper(idealTotalPrice);
int i = 0;
for (String assetName : matrixB.keySet()) {
expression.set(variables.get(i), matrixB.get(assetName));
i += 1;
}
for (Variable v : variables) {
long absShares = wantedAbsoluteSharesMap.get(v.getName()).longValue();
v.lower((long) Math.max(0, 0.8 * absShares)).upper((long) Math.max(Math.max(0, 1.2 * absShares), 5));
}
}
private void setIntegerSolving(ExpressionsBasedModel model) {
for (Variable v : model.getVariables()) {
v.setInteger(true);
}
}
private HashMap<String, Long> getIntegerOptimizationResult(ExpressionsBasedModel model, HashMap<String, BigDecimal> matrixB) {
Optimisation.Result result = model.maximise();
return prepareResult(result, matrixB);
}
private HashMap<String, Long> prepareResult(Optimisation.Result result, HashMap<String, BigDecimal> matrixB) {
int i = 0;
HashMap<String, Long> optimizedResult = new HashMap<>();
BigDecimal sumAssetPrices = new BigDecimal("0.0");
for (String assetName : matrixB.keySet()) {
long sharesCount = result.get(i).longValue();
log.debug(assetName + ": " + sharesCount);
optimizedResult.put(assetName, sharesCount);
sumAssetPrices = sumAssetPrices.add(matrixB.get(assetName).multiply(BigDecimal.valueOf(sharesCount)));
i += 1;
}
log.debug("Total assets value after converting shares to integer numbers: " + sumAssetPrices);
return optimizedResult;
}
public HashMap<String, Long> optimizeSharesCount(HashMap<String, BigDecimal> constraint1,
HashMap<String, BigDecimal> matrixB,
BigDecimal constraint2) throws InputMismatchException {
List<Variable> variableList = makeVariables(matrixB);
ExpressionsBasedModel model = createExpressionModel(variableList);
addExpressionConstraints(model, variableList, matrixB, constraint1, constraint2);
setIntegerSolving(model);
HashMap<String, Long> resultMap = getIntegerOptimizationResult(model, matrixB);
return resultMap;
}
private HashMap<String, BigDecimal> createWantedAbsoluteSharesTest1() {
HashMap<String, BigDecimal> absShares = new HashMap<>();
absShares.put("NFLX", new BigDecimal("2"));
absShares.put("MSFT", new BigDecimal("4"));
absShares.put("GOOG", new BigDecimal("0"));
absShares.put("AAPL", new BigDecimal("25"));
return absShares;
}
private HashMap<String, BigDecimal> createAssetPricesMapTest1() {
HashMap<String, BigDecimal> assetPrices = new HashMap<>();
assetPrices.put("NFLX", new BigDecimal("601.06"));
assetPrices.put("MSFT", new BigDecimal("296.75"));
assetPrices.put("GOOG", new BigDecimal("2843.78"));
assetPrices.put("AAPL", new BigDecimal("149.07"));
return assetPrices;
}
public static void main(String[] args) {
AllocationOptimization allocationOptimization = new AllocationOptimization();
// For testing
HashMap<String, BigDecimal> constr1 = allocationOptimization.createWantedAbsoluteSharesTest1();
HashMap<String, BigDecimal> matrixB = allocationOptimization.createAssetPricesMapTest1();
BigDecimal constr2 = new BigDecimal("5348.25");
HashMap<String, Long> optimizedResult = null;
try {
optimizedResult = allocationOptimization.optimizeSharesCount(constr1, matrixB, constr2);
} catch (Exception e) {
e.printStackTrace();
}
assert optimizedResult != null;
allocationOptimization.log.info("optimizedResult size: " + optimizedResult.size());
}
}
You assigned weights to the Variable:s. That makes them part of the objective function. You can also assign weights to Expression:s. Anything/everything that has a weight is summed up to form the objective function.
Expression objective = model.addExpression("Whole Objective").weight(BigDecimal.ONE);
for (Variable variableR : variables) {
objective.set(variableR, linearParameter);
for (Variable variableC : variables) {
objective.set(variableR, variableC, quadraticParameter);
}
}
Is equivalent to:
Expression objective = model.addExpression("Objective Part").weight(BigDecimal.ONE);
for (Variable variableR : variables) {
variableR.weight(linearParameter);
for (Variable variableC : variables) {
objective.set(variableR, variableC, quadraticParameter);
}
}
I modified the objective function and added necessary constraints, following #apete's comments. Posting my solution here for others.
private List<Variable> makeVariables(HashMap<String, BigDecimal> matrixB) {
List<Variable> result = new ArrayList<>();
for (String assetName : matrixB.keySet()) {
result.add(new Variable(assetName));
}
return result;
}
private ExpressionsBasedModel createObjective(ExpressionsBasedModel model, List<Variable> variables,
HashMap<String, BigDecimal> matrixA,
HashMap<String, BigDecimal> matrixB) {
// Anything and everything with that has a weight is summed up to form the objective function
Expression objective = model.addExpression("Objective function").weight(BigDecimal.ONE);
for (Variable variable : variables) {
String assetName = variable.getName();
objective.set(variable, new BigDecimal("-2").multiply(matrixA.get(assetName)).multiply(matrixB.get(assetName)));
objective.set(variable, variable, matrixB.get(assetName).pow(2));
}
return model;
}
private void addExpressionConstraints(ExpressionsBasedModel model, List<Variable> variables,
HashMap<String, BigDecimal> matrixB,
HashMap<String, BigDecimal> wantedAbsoluteSharesMap,
HashMap<String, BigDecimal> matrixA,
BigDecimal idealTotalPrice, BigDecimal accountBalance) {
Expression expression1 = model.addExpression("C1").upper(idealTotalPrice);
for (Variable variable : variables) {
expression1.set(variable, matrixB.get(variable.getName()));
}
for (Variable v : variables) {
// No negative values constraint
v.lower(0);
}
// This constraint is used to compensate for the constants arising in the quadratic objective function
BigDecimal sumSquaresUserAllocation = new BigDecimal("0.0");
for (String assetName : this.assetsList) {
sumSquaresUserAllocation = sumSquaresUserAllocation.add(matrixA.get(assetName).pow(2));
}
Expression expression2 = model.addExpression("C2").upper(new BigDecimal("1.01").multiply(sumSquaresUserAllocation.multiply(new BigDecimal("-1"))));
expression2.lower(new BigDecimal("0.99").multiply(sumSquaresUserAllocation.multiply(new BigDecimal("-1"))));
for (Variable variable : variables) {
String assetName = variable.getName();
expression2.set(variable, new BigDecimal("-2").multiply(matrixA.get(assetName)).multiply(matrixB.get(assetName)));
expression2.set(variable, variable, matrixB.get(assetName).pow(2));
}
}
Finally, instead of using the model.maximise() function, I used model.minimise() to minimize the objective function.
Related
Say I got 2 Key Value pairs:
String k1 = "a.b.c.d";
String v1 = "123";
String k2 = "a.b.c.d";
String v2 = "456";
And the desired output is:
a {
b {
c {
d = "123",
e = "456"
}
}
}
So, I've decided the split the keys by "." and form nested HashMaps and then trying to merge them when they have duplicate keys. However, it needs to merge at the leaf or innermost level instead of the outermost level.
This is the full code:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestClass {
public static void main(String []args)
{
Map<String, Object> finalMap = new HashMap<>();
Map<String, Object> outerMap1 = new HashMap<>();
Map<String, Object> outerMap2 = new HashMap<>();
String k = "a.b.c.d";
String v = "123";
outerMap1 = createNestedStructure(k, v);
k = "a.b.c.e";
v = "456";
outerMap2 = createNestedStructure(k, v);
finalMap = Stream
.concat(outerMap1.entrySet().stream(),
outerMap2.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue, (a, b) -> {
String c = a.toString() + "\n" + b.toString();
return c;
}, HashMap::new));
System.out.println(finalMap.toString());
}
public static Map<String, Object> createNestedStructure(String k, String v)
{
String[] tokens = k.split("\\.");
Map<String, String> innerMap = new HashMap<>();
v = "\"" + v + "\"";
innerMap.put(tokens[tokens.length-1], v);
Map<String, Object> middleMap = new HashMap<>();
middleMap.put(tokens[tokens.length-2], innerMap);
for(int i=tokens.length-3; i>=0; i--)
{
Map<String, Object> middleMapTmp = new HashMap<>();
middleMapTmp.put(tokens[i], middleMap);
middleMap = middleMapTmp;
}
// Map<String, Object> outerMap = new HashMap<>();
// outerMap.put(tokens[0], middleMap);
// return outerMap;
return middleMap;
}
}
I'm not sure if this is the correct approach. So suggestions on better approaches are also welcome.
Not exactly sure about your specific problem, but you could simply insert the values into the same structure instead of merging them afterward. For example, you can make a recursive insert that creates the nested maps until it inserts your value on the last key part. If the nested map already exists it uses the existing one. Something like this could do the trick:
public static void main(String[] args) {
String k1 = "a.b.c.d";
String v1 = "123";
String k2 = "a.b.c.e";
String v2 = "456";
Map<String, Object> map = new HashMap<>();
recursiveInsert(map,k1, v1);
recursiveInsert(map,k2, v2);
System.out.println(map);
}
public static void recursiveInsert(Map<String, Object> map, String key, String value) {
int index = key.indexOf('.');
if (index == -1) {
map.put(key, value);
} else {
String subKey = key.substring(0, index);
map.putIfAbsent(subKey, new HashMap<>());
recursiveInsert((Map<String, Object>) map.get(subKey), key.substring(index + 1), value);
}
}
The output of this is what you requested:
{a={b={c={d=123, e=456}}}}
I am working on a problem I came across in an interview.
Input contains Population|City|State|Interstates list
Output needs to be sorted in descending order by population first, then alphabetically by city and state, and then the interstates need to be sorted in ascending order too.
Sample input:
27|Chicago|Illinois|I-94;I-90;I-88;I-57;I-55
83|New York|New York|I-78;I-95;I-87;I-80
15|Phoenix|Arizona|I-10;I-17;I-8
15|Philadelphia|Pennsylvania|I-95;I-76
Sample output:
83
New York, New York
Interstates: I-78, I-80, I-87, I-95
27
Chicago, Illinois
Interstates: I-55, I-57, I-88, I-90, I-94
15
Philadelphia, Pennsylvania
Interstates: I-76, I-95
Phoenix, Arizona
Interstates: I-8, I-10, I-17
Here's my approach so far. I am currently stuck in the if block where I've added a comment. I am not sure if I am going in the right direction. I am looking for a hint to take the right approach here.
Scanner sc = new Scanner(System.in);
String line;
List<String> al = new ArrayList<>();
//Outer map sorts reverse by population, inner map1 sorts by city, inner
map2 sorts by state
Map<Integer, Map<String, Map<String, String>>> outerMap = new TreeMap<>
(Collections.reverseOrder());
Map<String, Map<String, String>> innerMap1 = new TreeMap<>();
Map<String, String> innerMap2 = new TreeMap<>();
while(sc.hasNextLine() && (line = sc.nextLine()).length()!=0) {
//Ignore if input contains this character
if(line.contains("#")) {
line = sc.nextLine();
}
al.add(line);
}
for(int i = 0; i < al.size(); i++) {
int outerMapKey = Integer.parseInt(al.get(i).split("\\|")[0]);
String innerMap1Key = al.get(i).split("\\|")[1];
String innerMap2Key = al.get(i).split("\\|")[2];
String value = al.get(i);
outerMap.get(outerMapKey);
if(outerMap.containsKey(outerMapKey)) {
innerMap1 = outerMap.get(outerMapKey);
/* Logic to put values in inner maps
This is going to get very convoluted, not sure if I have the
right approach
*/
}
else {
innerMap1 = new TreeMap<>();
innerMap2 = new TreeMap<>();
innerMap2.put(innerMap2Key, value);
innerMap1.put(innerMap1Key, innerMap2);
outerMap.put(outerMapKey, innerMap1);
}
}
Thank you for all your help so far. I am posting my code (working now) based on feedback here. Please take a look and suggest how it can be improved.
public static void main(String[] args) {
Map<String, List<PopulationByCityState>> map = readAndProcessInput();
printSortedOutput(map);
}
private static Map<String, List<PopulationByCityState>> readAndProcessInput() {
Map<String, List<PopulationByCityState>> map = readInput();
sortByPopulationCityAndState(map);
return map;
}
private static Map<String, List<PopulationByCityState>> readInput() {
System.out.println("Enter input:");
Scanner sc = new Scanner(System.in);
String line;
Map<String, List<PopulationByCityState>> map = new TreeMap<>(Collections.reverseOrder());
while (sc.hasNextLine() && (line = sc.nextLine()).length() != 0) {
if (line.contains("#")) {
line = sc.nextLine();
}
populateMap(line, map);
}
return map;
}
private static void populateMap(String line, Map<String, List<PopulationByCityState>> map) {
String[] s = line.split("\\|");
String[] is = s[3].split(";");
String key = s[0];
PopulationByCityState p = new PopulationByCityState();
p.setPopulation(Long.parseLong(s[0]));
p.setCity(s[1]);
p.setState(s[2]);
List<String> interstates = new ArrayList<>();
for (String aString : is) {
interstates.add(aString);
}
sortInterstates(interstates);
p.setInterstates(interstates);
if (map.containsKey(key)) {
map.get(key).add(p);
} else {
List<PopulationByCityState> al = new ArrayList<>();
al.add(p);
map.put(key, al);
}
}
private static void sortInterstates(List<String> interstates) {
Collections.sort(interstates, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
int n1 = Integer.parseInt(o1.split("-")[1]);
int n2 = Integer.parseInt(o2.split("-")[1]);
return n1 - n2;
}
});
}
private static void sortByPopulationCityAndState(Map<String, List<PopulationByCityState>> map) {
for (Map.Entry entry : map.entrySet()) {
List<PopulationByCityState> list = (List<PopulationByCityState>) entry.getValue();
Collections.sort(list, new Comparator<PopulationByCityState>() {
#Override
public int compare(PopulationByCityState o1, PopulationByCityState o2) {
int c;
c = (int) (o2.getPopulation() - o1.getPopulation());
if (c == 0) {
c = o1.getCity().compareTo(o2.getCity());
}
if (c == 0) {
c = o1.getState().compareTo(o2.getState());
}
return c;
}
});
}
}
private static void printSortedOutput(Map<String, List<PopulationByCityState>> map) {
for (Map.Entry<String, List<PopulationByCityState>> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println();
List<PopulationByCityState> list = entry.getValue();
for (PopulationByCityState p : list) {
System.out.println(p.getCity() + ", " + p.getState());
List<String> interstates = p.getInterstates();
System.out.print("Interstates: ");
int s = 0;
for (String is : interstates) {
s++;
System.out.print(is);
if (s != interstates.size()) {
System.out.print(", ");
}
}
System.out.println();
System.out.println();
}
}
}
Your approach relies on over complicated and not meaningful structure and also uses a Comparator that will only sort the first level of the map :
Map<Integer, Map<String, Map<String, String>>> outerMap = new TreeMap<>
(Collections.reverseOrder());
A finer approach could rely on using a class that represents each individual information that you need to represent a population for a state : PopulationForState
Here is a very simple representation of it (that is of course improvable but that should help you to understand the logic) :
public class PopulationForState{
private long population;
private String city;
private String state;
private List<String> interstates;
...
// getters
}
Add instances of them in a List and use a comparator that sorted them in descending order by population first, then alphabetically by city and state.
The interstates field may be sorted independently or directly during the sort of outer elements.
You could provide a sort method in PopulationForState, for example sortInnerStates() that sorts them in ascending order.
Personally, I would make it independently to keep the processing less coupled between.
So you could write something like :
List<PopulationForState> populationForStates = new ArrayList<>();
populationForStates.add(new PopulationForState(...));
populationForStates.add(new PopulationForState(...));
Collection.sort(populationForStates, Comparator.comparing(PopulationForState::population).reversed()
.thenComparing(PopulationForState::getCity)
.thenComparing(PopulationForState::getState);
populationForStates.stream()
.forEach(PopulationForState::sortInnerStates);
If you have a structure such the one posted in above post:
public class PopulationForState{
public long population;
public String city;
public String state;
public List<String> interstates;
//Do encapsulate
}
You can sort it with one comparator:
Collections.sort(populatisForStates, new Comparator<PopulationForState>(){
public int compare(PopulationForState first, PopulationForState scnd) {
int compare = first.population - scnd.population;
if(compare != 0) return compare;
compare = first.city.compareTo(scnd.city);
if(compare != 0) return compare;
return first.state.compareTo(scnd.state);
}
});
Sorting Interstates is similar and you just need to use Collections.sort(interstates) on each instance.
There is a table:
key consists from 3 suffixes:
region+s1+s2
region, like US is always specified, but other ones can be not specified so * will be used for "all".
for example:
for key = "US_A_U" value = 2, because:
trying to find full match: find in the table key ("US_A_U") - not
found
1 step less strict find: find key ("US_A_*") - found == 2
for key = "US_Q_Q" value = 3, because:
trying to find full match: find in the table key ("US_Q_Q") - not
found
1 step less strict find: find key ("US_Q_*") - not found
find key ("US_*_Q") - not found
1 step less strict find: find key ("US_*_*") - found = 3
for key = "US_O_P" value = 3, because:
trying to find full match: find in the table key ("US_O_P") - not
found
1 step less strict find: find key ("US_O_*") - not found
find key ("US_*_P") - not found
1 step less strict find: find key ("US_*_*") - found = 3
so to use HashMap method I will need to call 4 times map.get() to find a value, which is too many as this code will be run very very often.
Is there any nicer or faster solutions?
package test;
import java.util.HashMap;
public class MainCLass {
public static void main(String[] args) {
// init map (assuming this code will be run only once)
HashMap<String, String> map = new HashMap<>();
map.put("US_A_B", "1");
map.put("US_A_*", "2");
map.put("US_*_*", "3");
map.put("US_O_O", "4");
map.put("US_*_W", "5");
map.put("ASIA_*_*", "6");
// now often called logic
// incoming params, for this example hardcoded
String reg = "US";
String s1 = "O";
String s2 = "P";
String val = null;
val = map.get(reg+"_"+s1+"_"+s2);
if (val == null){
val = map.get(reg+"_"+s1+"_*");
if (val == null){
val = map.get(reg+"_"+"*_"+s2);
if (val == null){
val = map.get(reg+"_*_*");
}
}
}
System.out.println(val);
}
}
upd: I need to add that there are always 3 incoming params (region, s1, s2). Each of this param never will equal "*" and never be empty, so the full key always be like US_J_K (and not US_*_K etc.)
so by these 3 params I need to find right value from the init table.
You could try creating a tier of maps such as
Map<String, Map<String, Map<String, String>>> map;
In this map the first key is region, the second key is s1, and the third key is s2. This will allow To easily search for region, s1, and s2 independently.
EDIT:
Example usage with searching for "US_O_P"
public static void main(String[] args) {
RegionMap map = new RegionMap();
String region = "US";
String s1 = "O";
String s2 = "P";
String val = map.search(region, s1, s2);
System.out.println(val);
}
public class RegionMap {
private Map<String, Map<String, Map<String, String>>> regionMap;
public RegionMap() {
init();
}
public String search(String region, String s1, String s2) {
String val = searchS1(regionMap.get(region), s1, s2);
if (val == null) {
val = searchS1(regionMap.get("*"), s1, s2);
}
return val;
}
private String searchS1(Map<String, Map<String, String>> s1Map, String s1, String s2) {
if (s1Map == null) {
return null;
}
String val = searchS2(s1Map.get(s1), s2);
if (val == null) {
val = searchS2(s1Map.get("*"), s2);
}
return val;
}
private String searchS2(Map<String, String> s2Map, String s2) {
if (s2Map == null) {
return null;
}
String val = s2Map.get(s2);
if (val == null) {
val = s2Map.get("*");
}
return val;
}
private void init() {
regionMap = new HashMap<>();
addEntry("US", "A", "B", "1");
addEntry("US", "A", "*", "2");
addEntry("US", "*", "*", "3");
addEntry("US", "O", "O", "4");
addEntry("US", "*", "W", "5");
addEntry("ASIA", "*", "*", "6");
}
private void addEntry(String region, String s1, String s2, String value) {
Map<String, Map<String, String>> s1Map = regionMap.get(region);
if (s1Map == null) {
s1Map = new HashMap<>();
regionMap.put(region, s1Map);
}
Map<String, String> s2Map = s1Map.get(s1);
if (s2Map == null) {
s2Map = new HashMap<>();
s1Map.put(s1, s2Map);
}
s2Map.put(s2, value);
}
}
EDIT:
Benchmark results
I ran tests for searching for "US_O_P" multiple times and found the following results for 1,000,000,000 searches
Original: 9.7334702479 seconds
Tiered: 2.471287074 seconds
The following is the benchmark code
public class RegionMapOrig {
private Map<String, String> map;
public RegionMapOrig() {
init();
}
private void init() {
map = new HashMap<>();
map.put("US_A_B", "1");
map.put("US_A_*", "2");
map.put("US_*_*", "3");
map.put("US_O_O", "4");
map.put("US_*_W", "5");
map.put("ASIA_*_*", "6");
}
public String search(String reg, String s1, String s2) {
String val = null;
val = map.get(reg + "_" + s1 + "_" + s2);
if (val == null) {
val = map.get(reg + "_" + s1 + "_*");
if (val == null) {
val = map.get(reg + "_" + "*_" + s2);
if (val == null) {
val = map.get(reg + "_*_*");
}
}
}
return val;
}
}
private static final int N = 1000000000;
public static void main(String[] args) {
String region = "US";
String s1 = "O";
String s2 = "P";
testOrig(region, s1, s2);
test(region, s1, s2);
}
private static void testOrig(String region, String s1, String s2) {
RegionMapOrig map = new RegionMapOrig();
long start = System.nanoTime();
for (int i = 0; i < N; ++i) {
String val = map.search(region, s1, s2);
}
long end = System.nanoTime();
System.out.println((end - start) / 10E9);
}
private static void test(String region, String s1, String s2) {
RegionMap map = new RegionMap();
long start = System.nanoTime();
for (int i = 0; i < N; ++i) {
String val = map.search(region, s1, s2);
}
long end = System.nanoTime();
System.out.println((end - start) / 10E9);
}
Running this code multiple times have yielded the same results. However, this benchmark is a simple and may not be definitive. To truly test your results you will need to analyze the performance with a real data set that represents your typical values. I believe your performance issue may lie within your string concatenation and not how many calls to the map. The other reason why mine may have performed better is that my internal maps may be cached making multiple retrievals faster.
EDIT: Benchmark update
After further investigation by removing string concatentation your original code improved showing these results:
Orginal (no concatentation): 1.2068575417 seconds
Tiered: 2.2982665873 seconds
The code changes are:
public String searchNoCat(String cache1, String cache2, String cache3, String cache4) {
String val = null;
val = map.get(cache1);
if (val == null) {
val = map.get(cache2);
if (val == null) {
val = map.get(cache3);
if (val == null) {
val = map.get(cache4);
}
}
}
return val;
}
private static void testOrigNoCat(String region, String s1, String s2) {
RegionMapOrig map = new RegionMapOrig();
String cache1 = region + "_" + s1 + "_" + s2;
String cache2 = region + "_" + s1 + "_*";
String cache3 = region + "_" + "*_" + s2;
String cache4 = region + "_*_*";
long start = System.nanoTime();
for (int i = 0; i < N; ++i) {
String val = map.searchNoCat(cache1, cache2, cache3, cache4);
}
long end = System.nanoTime();
System.out.println((end - start) / 10E9);
}
However, the issue still remains on how to efficiently cache such values or reduce the number of concatenations for generic input. I do not know of an efficient way to do this. Therefore, I think that the tiered map is an efficient solution that eludes the concatenation problem.
It looks like you need some tree structure to help you encapsulating the logic with the wildcards ("*") replacements when searching for a value.
First I wrote some unit tests to describe the expected behaviour
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class WildcardSearchSpec {
private Node root;
#Before
public void before() {
root = new WildcardSearch();
root.add("US_A_B", "1");
root.add("US_A_*", "2");
root.add("US_*_*", "3");
root.add("US_O_O", "4");
root.add("US_*_W", "5");
root.add("ASIA_*_*", "6");
}
#Test
public void itShouldReturnFullWildcardCorrespondingValue() {
String key = "US_Q_Q";
String value = root.value(key);
assertEquals("3", value);
}
#Test
public void itShouldReturnNoWildcardCorrespondingValue() {
String key = "US_A_B";
String value = root.value(key);
assertEquals("1", value);
}
#Test
public void itShouldReturnS2WildcardCorrespondingValue() {
String key = "US_A_U";
String value = root.value(key);
assertEquals("2", value);
}
#Test
public void itShouldReturnS1WidlcardCorrespondingValue() {
String key = "US_W_W";
String value = root.value(key);
assertEquals("5", value);
}
#Test(expected=NoValueException.class)
public void itShouldThrowWhenNoCorrespondingValue() {
String key = "EU_A_B";
root.value(key);
fail();
}
}
The interface one can extract from these tests is the following
public interface Node {
void add(String key, String value);
String value(String key);
}
Which is implemented by WildcardSearch
import java.util.HashMap;
import java.util.Map;
public final class WildcardSearch implements Node {
private final Map<String, CountrySearch> children = new HashMap<>();
#Override
public void add(String key, String value) {
String country = key.split("_")[0];
String rest = key.substring(country.length() + 1);
children.putIfAbsent(country, new CountrySearch());
children.get(country).add(rest, value);
}
#Override
public String value(String key) {
String country = key.split("_")[0];
String rest = key.substring(country.length() + 1);
if (!children.containsKey(country)) {
return children.get(country).value(rest);
} else {
throw new NoValueException();
}
}
}
WildcardSearch uses CountrySearch to delegate the search in each country.
import java.util.HashMap;
import java.util.Map;
final class CountrySearch implements Node {
private final Map<String, SuffixeSearch> children = new HashMap<>();
#Override
public void add(String key, String value) {
String[] splittedKey = key.split("_");
String s1 = splittedKey[0];
String s2 = splittedKey[1];
children.putIfAbsent(s1, new SuffixeSearch());
children.get(s1).add(s2, value);
}
#Override
public String value(String key) {
String[] splittedKey = key.split("_");
String s1 = splittedKey[0];
String s2 = splittedKey[1];
if (children.containsKey(s1)) {
return children.get(s1).value(s2);
} else if (children.containsKey("*")) {
return children.get("*").value(s2);
} else {
throw new NoValueException();
}
}
}
CountrySearch uses SuffixeSearch to delegate the search in the suffixes.
import java.util.HashMap;
import java.util.Map;
final class SuffixeSearch implements Node {
private final Map<String, String> children = new HashMap<>();
public void add(String key, String value) {
children.put(key, value);
}
#Override
public String value(String key) {
if (children.containsKey(key)) {
return children.get(key);
} else if (children.containsKey("*")) {
return children.get("*");
} else {
throw new NoValueException();
}
}
}
Note: NoValueException is a custom RuntimeException.
The point is that each responsibility is clearly separated.
SuffixeSearch is only able to return the value for the corresponding key or the value corresponding to "*". It doesn't know anything about how is the overall key structured, nor the values are clustered by country, etc.
CountrySearch only knows about its level, delegating the rest to SuffixeSearch or ignoring what is above.
WildcardSearch only knows about splitting in country and delegates to CountrySearch the responsibility to do the wildcard magic.
Best and more general solution would be to use a Search Tree which you could implement yourself fairly easily and is a good programming exercise as well. There are also lots of tutorials and examples around, how to implement it.
For your special use case you could make use of cascading Maps, as DragonAssassin aready posted, which leverages what Java already offers.
One possible optimization is to expand the map to all possible values, it will need more memory and has some initialization cost but it might be worth it.
I made a few assumptions, if they dont apply to your problem this approach is useless for you.
The region data does not change, (a partial restart is acceptable in the case the data changes).
It is always one char instead of the "star". So "US_A_B" not "US_AA_BB".
Only uppercase letters instead of the "star". So no "US_a_b" or "US_/_/"
This approach creates int[] for every region. In this array are all the possible values calculated for 'A''A' -> 'Z''Z' including the '*'. So for a request you only need to find the correct int[] and calculate the index in the array based on the chars supplied.
I run it with benchmarks from #DragonAssassin and got 1/10 of his approach. The cost is about 1kb of memory for each region.
Here is the code:
static class AreaMapBuilder {
private List<String> areas = new ArrayList<>();
private Map<String, Integer> codes = new HashMap<>();
public void put(String area, char a, char b, int value) {
areas.add(area);
if (a == '*')
a = '#';
if (b == '*')
b = '#';
codes.put(area + "_" + a + "_" + b, value);
}
public AreaMap build() {
Map<String, int[]> codes = new HashMap<>();
for (String area : areas) {
codes.put(area, forArea(area));
}
return new AreaMap(codes);
}
private int[] forArea(String area) {
int[] forArea = new int[27 * 27];
for (int indexA = 0; indexA < 27; indexA++) {
for (int indexB = 0; indexB < 27; indexB++) {
forArea[indexA * 27 + indexB] = slowGet(area, (char) (indexA + '#'), (char) (indexB + '#'));
}
}
return forArea;
}
private int slowGet(String area, char a, char b) {
Integer val = codes.get(area + "_" + a + "_" + b);
if (val == null) {
val = codes.get(area + "_" + a + "_#");
if (val == null) {
val = codes.get(area + "_" + "#_" + b);
if (val == null) {
val = codes.get(area + "_#_#");
}
}
}
return val;
}
}
static class AreaMap {
private Map<String, int[]> codes;
public AreaMap(Map<String, int[]> codes) {
this.codes = codes;
}
public int get(String area, char a, char b) {
if (a == '*')
a = 0;
else
a -= '#';
if (b == '*')
b = 0;
else
b -= '#';
return codes.get(area)[a * 27 + b];
}
}
static AreaMap getMap(){
AreaMapBuilder areaBuilder = new AreaMapBuilder();
areaBuilder.put("US", 'A', 'B', 1);
areaBuilder.put("US", 'A', '*', 2);
areaBuilder.put("US", '*', '*', 3);
areaBuilder.put("US", 'O', 'O', 4);
areaBuilder.put("US", '*', 'W', 5);
areaBuilder.put("ASIA", '*', '*', 6);
return areaBuilder.build();
}
If prepared right you could nest three maps and mark an entry star for the generic cases (in fact * would just be another key into the maps). To get the desired number you would then need three "Indexes". Assuming there will always be a *-Map:
Map<String, Map<String, Map<String, Integer>>> map;
Map<String, Map<String, String> us_map = new Map<String, Map<String, String>();
Map<String, Map<String, String> asia_map = new Map<String, Map<String, String>();
Map<String, String> us_a_map = new Map<String, Integer>();
us_a_map.put("B", 1);
us_a_map.put("*", 2);
Map<String, String> us_star_map = new Map<String, Integer>();
us_star_map.put("*", 3);
us_star_map.put("W", 5);
map.put( "US", us_map);
us_map.put( "A", us_a_map );
us_map.put( "*", us_star_map );
map.put( "ASIA", asia_map);
In this map the performance will be better than in your proposed case, since the maps are smaller. For example to get element US_A_B you would
Integer value = map.get( "US" ).get( "A" ).get( "B" );
To deal with missing elements (in this case the * elements have to be considered) one also can find the Map entry "in each level": With following input:
String l0 = "US";
String l1 = "A";
String l2 = "unknown";
And assuming there is always an entry for "*" in each of the Maps:
Map<String, Map<String, String>> level_0
Map<String, String> level_1;
Integer level_2; // This will be the desired result
level_0 = map.get(l0);
if (level_0 == null) {
level_0 = star_0;
}
level_1 = level_0.get(l1);
if (level_1 == null) {
level_1 = level_0.get("*");
}
level_2 = level_1.get(l2);
if (level_2 == null) {
level_2 = level_1.get("*");
}
The Result will then be the value of level_2.
I have a hashMap that contains key and value as 'String'. I am getting these values from a web page in my selenium automation script.
my hashmap has following
<Italy, 3.3 millions>
<Venezuela, 30.69 millions>
<Japan, 127.1 millions>
How can I convert all the string alphanumeric values to integers so that I can apply sorting on the hashmap?
I have to display the word 'millions'.
As far as I understand from your question what you need to do is to be able to sort those values, so what you need is a Comparator.
Here is the Comparator that could do the trick:
Comparator<String> comparator = new Comparator<String>() {
#Override
public int compare(final String value1, final String value2) {
return Double.compare(
Double.parseDouble(value1.substring(0, value1.length() - 9)),
Double.parseDouble(value2.substring(0, value2.length() - 9))
);
}
};
System.out.println(comparator.compare("3.3 millions", "30.69 millions"));
System.out.println(comparator.compare("30.69 millions", "30.69 millions"));
System.out.println(comparator.compare("127.1 millions", "30.69 millions"));
Output:
-1
0
1
If you have only millions you can try something like this
String str = "3.3 Millions";
String[] splitted = str.split(" ");
double i = Double.valueOf(splitted[0])*1000000;
System.out.println(i);
or do your calculation depending on the substring
not sure if this is what you are looking for.. If i get it right you have to change your map from
<String, String> to <String, Double>.
See my example below :
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
public class NewClass9 {
public static void main(String[] args) throws ParseException{
Map<String,String> oldMap = new HashMap<>();
oldMap.put("Italy", "3.3 millions");
oldMap.put("Venezuela", "30.69 millions");
oldMap.put("Japan", "127.1 millions");
Map<String,Double> newMap = new HashMap<>();
for(String key : oldMap.keySet()){
newMap.put(key, convert(oldMap.get(key)));
}
for(String key : newMap.keySet()){
System.out.printf("%.0f millions\n" ,newMap.get(key));
}
}
private static double convert(String str) {
String[] splitted = str.split(" ");
return Double.valueOf(splitted[0])*1000000;
}
}
A bit overkill but this should be extensible.
NB: I've only covered the multiplier lookup.
/**
* Possible units and their multipliers.
*/
enum Unit {
Unit(1),
Hundred(100),
Thousand(1000),
Million(1000000),
Billion(1000000000),
Squillion(Integer.MAX_VALUE);
private final int multiplier;
Unit(int multiplier) {
this.multiplier = multiplier;
}
}
/**
* Comparator that matches caseless and plurals
*
* NB: Not certain if this is consistent.
*/
private static final Comparator<String> COMPARECASELESSANDPLURALS
= (String o1, String o2) -> {
// Allow case difference AND plurals.
o1 = o1.toLowerCase();
o2 = o2.toLowerCase();
int diff = o1.compareTo(o2);
if (diff != 0) {
// One character different in length?
if (Math.abs(o1.length() - o2.length()) == 1) {
// Which may be plural?
if (o1.length() > o2.length()) {
// o1 might be plural.
if (o1.endsWith("s")) {
diff = o1.substring(0, o1.length() - 1).compareTo(o2);
}
} else if (o2.endsWith("s")) {
// o2 might be plural.
diff = -o2.substring(0, o2.length() - 1).compareTo(o1);
}
}
}
return diff;
};
// Build my lookup.
static final Map<String, Integer> MULTIPLIERS
= Arrays.stream(Unit.values())
// Collect into a Map
.collect(Collectors.toMap(
// From name of the enum.
u -> u.name(),
// To its multiplier.
u -> u.multiplier,
// Runtime exception in case of duplicates.
(k, v) -> {
throw new RuntimeException(String.format("Duplicate key %s", k));
},
// Use a TreeMap that ignores case and plural.
() -> new TreeMap(COMPARECASELESSANDPLURALS)));
// Gives the multiplier for a word.
public Optional<Integer> getMultiplier(String word) {
return Optional.ofNullable(MULTIPLIERS.get(word));
}
public void test() {
String[] tests = {"Million", "Millions", "Thousand", "Aardvark", "billion", "billions", "squillion"};
for (String s : tests) {
System.out.println("multiplier(" + s + ") = " + getMultiplier(s).orElse(1));
}
}
Problem
Data is in the format:
Map<String, Map<String, Integer>>
Which looks something like this:
{"MilkyWay": {"FirstStar" : 3, "SecondStar" : 9 .... }, "Andromeda": {"FirstStar" : 10, "SecondStar" : 9 .... } }
I want to compare the Star values in a quick loop, so I'd like to compare the integer value of FirstStar in MilkyWay and Andromeda and have it return true or falseif the values are the same or not. Since this Map of Maps is huge.
My Attempt
I'd like to do it something like:
//GalaxyMap<String, <Map<String, Integer>>
for (Map<String, Integer> _starMap : GalaxyMap.values())
{
for (String _starKey : _starMap.keySet()){
//Can't quite think of the correct logic... and I'm tired...
}
}
I'd like to keep it as short as possible... I've been staring at this for a while and I'm going in circles with it.
EDIT
Outer keys differ, Inner keys are the same
Also since this is a response from a server, I don't know the size it's going to be
Why does this need to be a map. If you're always using "FirstStar", "SecondStar" etc, as your keys, then why not make it a list instead..
Map<String, List<Integer>>
Then you can do something like:
public boolean compareGalaxyStar(String galaxyName, String otherGalaxyName, int star) {
List<Integer> galaxyStars = galaxyMap.get(galaxyName);
List<Integer> otherGalaxyStars = galaxyMap.get(otherGalaxyName);
return galaxyStars.get(star) == otherGalaxyStars.get(star);
}
NOTE: You need to do some validation to make sure the input is correct.
To implement this for all stars, it is not much different.
if(galaxyStars.size() == otherGalaxyStars.size()) {
for(int x = 0; x < galaxyStars.size(); x++) {
// Perform your comparisons.
if(galaxyStars.get(x) != otherGalaxyStars.get(x)) {
// Uh oh, unequal.
return false;
}
}
}
If the structure of the inner maps also could differ, you should do something like that:
static boolean allStarValuesEqual(Map<String, Map<String, Integer>> galaxies) {
Map<String, Integer> refStars = null;
for (Map<String, Integer> galaxy : galaxies.values()) {
if (refStars == null) {
refStars = galaxy;
} else {
for (Entry<String, Integer> currentStar : galaxy.entrySet()) {
if (!currentStar.getValue().equals(refStars.get(currentStar.getKey()))) {
return false;
}
}
}
}
return true;
}
Please check below program along with output:
package com.test;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class CompareMapValues {
private final static String FS = "FirstStar";
private final static String SS = "SecondStar";
private final static String MW = "MilkyWay";
private final static String A = "Andromeda";
public static void main(String[] args) {
Map> map = new HashMap>();
Map innerMap1 = new HashMap();
innerMap1.put(FS, 3);
innerMap1.put(SS, 9);
Map innerMap2 = new HashMap();
innerMap2.put(FS, 10);
innerMap2.put(SS, 9);
map.put(MW, innerMap1);
map.put(A, innerMap2);
Set set = map.keySet();
for(String s: set) {
Map outerMap = map.get(s);
Set set2 = map.keySet();
for(String s2: set2) {
Map innerMap = map.get(s2);
if(!s2.equals(s)) {
Set set3 = outerMap.keySet();
for(String s3: set3) {
int i1 = outerMap.get(s3);
Set set4 = innerMap.keySet();
for(String s4: set4) {
int i2 = innerMap.get(s3);
if(s3.equals(s4) && i1==i2) {
System.out.println("For parent " + s + " for " + s3 + " value is " + i1);
}
}
}
}
}
}
}
}
//Output:
//For parent Andromeda for SecondStar value is 9
//For parent MilkyWay for SecondStar value is 9
Hope this helps.