/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ket.math;
import java.util.*;
import java.awt.*;
import static ket.math.ArgumentVector.INCLUDE_ROOT;
import static ket.math.ArgumentVector.REVERSE_SIBLING_ORDER;
import static ket.math.ArgumentVector.EXCLUDE_HIDDEN_CHILDREN;
import ket.display.*;
import ket.display.box.Box;
import ket.display.box.BoxWord;
import ket.math.convert.Like;
import ket.math.purpose.NumberValue;
import ket.math.purpose.Text;
import ket.math.purpose.VariableToken;
import ket.math.purpose.Word;
import ketUI.Ket;
/**
* Provide common methods to equations and their contents (ie branches and tokens).
*/
public abstract class Argument extends Relative implements Displayable, Comparable<Argument> {
public static Argument PAD_ARG = new Token(new Word("padArg"));
/**
* Every argument has a parent unless it is the root of a tree.
*/
Parent parent;
/**
* Sub-branches can be hidden to reduce the complexity of large equations.
*/
boolean visible;
/**
* If true this will be drawn in bold.
*/
boolean bold;
/**
* Return a copy of the given argument. Different methods are used for
* different objects. This is an Argument specific specialization
* similar in implementation to the object.clone() method. The
* difference is that all sub classes are explicitly listed in this
* method. This is to keep all associate information in one place, but
* does require updating as each new child class is added.
*/
public static Argument cloneArgument(Argument argument) {
if (argument==null) {
return null;
} else if (argument instanceof VariableToken) {
return ((VariableToken) argument).cloneVariableToken();
} else if (argument instanceof Token) {
return ((Token) argument).cloneToken();
} else if (argument instanceof Branch) {
return ((Branch) argument).cloneBranch();
} else {
throw new RuntimeException(
"Argument.cloneArgument(): unfinished code: passed " +
argument.getClass());
}
}
public Argument cloneArgument() {
return Argument.cloneArgument(this);
}
public void TEST__showParentChildren() { //d
Branch p = getParentBranch();
if (p==null) {
Ket.out.println("<null>");
return;
}
Ket.out.print(" {pc}:"+p.size()+": ");
for (Argument child : p.getChildren()) {
Ket.out.print("<" + (child==this?".":(child.getParent()==p?"y":"n")) + ">");
}
Ket.out.println();
}
/**
* Construct an equation with a given parent.
*/
public Argument() {
this.parent = null;
this.visible = true;
}
//////////////
// ABSTRACT //
//////////////
public abstract int recursiveSize();
/**
* Includes tokens as well as hidden or childless branches.
*/
public abstract boolean isLeaf();
/**
* If an argument is branch[-](argument) then return argument,
* otherwise return null.
*/
public abstract Argument getNegativeFunctionArg();
/**
* Check if this argument matches the given string pattern.
*/
public abstract boolean matches(String pattern);
/**
* Ensure that each subclass returns a meaningful value.
*/
public abstract int hashCode();
public boolean equals(Object o) {
if ( ! (o instanceof Argument) ) {
return false;
}
return subBranchEquals( (Argument) o );
}
/**
* Check that this object equals the given argument, including any
* child components.
*/
public abstract boolean subBranchEquals(Argument argument);
/**
* Check that this object equals the given argument, regardless of any
* child components.
*/
public abstract boolean elementEquals(Argument argument);
/**
* Check if the state of a given argument is equal to value.
*/
public abstract boolean elementEquals(double value);
/**
* Move recursively through all corresponding sub-branches and return
* those child arguments that do not match. Correspondance requires
* both element equality and a common number of child args.
*/
public IdentityHashMap<Argument,Argument> cypher(Argument b) { // BEWARE: Map is uncloned and so edits to 'this' or 'b' mutate its elements.
IdentityHashMap<Argument,Argument> im = new IdentityHashMap<Argument,Argument>();
cypherRecur(this, b, im);
return im;
}
/**
* Given a pair of arguments, recursively check for tokens in a have
* corresponding sub-branches within b. For example<br>
* sin(x) = y
* and<br>
* sin(f(u)) = integral(ln(x), x)
* returns<br>
* {"x":"f(u)", "y":"integral(ln(x), x)"}.
*/
public IdentityHashMap<Token, Token> cypherEquals(Argument b) {
IdentityHashMap<Argument, Argument> c = this.cypher(b);
IdentityHashMap<Token, Token> t = new IdentityHashMap<Token, Token>();
for (Argument k : c.keySet() ) {
if (k instanceof Token && c.get(k) instanceof Token) {
t.put( (Token) k, (Token) c.get(k));
} else {
return null;
}
}
boolean duplicateKeys = unique(t.keySet());
boolean duplicateValues = unique(t.values());
if (duplicateKeys ^ duplicateValues) {
return null;
}
// TODO: what happens to duplicates, duplicateKeys && duplicateValues
return t;
}
private static boolean unique(Collection<Token> c) {
for (Token u : c) {
int count = 0;
for (Token v : c) {
if (u.subBranchEquals(v)) {
count += 1;
}
}
if (count!=1) {
return false;
}
}
return true;
}
/**
* Recursively sift through the sub-branches of descendent and root while they match one another and otherwise recording their correspondence in the map.
*/
private static void cypherRecur(Argument descendent, Argument root, IdentityHashMap<Argument, Argument> im) { // Where should this method be?
Address a = descendent.getAddress();
Argument r = root.relativeAddress(a);
if (descendent.elementEquals(r)) {
if (descendent instanceof Token) {
// Both tokens are equal.
// TODO: Don't bother recording 3=3, but x=x or c_0 = c_0 may be useful?
return;
}
// Both tokens are branches.
Branch dBranch = (Branch) descendent;
Branch rBranch = (Branch) r;
if (dBranch.size()==rBranch.size()) {
// Both descendents are [element] equal and have the same number of arguments.
for (Argument child : dBranch.getChildren()) {
cypherRecur(child, root, im);
}
return;
}
// Otherwise keep going.
}
// Otherwise record the correspondance.
im.put(descendent, r);
}
/**
* Child classes may contain symbols, and these may be gathered using
* this method.
*/
public abstract State getState();
/**
* Some arguments (branches) have functions associated with them, this
* method returns null otherwise.
*/
public abstract Function getFunction();
/**
* Value is a special case of a token's state, which this method
* returns (or null otherwise).
*/
public abstract String getValue();
public abstract int size();
////////////////
// COMPARABLE //
////////////////
@Override
public int compareTo(Argument that) {
if (that==null) {
return 1;
}
// Start with numbers
boolean thisState = this.getPurpose() instanceof NumberValue;
boolean thatState = that.getPurpose() instanceof NumberValue;
if (thisState && !thatState) {
return -1;
}
if (!thisState && thatState) {
return +1;
}
// End with square roots
boolean thisSqrt = this.getPurpose()==Function.SQRT;
boolean thatSqrt = that.getPurpose()==Function.SQRT;
if (thisSqrt && !thatSqrt) {
return 1;
}
if (!thisSqrt && thatSqrt) {
return -1;
}
return this.toString().compareTo(that.toString());
}
///////////
// OTHER //
///////////
public void toggleBold() {
bold = ! bold;
}
public void setBold(boolean bold) {
this.bold = bold;
}
public boolean isBold() {
return bold;
}
////////////////
// VISIBILITY //
////////////////
/**
* Determine whether this argument is to be displayed.
*/
public boolean isVisible() {
return visible;
}
/**
* Set the visibility of this argument.
*/
public void setVisible(boolean visible) {
this.visible = visible;
}
//////////////
// RELATIVE //
//////////////
protected Argument asArgument() {
return this;
}
/**
* Return the argument's parent, that is the branch or equation that
* contains it.
*/
@Override
public Parent getParent() {
return parent;
}
/**
* Associate a parent with this branch, but only change this argument
* and not it's parent.
*/
public void setParent(Parent parent) {
this.parent = parent;
}
/////////////////////
// TRANSFORMATIONS //
/////////////////////
public abstract String toEditString();
public abstract void removeIntermediate();
/**
* This argument is linked (bidirectionally to its parent, both links
* of which are severed.
*/
public void remove() {
if (parent==null) {
return;
} else if (this.isRoot()) {
getEquation().setRoot(new Token(Symbol.UNKNOWN));
} else {
getParentBranch().removeChild(this.getIndex());
}
}
public void replaceIntermediate(Argument replacement) {
// If this and the replacement are branches, then arguments are copied over.
this.replace(replacement);
}
/**
* Replace this argument with an alternative replacement argument.
*/
public void replace(Argument replacement) {
if (this==replacement) {
Ket.out.println(" !!! Argument::replace(): Can't replace self !!! ");
} else if (this.isRoot()) {
//- ((Equation) parent).setRoot(replacement);
getEquation().setRoot(replacement);
} else {
//- ((Branch) parent).replaceArgument(this, replacement);
getParentBranch().replaceArgument(this, replacement);
}
}
/**
* Move this argument from being linked to its current parent to the
* parent of that branch.
*/
public boolean deleteParent() {
Branch parentBranch = getParentBranch();
if (parentBranch==null) {
Ket.out.println(" !!! Can't delete the parent of a null branch !!! ");
return false;
}
Branch grandparent = parentBranch.getParentBranch();
if (grandparent!=null) {
int index = grandparent.getIndex(parentBranch);
grandparent.removeChild(index);
this.remove();
if (grandparent.size()==0) {
grandparent.append(this);
} else {
if (index==0) {
grandparent.prepend(this);
} else {
grandparent.addAfter(index-1, this);
}
}
} else {
getEquation().setRoot(this);
}
return true;
}
public Branch addIntermediaryParent(Function function) {
Branch intermediaryBranch = new Branch(function);
if (this.isRoot()) {
Equation currentEquation = this.getEquation();
intermediaryBranch.append(this);
currentEquation.setRoot(intermediaryBranch);
} else {
Branch oldParent = (Branch) parent;
oldParent.replaceArgument(this, intermediaryBranch);
intermediaryBranch.append(this);
}
return intermediaryBranch;
}
/**
* Inserts the given branch inbetween the current argument and its
* current parent. That is, it transforms
* oldParent[this]
* into
* oldParent[newParent[this]].
*/
public void addIntermediaryParent(Branch intermediaryBranch, boolean prepend) {
if (this.isRoot()) {
Equation currentEquation = this.getEquation();
if (prepend) {
intermediaryBranch.prepend(this);
} else {
intermediaryBranch.append(this);
}
currentEquation.setRoot(intermediaryBranch);
} else {
Branch oldParent = (Branch) parent;
oldParent.replaceArgument(this, intermediaryBranch);
if (prepend) {
intermediaryBranch.prepend(this);
} else {
intermediaryBranch.append(this);
}
}
}
/////////////
// DISPLAY //
/////////////
/**
* Record this as lisp-like nested brackets.
*/
public abstract String toPrefixNotation();
/**
* The default appearance of an argument is as having been hidden.
*/
@Override
public Box toBox(long settings, ColourScheme colourScheme) {
if (isBold()) { //?
settings |= Box.BOLD_FONT;
}
//- Box box = new BoxWord(this, Symbol.HIDDEN.toUnicode(), settings, colourScheme.getBoxColour(this));
Box box = new BoxWord(this, Symbol.HIDDEN.toUnicode(), settings);
box.setArgument(this); //? Redundant?
return box;
}
/**
* Return true if any element of vector is ientically equal to this argument.
*/
public boolean isIn(Vector vector) { // TODO: Move to relative?
for (Object element : vector) {
if (this==element) {
return true;
}
}
return false;
}
/**
* Find the index of the vector identically equal to the this argument.
*/
public int indexIn(Vector vector) { // TODO: Move to relative?
for (int i=0; i<vector.size(); i++) {
if (this==vector.get(i)) {
return i;
}
}
return -1;
}
}