A Road to Trouble
When programming in Java, you generally have collections
of objects, as previously discussed here. Often, you want to create a variable (or constraint) for element of a collection. A bad approach is illustrated below:
Set<String> stringCollection = makeSomeStrings();//assume this is defined elsewhere
IloCplex cplex = new IloCplex();
String[] stringToIndex = new String[stringCollection.size()];
IloIntVar[] variablesToIndex = cplex.boolVarArray(stringCollection.size());
int i = 0;
for(String s: stringCollection){
stringToIndex[i++] = s;
}
Then given a String from the collection, we could access the corresponding IloIntVar, and with an IloIntVar, we would access the corresponding String, with the following snippets
public IloIntVar getVariableForString(String s, String[] stringToIndex, IloIntVar[] variablesToIndex){
for(int i = 0; i < stringToIndex.length; i++){
if(stringToIndex[i].equals(s)){
return variablesToIndex[i];
}
}
return null;
}
public String getStringForVariable(IloIntVar v, String[] stringToIndex, IloIntVar[] variablesToIndex){
for(int i = 0; i < variablesToIndex.length; i++){
if(variablesToIndex[i] == v){
return stringToIndex[i];
}
}
return null;
}
There are several reasons to be concerned with this.
- For
Unknown macro: {mathinline}variables, access time takes
n
Unknown macro: {mathinline}.O

- We are maintaining two data structures and an index by hand, which leave a lot of room for programmer error
- We cannot add new variables at a later date
We can eliminate any chance of an indexing error and improve our access time to
O(1)
by replacing our two arrays by two HashMaps
, e.g.
Set<String> stringCollection = makeSomeStrings();//assume this is defined elsewhere
IloCplex cplex = new IloCplex();
Map<String,IloIntVar> stringToVariable = new HashMap<String,IloIntVar>();
Map<IloIntVar,String> variableToString = new HashMap<IloIntVar,String>();
for(String s: stringCollection){
IloIntVar v = cplex.boolVar();
stringToVariable.put(s,v);
variableToString.put(v,s);
}
However, we are still maintaining two separate data structures which we need to keep synchronized, which is asking for trouble.
Good Style
Instead, we use Guava's
special data structure, the ImmutableBiMap
, which will maintain two HashMaps for us (and as a bonus, prevent any accidental modifications once we build the map). The following methods can be found in Util.java in your project:
public static <T> ImmutableBiMap<T,IloIntVar> makeBinaryVariables(IloCplex cplex, Iterable<T> set) throws IloException{
Builder<T,IloIntVar> ans = ImmutableBiMap.builder();
for(T t: set){
ans.put(t, cplex.boolVar());
}
return ans.build();
}
Now the unfortunate code from before can be replaced by
Set<String> stringCollection = makeSomeStrings();//assume this is defined elsewhere
IloCplex cplex = new IloCplex();
ImmutableBiMap<String,IloIntVar> stringVarBiMap = Util.makeBinaryVariables(cplex,stringCollection);
for(String s: stringCollection){
stringVarMap.put(s,cplex.boolVar());
}
public IloIntVar getVariableForString(String s, ImmutableBiMap<String,IloIntVar> stringVarBiMap){
return stringVarBiMap.get(s);
}
public String getStringForVariable(IloIntVar v, ImmutableBiMap<String,IloIntVar> stringVarBiMap){
return stringVarBiMap.inverse().get(v);
}
There are a few additional methods in the class Util for creating IloLinearIntExpr objects and IloLinearNumExpr objects designed to keep your code organized and error free. They use another Guava class, Function
, (see here
for Javadoc). The idea of a Function<F,T> is simple, they take in any object of type F and produce some object of type T. Functions are a little clunky to make (a weakness of Java), but fortunately you won't have to make many. The follwing static functions are also found in Util
Method Name |
Return Type |
Arguments |
Description |
|---|---|---|---|
integerSum |
IloLinearIntExpr |
IloCplex cplex, BiMap<T,IloIntVar> variables, Iterable<T> set |
For each Unknown macro: {mathinline}
e \in \text Unknown macro: {set} finds the variable Unknown macro: {mathinline}
x_e in variables and returns Unknown macro: {mathinline} \sum_{e \in \text{set}} x_e |
integerSum |
IloLinearIntExpr |
IloCplex cplex, BiMap<T,IloIntVar> variables, Iterable<T> set,Function<? super T,Integer> coefficients |
For each Unknown macro: {mathinline}
e \in \text Unknown macro: {set} finds the variable Unknown macro: {mathinline}
x_e in variables and Unknown macro: {mathinline}
c_e by applying coefficients to Unknown macro: {mathinline}
e and returns Unknown macro: {mathinline} \sum_{e \in \text{set}} c_e x_e |
Simple Example Revisited
Recall the IP we modeled with CPLEX in the previous section:
\begin
&\min & x + 2y + 3z
&\text
& x + y + z &\geq 2
&& x,y,z &\in{0,1}
\end
Lets design a more scalable implementation using our new methods. Try and finish the following stub.
List<String> varNames = Arrays.asList("x","y","z");
Map<String,Integer> weightsMap = new HashMap<String,Integer>();
weightsMap.put("x",1);
weightsMap.put("y",2);
weightsMap.put("z",3);
Function<String,Integer> weights = Functions.forMap(weightsMap);
Solution