package it.univr.di.labeledvalue;
import java.io.Serializable;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap.BasicEntry;
import it.unimi.dsi.fastutil.objects.AbstractObject2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectRBTreeMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import it.univr.di.Debug;
/**
* Allows to manage upper-case values that are also associated to propositional labels.
* Labeled (by {@link Label}) values are grouped by alphabetic labels {@link ALabel}.
* Each labeled value group is represented as LabeledIntTreeMap.
* Be careful!
* Since lower-case value are singular for each edge, it not convenient to represent it as TreeMap.
* A specialized class has been developed to represent such values: {@link LabeledLowerCaseValue}.
*
* At first time, I made some experiments for evaluating if it is better to use a Object2ObjectRBTreeMap or a ObjectArrayMap for representing the internal map. * The below table shows that for very small network, the two implementation are almost equivalent. So, ObjectArrayMap was chosen. * * *
| Operation | *Using Object2ObjectRBTreeMap (ms) | *Using ObjectArrayMap (ms) | *
|---|---|---|
| Create 1st map | *0.370336085 | *0.314329559 | *
| min value | *0.017957532 | *0.014711536 | *
| Retrieve value | *0.001397098 | *0.000600641 | *
| Simplification | *~0.183388 | *~0.120013 | *
* In October 2017, I verified that even with medium size network, some edges can contain around 5000 labeled UC values. * So, I tested the two implementation again considering up to 10000 labeled UC values. * It resulted that up to 1000 values, the two implementation show still almost equivalent performance, BUT when the keys are 5000, using ObjectArrayMap * retrieve the keys requires more than ONE hour, while using Object2ObjectRBTreeMap it requires almost 96. ms!!! * Details using Object2ObjectRBTreeMap: * *
* Time to retrieve 50 elements using entrySet(): --- * Time to retrieve 50 elements using keySet(): 0.012000000000000004ms * * Time to retrieve 100 elements using entrySet(): --- * Time to retrieve 100 elements using keySet(): 0.006000000000000003ms * * Time to retrieve 1000 elements using entrySet(): 0.045ms * Time to retrieve 1000 elements using keySet(): 0.034ms * The difference is 0.025000000000000015 ms. It is better to use: keySet() approach. * * Time to retrieve 5000 elements using entrySet(): 0.9623700000000001ms * Time to retrieve 5000 elements using keySet(): 0.352ms * The difference is 0.6139400000000002 ms. It is better to use: keySet() approach. * * Time to retrieve 10000 elements using entrySet(): -- * Time to retrieve 10000 elements using keySet(): 1.292ms ** * Considering then, RB Tree instead of RB Tree: * *
* Time to retrieve 50 elements using keySet(): 0.012000000000000002ms * * Time to retrieve 100 elements using keySet(): 0.007ms * * Time to retrieve 1000 elements using entrySet(): --- * Time to retrieve 1000 elements using keySet(): 0.038ms * The difference is 0.025000000000000015 ms. It is better to use: keySet() approach. * * Time to retrieve 5000 elements using entrySet(): --- * Time to retrieve 5000 elements using keySet(): 0.388ms * The difference is 0.6139400000000002 ms. It is better to use: keySet() approach. * * Time to retrieve 10000 elements using entrySet(): -- * Time to retrieve 10000 elements using keySet(): 1.314ms ** *
* All code for testing is in LabeledALabelIntTreeMapTest class (not public available).
*
* @author Roberto Posenato
* @version $Id: $Id
*/
public class LabeledALabelIntTreeMap implements Serializable {
/**
* A read-only view of an object
*
* @author posenato
*/
public static class LabeledALabelIntTreeMapView extends LabeledALabelIntTreeMap {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* @param inputMap
*/
public LabeledALabelIntTreeMapView(LabeledALabelIntTreeMap inputMap) {
this.map = inputMap.map;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public boolean mergeTriple(Label l, ALabel p, int i) {
return false;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public boolean mergeTriple(Label newLabel, ALabel newAlabel, int newValue, boolean force) {
return false;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public boolean mergeTriple(String label, ALabel p, int i) {
return false;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public boolean mergeTriple(String label, ALabel p, int i, boolean force) {
return false;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public LabeledIntTreeMap put(ALabel alabel, LabeledIntMap labeledValueMap) {
return null;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public boolean putTriple(Label l, ALabel p, int i) {
return false;
}
@Override
/**
* Object Read-only. It does nothing.
*/
public int remove(Label l, ALabel p) {
return Constants.INT_NULL;
}
}
/**
* Keyword \w because it is necessary to accept also node names!
*/
static final String labelCharsRE = ALabelAlphabet.ALETTER + ALabel.ALABEL_SEPARATORstring + ",\\-" + Constants.NOT + Constants.EMPTY_LABEL
+ Constants.INFINITY_SYMBOLstring + Constants.UNKNOWNstring + Constants.EMPTY_UPPER_CASE_LABELstring + Literal.PROPOSITIONS;
/**
* Matcher for RE
*/
static final Pattern patternlabelCharsRE = Pattern.compile("\\{[\\(" + labelCharsRE + "\\) ]*\\}");
/**
* logger
*/
private static final Logger LOG = Logger.getLogger("LabeledALabelIntTreeMap");
/**
*
*/
private static final long serialVersionUID = 2L;
/**
* @param label
* @param value
* @param nodeName this name is printed as it is. This method is necessary for saving the values of the map in a file.
* @return the canonical representation of the triple (as stated in ICAPS/ICAART papers), i.e.
* {@link Constants#OPEN_PAIR}Alabel, value, label{@link Constants#CLOSE_PAIR}
*/
static final public String entryAsString(Label label, int value, ALabel nodeName) {
StringBuffer s = new StringBuffer();
s.append(Constants.OPEN_PAIR);
s.append(nodeName);
s.append(", ");
s.append(Constants.formatInt(value));
s.append(", ");
s.append(label);
s.append(Constants.CLOSE_PAIR);
return s.toString();
}
/**
* Parse a string representing a LabeledValueTreeMap and return an object containing the labeled values represented by the string.
* The format of the string is given by the method {@link #toString()}.
* For historical reasons, the method is capable to parse two different map
* format:"{[(〈label〉, 〈Alabel〉, 〈value〉) ]*}
* or "{[(〈Alabel〉, 〈value〉, 〈label〉) ]*}", where [a]* is a meta constructor for saying zero o more 'a'.
*
* @param arg a {@link java.lang.String} object.
* @param alphabet
* @return a LabeledPairMap object if args represents a valid map, null otherwise.
*/
public static LabeledALabelIntTreeMap parse(String arg, ALabelAlphabet alphabet) {
// final Pattern splitterNode = Pattern.compile("〈|; ");
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("Begin parse: " + arg);
}
}
if ((arg == null) || (arg.length() < 3))
return null;
if (!patternlabelCharsRE.matcher(arg).matches())
return null;
final LabeledALabelIntTreeMap newMap = new LabeledALabelIntTreeMap();
arg = arg.replaceAll("[{}]", "");
// arg = arg.substring(1, arg.length() - 2);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("Before split: '" + arg + "'");
}
}
final Pattern splitterEntry = Pattern.compile("\\)|\\(");
final String[] entryThreesome = splitterEntry.split(arg);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("EntryThreesome: " + Arrays.toString(entryThreesome));
}
}
final Pattern splitterTriple = Pattern.compile(", ");
if (alphabet == null)
alphabet = new ALabelAlphabet();
int j;
String labelStr, aLabelStr, valueStr;
for (final String s : entryThreesome) {
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("s: '" + s + "'");
}
}
if (s.length() > 1) {// s can be empty or a space.
final String[] triple = splitterTriple.split(s);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("triple: " + Arrays.toString(triple));
}
}
Label l = Label.parse(triple[2]);
if (l == null) {
// probably it is the old format
labelStr = triple[0];
aLabelStr = triple[1];
valueStr = triple[2];
} else {
// new format
aLabelStr = triple[0];
valueStr = triple[1];
labelStr = triple[2];
}
if (l == null)
l = Label.parse(labelStr);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("Label: " + l);
}
}
if (valueStr.equals("-" + Constants.INFINITY_SYMBOLstring))
j = Constants.INT_NEG_INFINITE;
else
j = Integer.parseInt(valueStr);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("Value: " + j);
}
}
// LabeledNode is represented as " 〈; {}; Obs: null〉 "
// final String nodePart = labLitInt[1];//splitterNode.split(labLitInt[1]);
// System.out.println(aLabelStr);
final ALabel node = ALabel.parse(aLabelStr, alphabet);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LabeledALabelIntTreeMap.LOG.finest("LabeledNode: " + node);
}
}
newMap.mergeTriple(l, node, j, false);
}
}
return newMap;
}
/**
* Data structure.
*
* - A Upper/Lower Case value is a pair (nodeName, value) where nodeName is a name of a node
* and can be written either in all UPPER case or in all lower case.
* Such kind of constraint has been introduced by Morris Muscettola 2005.
*
- A labeled Upper/Lower Case value is a pair (nodeName, (plabel, value)), where plabel
* represents scenario where value holds.
* Such kind of constraint has been introduced by Hunsbergher, Combi, and Posenato in 2012.
*
- Each plabel is a conjunction of literals, i.e., of type {@link Label}.
* - Since there may be more pairs with the same 'nodeName', a labeled Upper/Lower Case value is as a map of (nodeName, LabeledIntMap). See
* {@link LabeledIntMap}.
*
- In 2017-10, nodeName has been substituted by Alabel. ALabel represent the name of a node or a conjunction of node names. Such modification has been
* introduced because CSTNU DC checking algorithm requires such kind of values.
*
- Since the introduction of ALabel, we suggest to use two @{link {@link LabeledALabelIntTreeMap}. One to represent the upper-case values.
* The other for lower-case ones.
*
*/
protected Object2ObjectRBTreeMap map;// ObjectArrayMap is not suitable when the map is greater than 1000 values!
/**
* Number of elements
*/
private int count;
/**
* Simple constructor. The internal structure is built and empty.
*/
public LabeledALabelIntTreeMap() {
this.map = new Object2ObjectRBTreeMap<>();
this.count = 0;
}
/**
* Constructor to clone the structure.
* All internal maps will be independent from lvm ones while elements of maps will be shared.
* The motivation is that usually a Label inside a map is managed as read-only.
*
* @param lvm the map to clone. If null, 'this' will be a empty map.
*/
public LabeledALabelIntTreeMap(final LabeledALabelIntTreeMap lvm) {
this();
if (lvm == null)
return;
for (final ALabel alabel : lvm.keySet()) {
final LabeledIntTreeMap map1 = new LabeledIntTreeMap(lvm.get(alabel));
this.map.put(alabel, map1);
this.count += map1.size();
}
}
/**
* @return a set view of this map. In particular, it returns a set of (ALabel, LabeledIntTreeMap) objects.
* Be careful: returned LabeledIntTreeMap(s) are not a copy but the maps inside this object.
* THIS METHOD HAS NOT A GOOD PERFORMANCE
* public ObjectSet> entrySet() {
* return this.map.entrySet();
* }
*/
/**
* @param newLabel it must be not null
* @param newAlabel it must be not null
* @param newValue
* @return true if the current map can represent the value. In positive case, an add of the element does not change the map.
* If returns false, then the adding of the value to the map would modify the map.
*/
public boolean alreadyRepresents(final Label newLabel, final ALabel newAlabel, final int newValue) {
final LabeledIntTreeMap map1 = this.map.get(newAlabel);
if (map1 != null && map1.alreadyRepresents(newLabel, newValue))
return true;
/**
* Check if there is already a value in the map having shorter ALabel that can represent the new value.
*/
final int newALabelSize = newAlabel.size();
for (ALabel otherALabel : this.keySet()) {
if (newALabelSize <= otherALabel.size() || !newAlabel.contains(otherALabel))
continue;
LabeledIntTreeMap labeledValuesOfOtherALabel = this.get(otherALabel);
if (labeledValuesOfOtherALabel.alreadyRepresents(newLabel, newValue)) {
// a smaller conjuncted upper case value map already contains the input value
return true;
}
}
return false;
}
/**
*
*/
public void clear() {
this.map.clear();
this.count = 0;
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object o) {
if (o == this)
return true;
if (!(o instanceof LabeledALabelIntTreeMap))
return false;
final LabeledALabelIntTreeMap lvm = (LabeledALabelIntTreeMap) o;
return this.map.equals(lvm.map);// this equals checks the size... so NO empty pair (key, {}) cannot be stored!
}
/**
* @param alabel
* @return the value to which the specified key is mapped, or null if this map contains no mapping for the key
*/
public final LabeledIntTreeMap get(final ALabel alabel) {
return this.map.get(alabel);
}
/**
* @return the minimal value of this map not considering upper/lower case label (node label), {@link Constants#INT_NULL} if the map is empty.
*/
public Object2ObjectMap.Entry