// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later package it.univr.di.labeledvalue; import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap.BasicEntry; import it.unimi.dsi.fastutil.objects.*; import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; import it.univr.di.Debug; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serial; import java.io.Serializable; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Allows managing conjoined upper-case values that are also associated with propositional labels. *

* Labeled values are grouped by alphabetic labels {@link ALabel}. Each group of labeled values is represented as {@link LabeledIntTreeMap}. *

* Therefore, such a class realizes the map {@link ALabel}-->{@link LabeledIntTreeMap}. *

* Be careful! *

* Since lower-case values are singular for each edge, it is not convenient to represent them as LabeledALabelIntTreeMap. *

* A specialized class has been developed to represent such values: {@link LabeledLowerCaseValue}. *

* At the start of this implementation, I did some experiments to evaluate whether it is better to use an Object2ObjectRBTreeMap or an ObjectArrayMap to represent the internal map. *

* In October 2017, I verified that even with the medium-sized network, some edges can contain around 5000 labeled UC values. So, I tested the two implementations again, considering up to 10000 labeled UC values. It resulted that up to 1000 values, the two implementations show still almost equivalent performance, BUT when the keys are ≥5000, to retrieve the keys from ObjectArrayMap requires more than ONE hour, while it requires almost ~96 ms using Object2ObjectRBTreeMap! Details using Object2ObjectRBTreeMap: * *

 * Time to retrieve 50 elements using entrySet(): ---
 * Time to retrieve 50 elements using aLabelSet(): 0.012000000000000004ms
 *
 * Time to retrieve 100 elements using entrySet(): ---
 * Time to retrieve 100 elements using aLabelSet(): 0.006000000000000003ms
 *
 * Time to retrieve 1000 elements using entrySet(): 0.045ms
 * Time to retrieve 1000 elements using aLabelSet(): 0.034ms
 * The difference is 0.025000000000000015 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 5000 elements using entrySet(): 0.9623700000000001ms
 * Time to retrieve 5000 elements using aLabelSet(): 0.352ms
 * The difference is 0.6139400000000002 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 10000 elements using entrySet(): --
 * Time to retrieve 10000 elements using aLabelSet(): 1.292ms
 * 
* Considering then, RB Tree instead of RB Tree: * *
 * Time to retrieve 50 elements using aLabelSet(): 0.012000000000000002ms
 *
 * Time to retrieve 100 elements using aLabelSet(): 0.007ms
 *
 * Time to retrieve 1000 elements using entrySet(): ---
 * Time to retrieve 1000 elements using aLabelSet(): 0.038ms
 * The difference is 0.025000000000000015 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 5000 elements using entrySet(): ---
 * Time to retrieve 5000 elements using aLabelSet(): 0.388ms
 * The difference is 0.6139400000000002 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 10000 elements using entrySet(): --
 * Time to retrieve 10000 elements using aLabelSet(): 1.314ms
 * 
