Context.java
package org.thegalactic.context;
/*
* Context.java
*
* Copyright: 2010-2015 Karell Bertet, France
* Copyright: 2015-2016 The Galactic Organization, France
*
* License: http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html CeCILL-B license
*
* This file is part of java-lattices.
* You can redistribute it and/or modify it under the terms of the CeCILL-B license.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import org.thegalactic.context.io.ContextIOFactory;
import org.thegalactic.dgraph.Node;
import org.thegalactic.io.Filer;
import org.thegalactic.lattice.ArrowRelation;
import org.thegalactic.lattice.ClosureSystem;
import org.thegalactic.lattice.Concept;
import org.thegalactic.lattice.ConceptLattice;
import org.thegalactic.lattice.Lattice;
import org.thegalactic.util.ComparableSet;
import org.thegalactic.util.Couple;
/**
* This class gives a standard representation for a context.
* A context is a binary table, with attributes in column, and observations
* in row.
*
* A context is composed of
*
* - attributes, a treeset of comparable objects;
* - observations, a treeset of comparable objects;
* - a Galois connexion (extent,intent) between objects and attributes where
* `extent` is a TreeMap that associates to each attribute a TreeSet of
* observations and
* `intent` is a TreeMap that associates to each observation a TreeSet of
* attributes.
*
* This class provides methods implementing classical operation on a context:
* closure, reduction, reverse, ...
*
* A context owns properties of a closure system, and thus extends the abstract
* class
* {@link ClosureSystem} and implements methods {@link #getSet} and
* {@link #closure}.
* Therefore, the closed set lattice of a context can be generated by invoking
* method
* {@link ClosureSystem#closedSetLattice} of a closure system.
* However, this class also provides a method generating the concept lattice of
* this component
* by completing each closed set of the closed set lattice.
*
* A context can be instancied from and save to a text file in the following
* format:
*
* - the list of observations separated by a space on the first line;
* - the list of attrbutes separated by a space on the second line;
* - then, for each observations o, the list of its intent on a line, written
* like o a1 a2 ...
*
* ~~~
* Observations: 1 2 3
* Attributes: a b c d e
* 1: a c
* 2: a b
* 3: b d e
* 4: c e
* ~~~
*
* ![Context](Context.png)
*
* @uml Context.png
* !include resources/org/thegalactic/context/Context.iuml
* !include resources/org/thegalactic/lattice/ClosureSystem.iuml
*
* hide members
* show Context members
* class Context #LightCyan
* title Context UML graph
*/
public class Context extends ClosureSystem {
/**
* Generates a partially random context.
*
* @param nbObs number of observations
* @param nbGrp number of groups of attributes . Attributes are
* grouped such that each observation has one
* attribute per group.
* @param nbAttrPerGrp number of attributes per group.
*
* @return randomly generated context
*/
public static Context random(int nbObs, int nbGrp, int nbAttrPerGrp) {
Context ctx = new Context();
StringBuilder name = new StringBuilder();
// Generates Observations.
for (int i = 1; i <= nbObs; i++) {
ctx.addToObservations(Integer.toString(i));
}
// Generates Attributes.
for (int i = 1; i <= nbGrp; i++) {
for (int j = 1; j <= nbAttrPerGrp; j++) {
int q = i;
name.setLength(0);
do {
int rem = q % 26;
q = q / 26;
name.append((char) (rem + 65));
} while (q != 0);
name.append(j);
ctx.addToAttributes(name.toString());
}
}
// Generates all requested observations.
Random r = new Random();
int attr = r.nextInt(nbAttrPerGrp) + 1;
for (int i = 1; i <= nbObs; i++) {
// i : Observation
for (int j = 1; j <= nbGrp; j++) {
// j : Familly
int q = j;
// Clear the StringBuilder
name.setLength(0);
do {
int rem = q % 26;
q = q / 26;
name.append((char) (rem + 65));
} while (q != 0);
name.append(attr);
ctx.addExtentIntent(Integer.toString(i), name.toString());
attr = r.nextInt(nbAttrPerGrp) + 1;
}
}
ctx.setBitSets();
return ctx;
}
/*
* ------------- FIELD ------------------
*/
/**
* A set of observations.
*/
private TreeSet<Comparable> observations;
/**
* A set of attributes.
*/
private TreeSet<Comparable> attributes;
/**
* A map to associate a set of attributes to each observation.
*/
private TreeMap<Comparable, TreeSet<Comparable>> intent;
/**
* A map to associate a set of observations to each attribute.
*/
private TreeMap<Comparable, TreeSet<Comparable>> extent;
/*
* ------------- BITSET ADDON ------------------
*/
/**
* A bit set for intent.
*/
private TreeMap<Comparable, BitSet> bitsetIntent;
/**
* A bit set for extent.
*/
private TreeMap<Comparable, BitSet> bitsetExtent;
/**
* An array for observations.
*/
private ArrayList<Comparable> arrayObservations;
/**
* An array for attributes.
*/
private ArrayList<Comparable> arrayAttributes;
/*
* ------------- CONSTRUCTORS ------------------
*/
/**
* Constructs a new empty context.
*/
public Context() {
this.init();
}
/**
* Constructs a new context as a copy of the specified context.
*
* @param context context to be copied
*/
public Context(Context context) {
this();
this.attributes.addAll(context.getAttributes());
this.observations.addAll(context.getObservations());
for (Comparable o : context.getObservations()) {
this.intent.put(o, new TreeSet(context.getIntent(o)));
}
for (Comparable a : context.getAttributes()) {
this.extent.put(a, new TreeSet(context.getExtent(a)));
}
this.setBitSets();
}
/**
* Constructs this component from the specified file.
*
* The file have to respect a certain format:
*
* The list of observations separated by a space on the first line ;
* the list of attrbutes separated by a space on the second line ;
* then, for each observations o, the list of its intent on a line, written
* like o a1 a2 ...
*
* ~~~
* Observations: 1 2 3
* Attributes: a b c d e
* 1: a c
* 2: a b
* 3: b d e
* 4: c e
* ~~~
*
* Each observation must be declared on the first line, otherwise, it is not
* added.
* Each attribute must be declared on the second line, otherwise, it is not
* added.
*
* @param filename the name of the file
*
* @throws IOException When an IOException occurs
*/
public Context(String filename) throws IOException {
this.parse(filename);
}
/**
* Initialise the context.
*
* @return this for chaining
*/
public Context init() {
this.observations = new TreeSet();
this.attributes = new TreeSet();
this.intent = new TreeMap();
this.extent = new TreeMap();
this.bitsetIntent = new TreeMap();
this.bitsetExtent = new TreeMap();
this.arrayObservations = new ArrayList();
this.arrayAttributes = new ArrayList();
return this;
}
/**
* Returns subcontext with selected obs and attr.
*
* @param obs : observations to be keeped
* @param attr : attributes to be keeped
*
* @return subcontext with selected obs and attr.
*/
public Context getSubContext(TreeSet<Comparable> obs, TreeSet<Comparable> attr) {
Context ctx = new Context();
ctx.addAllToAttributes(attr);
ctx.addAllToObservations(obs);
for (Comparable o : obs) {
for (Comparable a : attr) {
if (this.getIntent(o).contains(a)) {
ctx.addExtentIntent(o, a);
}
}
}
ctx.setBitSets();
return ctx;
}
/**
* Returns the context defining the concept lattice of arrow-closed
* subcontexts of this component.
*
* Each observation of the returned context is a $1$-generated arrow-closed
* subcontext.
* The observation used to generate the context is used as a name for the
* subcontext.
* Attributes are the same of this component, and are used to defined the
* subcontext.
*
* @return the context defining the concept lattice of arrow-closed
* subcontexts of this component.
*/
public Context getArrowClosedSubContext() {
Context result = new Context();
result.addAllToAttributes(this.getAttributes());
for (Comparable o : this.getObservations()) {
result.addToObservations(o);
TreeSet<Comparable> setO = new TreeSet<Comparable>();
setO.add(o);
Context c = this.arrowClosureObject(setO);
for (Comparable a : c.getAttributes()) {
result.addExtentIntent(o, a);
}
}
return result;
}
/**
* Returns a list of subcontexts such that the concept lattice of this
* component is obtained from a subcontext by doubling a convex.
*
* Each subcontext defines a concept lattice L. With the getDivisionConvex
* method, a convex C of this conceptĀ lattice is obtained.
* By doubling the convex set C of L, we get L[C], the concept lattice of
* this component.
*
* @return a list of subcontexts such that the concept lattice of this
* component is obtained from a subcontext by doubling a convex.
*/
public ArrayList<Context> getDivisionContext() {
Context arrowCtx = this.getArrowClosedSubContext();
arrowCtx.reverse(); // We need co-atoms which are atoms of the reverse context.
ConceptLattice clArrowClosed = arrowCtx.conceptLattice(true); // This lattice is fun :-)
Vector<TreeSet<Comparable>> coAtoms = clArrowClosed.immediateSuccessors(clArrowClosed.bottom(), arrowCtx);
// Check if the complement of coAtoms is "empty".
// Only these coAtoms are kept
ArrayList<Context> goodCoAtoms = new ArrayList<Context>();
// Be careful that the concept has been reversed
for (int i = 0; i < coAtoms.size(); i++) {
TreeSet<Comparable> attrComp = (TreeSet<Comparable>) this.getAttributes().clone(); // Initial context
TreeSet<Comparable> attr = arrowCtx.getExtent(coAtoms.get(i));
attrComp.removeAll(attr); // As arrowCtx is reversed, Extent means Intent.
TreeSet<Comparable> obsComp = (TreeSet<Comparable>) this.getObservations().clone(); // Initial context
TreeSet<Comparable> obs = this.arrowClosureObject(coAtoms.get(i)).getObservations();
obsComp.removeAll(obs);
boolean cross = false; // If there is a cross, it is not empty.
for (Comparable o : obsComp) {
for (Comparable a : attrComp) {
cross = cross || this.getIntent(o).contains(a);
}
}
if (!cross) {
Context subContext = this.getSubContext(obs, attr);
goodCoAtoms.add(subContext);
}
}
return goodCoAtoms;
}
/**
* Returns a convex set of the concept lattice of c which can be doubled to
* recover the concept lattice of this component.
*
* This method must be used with contexts returned by the
* getDivisionContext. In other cases, it is meaningless.
*
* @param ctx context from which the convex set is computed.
*
* @return a convex set of the concept lattice of c which can be doubled to
* recover the concept lattice of this component.
*/
public TreeSet<Node> getDivisionConvex(Context ctx) {
ConceptLattice factor = ctx.conceptLattice(true);
TreeSet<Node> convex = new TreeSet<Node>();
// TODO Use parameterized Node
for (Node node : (SortedSet<Node>) factor.getNodes()) {
Concept c = (Concept) node;
if (!c.getSetB().containsAll(this.getExtent(c.getSetA()))
&& !c.getSetA().containsAll(this.getIntent(c.getSetB()))) {
convex.add(node);
}
}
return convex;
}
/*
* --------------- HANDLING METHODS FOR ATTRIBUTES AND OBSERVATIONS ------------
*/
/**
* Returns the set of attributes of this component.
*
* @return the set of attributes of this component
*/
public TreeSet<Comparable> getAttributes() {
return this.attributes;
}
/**
* Checks if the specified attribute belong to this component.
*
* @param att an attribute
*
* @return true if the attribute belongs to this component
*/
public boolean containsAttribute(Comparable att) {
return this.attributes.contains(att);
}
/**
* Checks if the specified set of attributes belongs to this component.
*
* @param set set of attributes
*
* @return true if all attributes belong to this component
*/
public boolean containsAllAttributes(TreeSet<Comparable> set) {
return this.attributes.containsAll(set);
}
/**
* Adds the specified element to the set of attributes of this component.
*
* @param att an attribute
*
* @return true if the attribute was successfully added
*/
public boolean addToAttributes(Comparable att) {
if (!this.containsAttribute(att)) {
this.extent.put(att, new TreeSet<Comparable>());
}
boolean ok = this.attributes.add(att);
this.setBitSets();
return ok;
}
/**
* Adds the set of specified element to the set of attributes of this
* component.
*
* @param set set of attributes
*
* @return true if all attributes were successfully added
*/
public boolean addAllToAttributes(TreeSet<Comparable> set) {
boolean all = true;
for (Comparable att : set) {
if (!this.addToAttributes(att)) {
all = false;
}
}
this.setBitSets();
return all;
}
/**
* Removes the specified element from the set of attributes of this
* component
* and from all the intents it belongs to.
*
* @param att an attribute
*
* @return true if the attribute was successfully removed
*/
public boolean removeFromAttributes(Comparable att) {
this.extent.remove(att);
for (Comparable o : this.getObservations()) {
this.intent.get(o).remove(att);
}
boolean ok = this.attributes.remove(att);
this.setBitSets();
return ok;
}
/**
* Returns the set of observations of this component.
*
* @return the set of observations
*/
public TreeSet<Comparable> getObservations() {
return this.observations;
}
/**
* Checks if the specified observation belongs to this component.
*
* @param obs an observation
*
* @return true if the observation belongs to this component
*/
public boolean containsObservation(Comparable obs) {
return this.observations.contains(obs);
}
/**
* Checks if the specified set of observations belong to this component.
*
* @param set set of observations
*
* @return true if all the observations are in this component
*/
public boolean containsAllObservations(TreeSet<Comparable> set) {
return this.observations.containsAll(set);
}
/**
* Adds the specified element to the set of observations of this component.
*
* @param obs an observation
*
* @return true if the observation was successfully added
*/
public boolean addToObservations(Comparable obs) {
if (!this.containsObservation(obs)) {
this.intent.put(obs, new TreeSet<Comparable>());
}
boolean ok = this.observations.add(obs);
this.setBitSets();
return ok;
}
/**
* Adds the set of specified element to the set of observations of this
* component.
*
* @param set set of observations
*
* @return true if all observations were successfully added
*/
public boolean addAllToObservations(TreeSet<Comparable> set) {
boolean all = true;
for (Comparable obs : set) {
if (!this.addToObservations(obs)) {
all = false;
}
}
this.setBitSets();
return all;
}
/**
* Removes the specified element from the set of observations of this
* component.
* and from all the extents it belongs to
*
* @param obs an observation
*
* @return true if the observation was removed
*/
public boolean removeFromObservations(Comparable obs) {
this.intent.remove(obs);
for (Comparable att : this.getAttributes()) {
this.extent.get(att).remove(obs);
}
boolean ok = this.observations.remove(obs);
this.setBitSets();
return ok;
}
/**
* Set the needed structures for the bitset optimization.
* WARNING: this must be called each time your dataset change
*/
public void setBitSets() {
this.setMaps();
this.setBitSetsIntentExtent();
}
/**
* Set the mapping structure for the bitset optimization.
*/
private void setMaps() {
this.arrayAttributes = new ArrayList();
this.arrayObservations = new ArrayList();
Iterator<Comparable> i = this.attributes.iterator();
while (i.hasNext()) {
this.arrayAttributes.add(i.next());
}
i = this.observations.iterator();
while (i.hasNext()) {
this.arrayObservations.add(i.next());
}
}
/**
* Set the extent and intent structures for the bitset optimization.
*/
private void setBitSetsIntentExtent() {
this.bitsetIntent = new TreeMap();
this.bitsetExtent = new TreeMap();
Iterator<Comparable> i = this.attributes.iterator();
BitSet b = new BitSet(this.observations.size());
while (i.hasNext()) {
Comparable att = i.next();
for (Comparable c : this.extent.get(att)) {
b.set(this.arrayObservations.indexOf(c));
}
this.bitsetExtent.put(att, (BitSet) b.clone());
b.clear();
}
i = this.observations.iterator();
b = new BitSet(this.attributes.size());
while (i.hasNext()) {
Comparable obs = i.next();
for (Comparable c : this.intent.get(obs)) {
b.set(this.arrayAttributes.indexOf(c));
}
this.bitsetIntent.put(obs, (BitSet) b.clone());
b.clear();
}
}
/*
* --------------- HANDLING METHODS FOR INTENT AND EXTENT ------------
*/
/**
* Returns the set of attributes that are intent of the specified
* observation.
*
* @param obs an observation
*
* @return the set of attributes
*/
public TreeSet<Comparable> getIntent(Comparable obs) {
if (this.containsObservation(obs)) {
return this.intent.get(obs);
} else {
return new TreeSet();
}
}
/**
* Returns the set of attributes that are all intent of observations of the
* specified set.
*
* @param set set of observations
*
* @return the set of observations
*/
public TreeSet<Comparable> getIntent(TreeSet<Comparable> set) {
TreeSet<Comparable> resIntent = new TreeSet(this.getAttributes());
for (Comparable obs : set) {
resIntent.retainAll(this.getIntent(obs));
}
return resIntent;
}
/**
* Return the number of attributes that are all intent of observations of
* the specified set.
*
* @param set set of observations
*
* @return the number of attributes
*/
public int getIntentNb(TreeSet<Comparable> set) {
int size = this.getAttributes().size();
BitSet obsIntent = new BitSet(size);
obsIntent.set(0, size);
for (Comparable obs : set) {
try {
obsIntent.and(this.bitsetIntent.get(obs));
} catch (NullPointerException e) {
return 0;
}
}
return obsIntent.cardinality();
}
/**
* Checks if the second specified element is an intent of the first
* specified element.
*
* @param obs an observation
* @param att an attribute
*
* @return true if the attribute is an intent of the observation
*/
public boolean containAsIntent(Comparable obs, Comparable att) {
if (this.containsObservation(obs) && this.containsAttribute(att)) {
return this.intent.get(obs).contains(att);
} else {
return false;
}
}
/**
* Returns the set of observations that are intent of the specified
* attribute.
*
* @param att an attribute
*
* @return the set of observations
*/
public TreeSet<Comparable> getExtent(Comparable att) {
if (this.containsAttribute(att)) {
return this.extent.get(att);
} else {
return new TreeSet();
}
}
/**
* Returns the set of observations that are all intent of attributes of the
* specified set.
*
* @param set set of attributes
*
* @return the set of observations
*/
public TreeSet<Comparable> getExtent(TreeSet<Comparable> set) {
TreeSet<Comparable> attExtent = new TreeSet(this.getObservations());
for (Comparable att : set) {
attExtent.retainAll(this.getExtent(att));
}
return attExtent;
}
/**
* Return the number of observations that are all intent of attributes of
* the specified set.
*
* @param set set of attributes
*
* @return the number of observations
*/
public int getExtentNb(TreeSet<Comparable> set) {
int size = this.getObservations().size();
BitSet attExtent = new BitSet(size);
attExtent.set(0, size);
for (Comparable att : set) {
try {
attExtent.and(this.bitsetExtent.get(att));
} catch (NullPointerException e) {
return 0;
}
}
return attExtent.cardinality();
}
/**
* Checks if the second specified element is an extent of the first
* specified element.
*
* @param att an attribute
* @param obs an observation
*
* @return true if the proposition is true
*/
public boolean containAsExtent(Comparable att, Comparable obs) {
if (this.containsObservation(obs) && this.containsAttribute(att)) {
return this.extent.get(att).contains(obs);
} else {
return false;
}
}
/**
* Adds the second specified element as intent of the first one,
* and the first one as extent of the second one.
* The first one has to belong to the observations set
* and the second one to the attribute set.
*
* @param obs an observation
* @param att an attribute
*
* @return true if both were added
*/
public boolean addExtentIntent(Comparable obs, Comparable att) {
if (this.containsObservation(obs) && this.containsAttribute(att)) {
boolean ok = this.intent.get(obs).add(att) && this.extent.get(att).add(obs);
this.setBitSets();
return ok;
} else {
return false;
}
}
/**
* Removes the second specified element from the intent of the first one,
* and the first one from the extent of the second one.
* The first one has to belong to the observations set
* and the second one to the attribute set.
*
* @param obs an observation
* @param att an attribute
*
* @return true if both were removed
*/
public boolean removeExtentIntent(Comparable obs, Comparable att) {
if (this.containsObservation(obs) && this.containsAttribute(att)) {
boolean ok = this.intent.get(obs).remove(att) && this.extent.get(att).remove(obs);
this.setBitSets();
return ok;
} else {
return false;
}
}
/*
* --------------- CONTEXT HANDLING METHODS ------------
*/
/**
* Returns a String representation of this component.
* The following format is respected:
*
* The list of observations separated by a space on the first line ;
* the list of attrbutes separated by a space on the second line ;
* then, for each observations o, the list of its intent on a line, written
* like o a1 a2 ...
*
* ~~~
* Observations: 1 2 3
* Attributes: a b c d e
* 1: a c
* 2: a b
* 3: b d e
* 4: c e
* ~~~
*
* @return the string representation of this component
*
* @todo Enforce test
*/
@Override
public String toString() {
String string;
StringBuilder builder = new StringBuilder();
builder.append("Observations: ");
for (Comparable o : this.observations) {
// first line : All observations separated by a space
// a StringTokenizer is used to delete spaces in the
// string description of each observation
string = o.toString();
if (string.contains(" ") || string.contains("\"") || string.contains("\n") || string.contains("\t")) {
string = "\"" + string.replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t") + "\"";
}
builder.append(string).append(" ");
}
String newLine = System.getProperty("line.separator");
builder.append(newLine).append("Attributes: ");
for (Comparable a : this.attributes) {
// second line : All attributes separated by a space
// a StringTokenizer is used to delete spaces in the
// string description of each observation
string = a.toString();
if (string.contains(" ") || string.contains("\"") || string.contains("\n") || string.contains("\t")) {
string = "\"" + string.replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t") + "\"";
}
builder.append(string).append(" ");
}
// next lines : All intents of observations, one on each line:
// observation : list of attributes
// a StringTokenizer is used to delete spaces in the
// string description of each observation and attributes
builder.append(newLine);
for (Comparable o : this.observations) {
string = o.toString();
if (string.contains(" ") || string.contains("\"") || string.contains("\n") || string.contains("\t")) {
string = "\"" + string.replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t") + "\"";
}
builder.append(string).append(": ");
for (Comparable a : this.getIntent(o)) {
string = a.toString();
if (string.contains(" ") || string.contains("\"") || string.contains("\n") || string.contains("\t")) {
string = "\"" + string.replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t") + "\"";
}
builder.append(string).append(" ");
}
builder.append(newLine);
}
return builder.toString();
}
/**
* Save the description of this component in a file whose name is specified.
*
* @param filename the name of the file
*
* @throws IOException When an IOException occurs
*/
public void save(final String filename) throws IOException {
Filer.getInstance().save(this, ContextIOFactory.getInstance(), filename);
}
/**
* Parse the description of this component from a file whose name is
* specified.
*
* @param filename the name of the file
*
* @return this for chaining
*
* @throws IOException When an IOException occurs
*/
public Context parse(final String filename) throws IOException {
this.init();
Filer.getInstance().parse(this, ContextIOFactory.getInstance(), filename);
return this;
}
/**
* Removes from this component reducible attributes.
*
* Reducible attributes are attributes equivalent by closure to others
* attributes.
* They are computed by `getReducibleElements` od `ClosureSystem` in
* O(|A|^3|O|)
*
* @return the set of reducibles removed attributes, with their equivalent
* attributes
*/
public TreeMap<Comparable, TreeSet<Comparable>> attributesReduction() {
// compute the reducible elements
TreeMap red = this.getReducibleElements();
// remove the reducible elements from the attributes set
for (Object att : red.keySet()) {
this.removeFromAttributes((Comparable) att);
}
return red;
}
/**
* Removes from this component reducible observations.
*
* Reducible observations are attributes equivalent by closure to others
* observations.
* They are computed by `getReducibleElements` od `ClosureSystem`
* applied on the reverse context in O(|O|^3|A|)
*
* @return the set of reducibles removed attributes, with their equivalent
* attributes
*/
public TreeMap<Comparable, TreeSet<Comparable>> observationsReduction() {
// compute the reducible elements of the reverse context
this.reverse();
TreeMap red = this.getReducibleElements();
this.reverse();
// remove the reducible elements from the observations set
for (Object att : red.keySet()) {
this.removeFromObservations((Comparable) att);
}
return red;
}
/**
* Removes from this component reducible attributes and observations.
*
* They are computed by `attributesReduction` then
* `observationsReduction` in O(|A|^3|O|+|O|^3|A|)
*
* @return the set of reducibles removed attributes and observations with
* their equivalent elements
*/
public TreeMap<Comparable, TreeSet<Comparable>> reduction() {
TreeMap<Comparable, TreeSet<Comparable>> red = this.attributesReduction();
red.putAll(this.observationsReduction());
return red;
}
/**
* Reverses this component by replacing attributes by observations and
* observations by
* attributes. Intent and extent are exchanged in the same way.
*/
public void reverse() {
TreeSet<Comparable> tmp = this.attributes;
this.attributes = this.observations;
this.observations = tmp;
TreeMap<Comparable, TreeSet<Comparable>> sauv = this.intent;
this.intent = this.extent;
this.extent = sauv;
}
/**
* Return a new reversed Context.
*
* @return a new reversed Context
*/
public Context getReverseContext() {
Context context = new Context(this);
context.reverse();
context.setBitSets();
return context;
}
/**
* Returns the arrow-closed subcontext of this component containing obs.
*
* A sub-context (H,N) of (G,M) is arrow-closed if :
* 1. For all h in H, h uparrow m implies m in N and
* 2. For all n in N, g downarrow n implies g in H
*
* @param obs set of observations to keep
*
* @return the arrow-closed subcontext of this component containing obs.
*/
public Context arrowClosureObject(TreeSet<Comparable> obs) {
ConceptLattice cl = this.getReverseContext().conceptLattice(true); // Where is bug with it ^^
ArrowRelation ar = cl.getArrowRelation();
/*
WARNING : this component contains "observations" and "attributes"
whereas following contexts, down and up, are made of concept.
*/
Context down = ar.getDoubleDownArrowTable();
Context up = ar.getDoubleUpArrowTable();
Context ctx = new Context();
ctx.addAllToObservations(obs);
int sizeObs = ctx.getObservations().size();
int sizeAttr = ctx.getAttributes().size();
int prevObs = 0;
int prevAttr = 0;
while ((prevObs < sizeObs) || (prevAttr < sizeAttr)) {
for (Comparable o : ctx.getObservations()) {
// Concept corresponding to observation o
TreeSet<Comparable> setBo = this.getIntent(o);
TreeSet<Comparable> setAo = this.getExtent(setBo);
Concept cptO = new Concept(setAo, setBo);
TreeSet<Comparable> attrUp = up.getIntent(cl.getNode(cptO)); // Doesn't work with up.getIntent(cptO) ...
for (Comparable a : this.getAttributes()) { // NOT GOOD for complexity :-(
// Try to find attributes in up-arrow relation
TreeSet<Comparable> setAa = this.getExtent(a);
TreeSet<Comparable> setBa = this.getIntent(setAa);
Concept cptA = new Concept(setAa, setBa); // Concept corresponding to attribute a
if (attrUp.contains(cl.getNode(cptA))) {
ctx.addToAttributes(a);
}
}
}
for (Comparable a : ctx.getAttributes()) {
// Concept corresponding to observation a
TreeSet<Comparable> setAa = this.getExtent(a);
TreeSet<Comparable> setBa = this.getIntent(setAa);
Concept cptA = new Concept(setAa, setBa);
TreeSet<Comparable> obsDown = down.getExtent(cl.getNode(cptA));
for (Comparable o : this.getObservations()) {
// Try to find attributes in down-arrow relation
TreeSet<Comparable> setBo = this.getIntent(o);
TreeSet<Comparable> setAo = this.getExtent(setBo);
Concept cptO = new Concept(setAo, setBo); // Concept corresponding to attribute o
if (obsDown.contains(cl.getNode(cptO))) {
ctx.addToObservations(o);
}
}
}
prevObs = sizeObs;
prevAttr = sizeAttr;
sizeObs = ctx.getObservations().size();
sizeAttr = ctx.getAttributes().size();
}
return this.getSubContext(ctx.getObservations(), ctx.getAttributes());
}
/**
* Returns the arrow-closed subcontext of this component containing attr.
*
* A sub-context (H,N) of (G,M) is arrow-closed if :
* 1. For all h in H, h uparrow m implies m in N and
* 2. For all n in N, g downarrow n implies g in H
*
* @param attr set of attributes to keep
*
* @return the arrow-closed subcontext of this component containing attr.
*/
public Context arrowClosureAttribute(TreeSet<Comparable> attr) {
ConceptLattice cl = this.getReverseContext().conceptLattice(true); // Where is bug with it ^^
ArrowRelation ar = cl.getArrowRelation();
Context down = ar.getDoubleDownArrowTable();
Context up = ar.getDoubleUpArrowTable();
Context ctx = new Context();
ctx.addAllToAttributes(attr);
int sizeObs = ctx.getObservations().size();
int sizeAttr = ctx.getAttributes().size();
int prevObs = 0;
int prevAttr = 0;
while ((prevObs < sizeObs) || (prevAttr < sizeAttr)) {
for (Comparable a : ctx.getAttributes()) {
// Concept corresponding to observation a
TreeSet<Comparable> setAa = this.getExtent(a);
TreeSet<Comparable> setBa = this.getIntent(setAa);
Concept cptA = new Concept(setAa, setBa);
TreeSet<Comparable> obsDown = down.getExtent(cl.getNode(cptA));
for (Comparable o : this.getObservations()) {
// Try to find attributes in down-arrow relation
TreeSet<Comparable> setBo = this.getIntent(o);
TreeSet<Comparable> setAo = this.getExtent(setBo);
Concept cptO = new Concept(setAo, setBo); // Concept corresponding to attribute o
if (obsDown.contains(cl.getNode(cptO))) {
ctx.addToObservations(o);
}
}
}
for (Comparable o : ctx.getObservations()) {
// Concept corresponding to observation o
TreeSet<Comparable> setBo = this.getIntent(o);
TreeSet<Comparable> setAo = this.getExtent(setBo);
Concept cptO = new Concept(setAo, setBo);
TreeSet<Comparable> attrUp = up.getIntent(cl.getNode(cptO)); // Doesn't work with up.getIntent(cptO) ...
for (Comparable a : this.getAttributes()) { // NOT GOOD for complexity :-(
// Try to find attributes in up-arrow relation
TreeSet<Comparable> setAa = this.getExtent(a);
TreeSet<Comparable> setBa = this.getIntent(setAa);
Concept cptA = new Concept(setAa, setBa); // Concept corresponding to attribute a
if (attrUp.contains(cl.getNode(cptA))) {
ctx.addToAttributes(a);
}
}
}
prevObs = sizeObs;
prevAttr = sizeAttr;
sizeObs = ctx.getObservations().size();
sizeAttr = ctx.getAttributes().size();
}
return this.getSubContext(ctx.getObservations(), ctx.getAttributes());
}
/*
* --------------- IMPLEMENTATION OF CLOSURE SYSTEM ABSTRACT METHODS ------------
*/
/*
* --------------- AND CONCEPT LATTICE GENERATION------------
*/
/**
* Returns the set of attributes as elements set used by the lattice
* generator abstract class
* to generate closed set lattice on attributes. The closed set lattice on
* abservations can
* be otained using the reverse method of this class.
*
* @return the set of attributes
*/
@Override
public TreeSet<Comparable> getSet() {
return this.attributes;
}
/**
* Builds the closure of a set X of attributes.
*
* The closure corresponds to the maximal set of attributes having the
* same intent as the specified one.
*
* This treatment is performed in O(|A||O|)
*
* @param set a TreeSet of indexed elements
*
* @return the closure of the set for this component
*/
@Override
public TreeSet<Comparable> closure(TreeSet<Comparable> set) {
return this.getIntent(this.getExtent(set));
}
/**
* Returns the set of union of observations that are intent with one of
* attributes of the specified set.
*
* @param set a specified set
*
* @return the set of union of observations
*/
public TreeSet<Comparable> getExtentUnion(TreeSet<Comparable> set) {
TreeSet<Comparable> ext = new TreeSet();
for (Comparable att : set) {
for (Comparable obs : this.getExtent(att)) {
if (this.containAsExtent(att, obs) && !ext.contains(obs)) {
ext.add(obs);
}
}
}
return ext;
}
/**
* Builds the inverse of the closure operator of a set of observations.
*
* The inverse closure corresponds to the maximal set of observations having
* the
* same intent as the specified one.
* This treatment is performed in O(|A||O|)
*
* @param set a TreeSet of indexed elements
*
* @return the closure of the set for this component
*/
public ComparableSet inverseClosure(ComparableSet set) {
return new ComparableSet(this.getExtent(this.getIntent((TreeSet) set)));
}
/**
* Returns the concept lattice of this component.
*
* A true value of the boolean `diagram` indicates that the
* Hasse diagramm of the lattice is computed (i.e. it is transitively
* reduced),
* whereas a false value indicates that the lattice is transitively closed
*
* The closed set lattice is first generated using
* `ConceptLattice closedSetLattice (boolean diagram)`
* Then, nodes of the lattice are completed as concepts.
*
* @param diagram a boolean indicating if the Hasse diagramm of the lattice
* is computed or not.
*
* @return The concept lattice induced by this component
*/
public ConceptLattice conceptLattice(boolean diagram) {
ConceptLattice csl = this.closedSetLattice(diagram);
// TreeMap<Concept, Concept> nodes = new TreeMap<Concept, Concept>();
for (Object node : csl.getNodes()) {
Concept cl = (Concept) node;
cl.putSetB(new ComparableSet(this.getExtent(cl.getSetA())));
}
return csl;
}
/**
* Reccursively generates nodes of the product lattice.
*
* @param c couple to be completed
* @param clParts list of last context to deal with
*
* @return a list of nodes to add to the product.
*/
private ArrayList<Couple> recursiveGenProd(Couple c, LinkedList<ConceptLattice> clParts) {
LinkedList<ConceptLattice> copy = (LinkedList<ConceptLattice>) clParts.clone();
if (copy.isEmpty()) {
ArrayList<Couple> result = new ArrayList<Couple>();
result.add(c);
return result;
} else {
ConceptLattice cl = (ConceptLattice) copy.poll();
ArrayList<Couple> nodes = new ArrayList<Couple>();
for (Object node : cl.getNodes()) {
ArrayList<Concept> listCopy = new ArrayList<Concept>();
for (Concept cpt : (ArrayList<Concept>) c.getLeft()) {
listCopy.add(cpt);
}
Couple coupleCopy = new Couple(listCopy, c.getRight());
((ArrayList<Concept>) coupleCopy.getLeft()).add((Concept) node);
nodes.addAll(recursiveGenProd(coupleCopy, copy));
}
return nodes;
}
}
/**
* Returns the concept lattice of this component represented as a subdirect
* product of its irreductibles components.
*
* WARNING : Context MUST BE REDUCED !
*
* @return concept Lattice of this component represented as a subdirect
* product of its irreductibles components.
*/
public Lattice subDirectDecomposition() {
// First, compute 1-generated arrow-closed subcontextes
ArrayList<Context> parts = new ArrayList<Context>();
for (Comparable o : this.getObservations()) {
TreeSet<Comparable> setO = new TreeSet<Comparable>();
setO.add(o);
parts.add(this.arrowClosureObject(setO));
}
// Second, remove contexts contained in other. They are dispendable.
// Remove first all contexts that appeared at least twice.
ArrayList<Context> single = new ArrayList<Context>();
for (int i = 0; i < parts.size(); i++) {
boolean containedNext = false;
for (int j = i + 1; j < parts.size(); j++) {
containedNext = containedNext || (parts.get(i).containsAllObservations(parts.get(j).getObservations())
&& parts.get(j).containsAllObservations(parts.get(i).getObservations())
&& parts.get(i).containsAllAttributes(parts.get(j).getAttributes())
&& parts.get(j).containsAllAttributes(parts.get(i).getAttributes()));
}
if (!containedNext) {
single.add(parts.get(i));
}
}
parts = single;
ArrayList<Context> toBeRemoved = new ArrayList<Context>();
for (Context remove : parts) {
for (Context test : parts) {
if ((parts.indexOf(test) != parts.indexOf(remove))
&& (test.containsAllObservations(remove.getObservations()))
&& (test.containsAllAttributes(remove.getAttributes()))) {
toBeRemoved.add(remove);
}
}
}
parts.removeAll(toBeRemoved);
// Third, compute the product but can't use LatticeFactory.product :-(
/*
* Content of each node is of the following form :
* 1. They are Couple
* 2. Left part is an ArrayList corresponding to the terms of the product
* 3. Right part is a boolean, true if the node is inside the sub-product.
* Thus we have : the full product, and nodes of the subproduct marked
*/
// First compute all nodes of the product
LinkedList<ConceptLattice> clParts = new LinkedList<ConceptLattice>();
for (Context ctx : parts) {
clParts.add(ctx.conceptLattice(true));
}
Lattice prod = new Lattice();
// Computes nodes
ArrayList<Couple> nodes = new ArrayList<Couple>();
LinkedList<ConceptLattice> copy = (LinkedList<ConceptLattice>) clParts.clone();
ConceptLattice firstCL = (ConceptLattice) copy.poll();
for (Object node : firstCL.getNodes()) {
Couple couple = new Couple(new ArrayList<Concept>(), false);
ArrayList<Concept> prodCPT = new ArrayList<Concept>();
prodCPT.add((Concept) node);
couple.setLeft(prodCPT);
nodes.addAll(recursiveGenProd(couple, copy));
}
for (Couple c : nodes) {
prod.addNode(new Node(c));
}
// Add edges
for (Object source : prod.getNodes()) {
for (Object target : prod.getNodes()) {
Couple contentSource = (Couple) ((Node) source).getContent();
Couple contentTarget = (Couple) ((Node) target).getContent();
boolean haveEdge = true;
boolean equals = true;
for (int i = 0; i < clParts.size(); i++) { // clParts.size() is the number of factor
Concept cptSource = ((ArrayList<Concept>) contentSource.getLeft()).get(i);
Concept cptTarget = ((ArrayList<Concept>) contentTarget.getLeft()).get(i);
equals = equals && cptSource.equals(cptTarget);
haveEdge = haveEdge && (clParts.get(i).containsEdge(cptSource, cptTarget) || cptSource.equals(cptTarget));
}
if (haveEdge && !equals) {
prod.addEdge(((Node) source), ((Node) target));
}
}
}
prod.transitiveReduction();
// Last, identify the sub-product, e.g. nodes of this component in the product.
// In the subdirect decomposition, if (A,B) is a concept then (A \cap H,B \cap N) also.
// Transform nodes of the original lattice into nodes of the subproduct lattice and mark them
ConceptLattice cl = this.conceptLattice(true);
for (Object cpt : cl.getNodes()) {
// Compute cpt representation in prod
ArrayList<Concept> subCpt = new ArrayList<Concept>();
for (int i = 0; i < parts.size(); i++) {
Context ctx = parts.get(i);
ConceptLattice term = clParts.get(i);
ComparableSet setA = new ComparableSet();
setA.addAll(((Concept) cpt).getSetA());
ComparableSet setB = new ComparableSet();
setB.addAll(((Concept) cpt).getSetB());
setA.retainAll(ctx.getAttributes());
setB.retainAll(ctx.getObservations());
subCpt.add(term.getConcept((ComparableSet) setA, (ComparableSet) setB));
}
// Check if cpt is in prod
for (Object nodeProd : prod.getNodes()) {
if (((Couple) ((Node) nodeProd).getContent()).getLeft().equals(subCpt)) {
((Couple) ((Node) nodeProd).getContent()).setRight(true);
}
}
}
return prod;
}
/**
* Returns the closed set iceberg of this component.
*
* The Hasse diagramm of the iceberg is computed (i.e. it is transitively
* reduced)
* until the support of the closed set is less than the support value.
*
* @param support a threshold, between 0 and 1, for a closed set to be part
* of the iceberg.
*
* @return The concept iceberg
*/
public ConceptLattice closedSetIceberg(double support) {
return ConceptLattice.diagramIceberg(this, support);
}
/**
* Returns the lattice of this component.
*
* @return The lattice induced by this component
*/
@Override
public ConceptLattice lattice() {
return this.conceptLattice(true);
}
}