Java tutorial
/** * SusiThought * Copyright 29.06.2016 by Michael Peter Christen, @0rb1t3r * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package ai.susi.mind; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONObject; /** * A thought is a piece of data that can be remembered. The structure or the thought can be * modeled as a table which may be created using the retrieval of information from elsewhere * of the current argument. */ public class SusiThought extends JSONObject { private final String metadata_name, data_name; private int times; /** * create an empty thought, to be filled with single data entities. */ public SusiThought() { super(true); this.metadata_name = "metadata"; this.data_name = "data"; this.times = 0; } /** * create a clone of a json object as a SusiThought object * @param json the 'other' thought, probably an exported and re-imported thought */ public SusiThought(JSONObject json) { this(); if (json.has(this.metadata_name)) this.put(this.metadata_name, json.getJSONObject(this.metadata_name)); if (json.has(this.data_name)) this.setData(json.getJSONArray(this.data_name)); if (json.has("actions")) this.put("actions", json.getJSONArray("actions")); } /** * Create an initial thought using the matcher on an expression. * Such an expression is like the input from a text source which contains keywords * that are essential for the thought. The matcher extracts such information. * Matching informations are named using the order of the appearance of the information pieces. * The first information is named '1', the second '2' and so on. The whole information which contained * the matching information is named '0'. * @param matcher */ public SusiThought(Matcher matcher) { this(); this.setOffset(0).setHits(1); JSONObject row = new JSONObject(); row.put("0", matcher.group(0)); for (int i = 0; i < matcher.groupCount(); i++) { row.put(Integer.toString(i + 1), matcher.group(i + 1)); } this.setData(new JSONArray().put(row)); } @Deprecated public SusiThought(String metadata_name, String data_name) { super(true); this.metadata_name = metadata_name; this.data_name = data_name; } public boolean equals(Object o) { if (!(o instanceof SusiThought)) return false; SusiThought t = (SusiThought) o; return this.getData().equals(t.getData()); } public SusiThought setTimes(int t) { this.times = t; return this; } public SusiThought addTimes(int t) { this.times += t; return this; } public int getTimes() { return this.times; } /** * In a series of information pieces the first information piece has number 0. * If the thought is a follow-up series of a previous set of information, an offset is needed. * That can be set here. * @param offset the offset to a previous set of information pieces. * @return the thought */ public SusiThought setOffset(int offset) { getMetadata().put("offset", offset); return this; } public int getOffset() { return getMetadata().has("offset") ? getMetadata().getInt("offset") : 0; } /** * The number of information pieces in a set of informations may have a count. * @return hits number of information pieces */ public int getCount() { return getData().length(); } public boolean isFailed() { return getData().length() == 0; } public boolean hasEmptyObservation(String key) { List<String> observations = this.getObservations(key); return observations.size() == 0 || observations.get(0).length() == 0; } /** * While the number of information pieces in a whole has a count, the number of relevant * information pieces may have been extracted. The hits number gives the number of relevant * pieces. This can be set here. * @param hits number of information pieces * @return the thought */ public SusiThought setHits(int hits) { getMetadata().put("hits", hits); return this; } public int getHits() { return getMetadata().has("hits") ? getMetadata().getInt("hits") : 0; } /** * The process which created this thought may have a name or description string. * To document what happened, the process namen can be given here * @param query the process which formed this thought * @return the thought */ public SusiThought setProcess(String processName) { getMetadata().put("process", processName); return this; } /** * If this thought was the result of a retrieval using a specific expression, that expression is * called the query. The query can be attached to a thought * @param query the expression which caused that this thought was formed * @return the thought */ public SusiThought setQuery(String query) { getMetadata().put("query", query); return this; } public String getQuery() { return getMetadata().has("query") ? getMetadata().getString("query") : ""; } /** * If the expression to create this thought had an agent that expressed the result set of the * information contained in this thought, it is called the scraper. The scraper name can be attached here. * @param scraperInfo the scraper that created this thought * @return the thought */ public SusiThought setScraperInfo(String scraperInfo) { getMetadata().put("scraperInfo", scraperInfo); return this; } /** * All details of the creation of this thought is collected in a metadata statement. * @return the set of meta information for this thought */ private JSONObject getMetadata() { JSONObject md; if (this.has(metadata_name)) md = this.getJSONObject(metadata_name); else { md = new JSONObject(); this.put(metadata_name, md); } if (!md.has("count")) md.put("count", getData().length()); return md; } /** * Information contained in this thought has the form of a result set table, organized in rows and columns. * The columns must have all the same name in each row. * @param table the information for this thought. * @return the thought */ public SusiThought setData(JSONArray table) { this.put(data_name, table); JSONObject md = getMetadata(); md.put("count", getData().length()); return this; } /** * Information contained in this thought can get returned as a table, a set of information pieces. * @return a table of information pieces as a set of rows which all have the same column names. */ public JSONArray getData() { if (this.has(data_name)) return this.getJSONArray(data_name); JSONArray a = new JSONArray(); this.put(data_name, a); return a; } /** * Merging of data is required during an mind-meld. * To meld two thoughts, we combine their data arrays into one. * The resulting table has at maximum the length of both source tables combined. * @param table the information to be melted into our existing table. * @return the thought */ public SusiThought mergeData(JSONArray table1) { JSONArray table0 = this.getData(); int t0c = 0; for (int i = 0; i < table1.length(); i++) { JSONObject j1i = table1.getJSONObject(i); while (t0c < table0.length() && anyObjectKeySame(j1i, table0.getJSONObject(t0c))) { t0c++; } if (t0c >= table0.length()) table0.put(new JSONObject(true)); table0.getJSONObject(t0c).putAll(table1.getJSONObject(i)); } setData(table0); return this; } private final static boolean anyObjectKeySame(final JSONObject a, final JSONObject b) { for (String k : a.keySet()) if (b.has(k)) return true; return false; } /** * If during thinking we observe something that we want to memorize, we can memorize this here. * We insert the new data always in front of existing same data to make it visible as primary * backtracking option. This means that new observations are always more important than old * observations but do not overwrite them; they can be used again in case that the new observation * is not valid during inference computation. * @param featureName the object key * @param observation the object value * @return the thought */ public SusiThought addObservation(String featureName, String observation) { JSONArray data = getData(); // find first occurrence of key in rows int rowc = 0; boolean found = false; while (rowc < data.length()) { JSONObject row = data.getJSONObject(rowc); if (row.has(featureName)) found = true; if (found) break; rowc++; } if (found) { // insert feature in front of row if (rowc == 0) { // insert a row and shift everything up JSONArray newData = new JSONArray(); JSONObject row = new JSONObject(); row.put(featureName, observation); newData.put(row); for (Object o : data) newData.put(o); this.setData(newData); } else { JSONObject row = data.getJSONObject(rowc - 1); row.put(featureName, observation); } } else { // insert into first line if (data.length() == 0) { JSONObject row = new JSONObject(); row.put(featureName, observation); data.put(row); } else { JSONObject row = data.getJSONObject(0); row.put(featureName, observation); } } return this; } public List<String> getObservations(String featureName) { List<String> list = new ArrayList<>(); JSONArray table = this.getData(); if (table != null && table.length() > 0) { for (int rc = 0; rc < table.length(); rc++) { JSONObject row = table.getJSONObject(rc); for (String key : row.keySet()) { if (key.equals(featureName)) list.add(row.get(key).toString()); } } } return list; } public String getObservation(String featureName) { JSONArray table = this.getData(); if (table != null && table.length() > 0) { for (int rc = 0; rc < table.length(); rc++) { JSONObject row = table.getJSONObject(rc); for (String key : row.keySet()) { if (key.equals(featureName)) return row.get(key).toString(); } } } return null; } /** * Every information may have a set of (re-)actions assigned. * Those (re-)actions are methods to do something with the thought. * @param actions (re-)actions on this thought * @return the thought */ public SusiThought addActions(List<SusiAction> actions) { JSONArray a = getActionsJSON(); actions.forEach(action -> a.put(action.toJSONClone())); return this; } public SusiThought addAction(SusiAction action) { JSONArray a = getActionsJSON(); a.put(action.toJSONClone()); return this; } /** * To be able to apply (re-)actions to this thought, the actions on the information can be retrieved. * @return the (re-)actions which are applicable to this thought. */ public List<SusiAction> getActions() { List<SusiAction> actions = new ArrayList<>(); getActionsJSON().forEach(action -> actions.add(new SusiAction((JSONObject) action))); return actions; } private JSONArray getActionsJSON() { JSONArray actions; if (!this.has("actions")) { actions = new JSONArray(); this.put("actions", actions); } else { actions = this.getJSONArray("actions"); } return actions; } public static final Pattern variable_pattern = Pattern.compile("\\$.*?\\$"); /** * Unification applies a piece of memory within the current argument to a statement * which creates an instantiated statement * @param statement * @return the instantiated statement with elements of the argument applied as much as possible */ public String unify(String statement) { if (statement.indexOf('$') < 0) return statement; JSONArray table = this.getData(); if (table != null && table.length() > 0) { for (int rownum = 0; rownum < table.length(); rownum++) { JSONObject row = table.getJSONObject(rownum); for (String key : row.keySet()) { int i; while ((i = statement.indexOf("$" + key + "$")) >= 0) { statement = statement.substring(0, i) + row.get(key).toString() + statement.substring(i + key.length() + 2); } if (statement.indexOf('$') < 0) break; } if (statement.indexOf('$') < 0) break; } } return statement; } public JSONObject toJSON() { return this; } public String toString() { return super.toString(2); // thats here to get a better debugging output } public static void main(String[] args) { SusiThought t = new SusiThought().addObservation("a", "letter-a"); System.out.println(t.unify("the letter $a$")); } }