* * All code for testing is in LabeledALabelIntTreeMapTest class (not publicly available). * * @author Roberto Posenato * @version $Rev$ */ @SuppressWarnings("UnusedReturnValue") public class LabeledALabelIntTreeMap implements Serializable { /** * A read-only view of a LabeledALabelIntTreeMap object. * * @author posenato */ public static class LabeledALabelIntTreeMapView extends LabeledALabelIntTreeMap { /** * */ @Serial private static final long serialVersionUID = 1L; /** * @param inputMap the input map */ public LabeledALabelIntTreeMapView(LabeledALabelIntTreeMap inputMap) { this.map = inputMap.map; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(Label newLabel, ALabel newAlabel, int newValue, boolean force) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(Label l, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(String label, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(String label, ALabel p, int i, boolean force) { return false; } // /** // * Object Read-only. It does nothing. // */ // @Nullable // @Override // public LabeledIntTreeMap put(ALabel upperCaseLabel, LabeledIntMap labeledValueMap) { // return null; // } /** * Object Read-only. It does nothing. */ @Override public boolean putTriple(Label l, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public int remove(Label l, ALabel p) { return Constants.INT_NULL; } } /** * Keyword \w because it is necessary to accept also node names! */ @SuppressWarnings("RegExpRedundantEscape") static final String labelCharsRE = ALabelAlphabet.ALETTER + ALabel.ALABEL_SEPARATORstring + ",\\-" + Constants.NOTstring + Constants.EMPTY_LABELstring + Constants.INFINITY_SYMBOLstring + Constants.UNKNOWNstring + Constants.EMPTY_UPPER_CASE_LABELstring + Literal.PROPOSITIONS; /** * Matcher for RE */ @SuppressWarnings("RegExpRedundantEscape") static final Pattern patternLabelCharsRE = Pattern.compile("\\{[\\(" + labelCharsRE + "\\) ]*\\}"); /** * Matcher for RE */ private static final Pattern PARENTESIS_PATTERN = Pattern.compile("[{}]"); /** * logger */ private static final Logger LOG = Logger.getLogger(LabeledALabelIntTreeMap.class.getName()); /** * Serial number */ @Serial private static final long serialVersionUID = 3L; /** * Utility. * * @param label the input label * @param value the input 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 public String entryAsString(Label label, int value, ALabel nodeName) { return Constants.OPEN_PAIR + nodeName + ", " + Constants.formatInt(value) + ", " + label + Constants.CLOSE_PAIR; } /** * 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 formats:{@code "{[(⟨label⟩, ⟨Alabel⟩, ⟨value⟩) ]*}} or {@code "{[(⟨Alabel⟩, ⟨value⟩, ⟨label⟩) ]*}"}, where [a]* is a meta constructor for saying zero o more 'a'. * * @param arg a {@link String} object. * @param alphabet the alphabet to use to code the labels * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. * * @return a LabeledPairMap object if args represents a valid map, null otherwise. */ @Nullable public static LabeledALabelIntTreeMap parse(String arg, ALabelAlphabet alphabet, final Class labeledValueMapImple) { // final Pattern splitterNode = Pattern.compile("〈|; "); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { 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(labeledValueMapImple); arg = PARENTESIS_PATTERN.matcher(arg).replaceAll(""); // arg = arg.substring(1, arg.length() - 2); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Before split: '" + arg + "'"); } } @SuppressWarnings("RegExpSingleCharAlternation") final Pattern splitterEntry = Pattern.compile("\\)|\\("); final String[] entryThreesome = splitterEntry.split(arg); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { 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)) { 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)) { 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)) { 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)) { 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)) { LOG.finest("LabeledNode: " + node); } } newMap.mergeTriple(l, node, j, false); } } return newMap; } /** * Data structure. *

    *
  1. An Upper/Lower Case value is a pair (nodeName, value) where nodeName is the name of a node and can be written either in all UPPER case or in all lower case. Morris & Muscettola 2005 have introduced such a constraint. *
  2. A labeled Upper/Lower Case value is a pair (nodeName, (proposition_label, value)), where proposition_label represents a scenario where the value holds. Such a constraint was introduced by Hunsberger, Combi, and Posenato in 2012. *
  3. Each proposition_label is a conjunction of literals, i.e., of type {@link Label}.
  4. *
  5. Since there may be more pairs with the same 'nodeName', a labeled Upper/Lower Case value is represented as a map of (nodeName, LabeledIntMap).
    * See {@link LabeledIntMap}. *
  6. In 2017-10, `nodeName` was substituted by ALabel. ALabel represents the name of a node or a conjunction of node names.
    * Such a modification has been introduced because the CSTNU DC checking algorithm requires such values. *
*/ // ObjectArrayMap is not suitable when the map has more than 1000 values! Object2ObjectRBTreeMap map;//It is important to maintain the ALabel ordered. /** * Number of elements */ private int count; /** * Labeled value class used in the class. */ private Class labeledValueMapImpl; /** * Constructor to clone the structure. All internal maps will be independent of the 'lvm' ones. * * @param lvm the map to clone. If null, 'this' will be an empty map. * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. */ public LabeledALabelIntTreeMap(final LabeledALabelIntTreeMap lvm, final Class labeledValueMapImple) { this(labeledValueMapImple); if (lvm == null) { return; } final LabeledIntMapSupplier labeledIntMapSupplier = new LabeledIntMapSupplier<>(this.labeledValueMapImpl); for (final ALabel alabel : lvm.aLabelSet()) { final LabeledIntMap map1 = labeledIntMapSupplier.get(lvm.get(alabel)); map.put(alabel, map1); count += map1.size(); } } /** * Simple constructor. The internal structure is built and empty. * * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. */ public LabeledALabelIntTreeMap(final Class labeledValueMapImple) { map = new Object2ObjectRBTreeMap<>(); count = 0; this.labeledValueMapImpl = (labeledValueMapImple == null) ? LabeledIntMapSupplier.DEFAULT_LABELEDINTMAP_CLASS : labeledValueMapImple; } /** * Simple constructor. The internal structure is built and empty. */ private LabeledALabelIntTreeMap() { } /** * @return a set of all ALabels present in this map. The set is not modified if the map is modified after set creation. */ public ObjectSet aLabelSet() { return map.keySet(); } /** * @param newLabel it must not be null * @param newAlabel it must not be null * @param newValue the new value * * @return true if the current map can represent the value. In a positive case, the addition of the element does not change the map. If it returns false, then adding the value to the map would modify the map. */ public boolean alreadyRepresents(final Label newLabel, final ALabel newAlabel, final int newValue) { final LabeledIntMap map1 = map.get(newAlabel); if (map1 != null && map1.alreadyRepresents(newLabel, newValue)) { return true; } /* * Check if there is already a value in the map having a shorter ALabel that can represent the new value. */ final int newALabelSize = newAlabel.size(); for (final ALabel otherALabel : aLabelSet()) { if (newALabelSize <= otherALabel.size() || !newAlabel.contains(otherALabel)) { continue; } final LabeledIntMap labeledValuesOfOtherALabel = 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() { map.clear(); count = 0; } /** * The returned set is a view of the map. Don't modify the entries on this set and don't modify the map using methods like {@link #putTriple(Label, ALabel, int)} (with 2nd parameter = alpha) during the scan of this set. Otherwise, the correctness of this set is not guaranteed. * * @param alpha ALabel of the wanted labeled value map. * * @return a set of entries present in the map associated with `alpha` ALabel; an empty set if there is no such map. */ public ObjectSet> entrySet(final ALabel alpha) { ObjectSet> entrySet = new ObjectArraySet<>(); final LabeledIntMap localMap = map.get(alpha); if (localMap == null || localMap.isEmpty()) { return entrySet; } entrySet = localMap.entrySet(); return entrySet; } /** * Important * The returned set is a view of the map. Don't modify the entries on this set, and don't modify the map using methods like * {@link #putTriple(Label, ALabel, int)} (with 2nd parameter = alpha) during the scan of this set, otherwise the correctness of this set is not * guaranteed. * * @return a set of all entries present in the map associated; an empty set if there is no such map. */ public ObjectSet>> entrySet() { final ObjectSet>> result = new ObjectArraySet<>(); for (final ALabel aleph : aLabelSet()) { final LabeledIntMap localMap = map.get(aleph); for (final Entry