summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/gen
diff options
context:
space:
mode:
Diffstat (limited to 'base/src/main/java/bjc/utils/gen')
-rw-r--r--base/src/main/java/bjc/utils/gen/RandomGrammar.java69
-rw-r--r--base/src/main/java/bjc/utils/gen/WeightedGrammar.java573
-rw-r--r--base/src/main/java/bjc/utils/gen/WeightedRandom.java112
3 files changed, 754 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/gen/RandomGrammar.java b/base/src/main/java/bjc/utils/gen/RandomGrammar.java
new file mode 100644
index 0000000..3de08d6
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gen/RandomGrammar.java
@@ -0,0 +1,69 @@
+package bjc.utils.gen;
+
+import bjc.utils.funcdata.FunctionalMap;
+import bjc.utils.funcdata.IList;
+
+/**
+ * A weighted grammar where all the rules have a equal chance of occuring.
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of grammar elements to use.
+ */
+public class RandomGrammar<E> extends WeightedGrammar<E> {
+ /**
+ * Create a new random grammar.
+ */
+ public RandomGrammar() {
+ rules = new FunctionalMap<>();
+ }
+
+ /**
+ * Add cases to a specified rule.
+ *
+ * @param rule
+ * The name of the rule to add cases to.
+ * @param cases
+ * The cases to add for this rule.
+ */
+ @SafeVarargs
+ public final void addCases(final E rule, final IList<E>... cases) {
+ for (final IList<E> currentCase : cases) {
+ super.addCase(rule, 1, currentCase);
+ }
+ }
+
+ /**
+ * Create a rule with the specified name and cases.
+ *
+ * @param rule
+ * The name of the rule to add.
+ * @param cases
+ * The cases to add for this rule.
+ */
+ @SafeVarargs
+ public final void makeRule(final E rule, final IList<E>... cases) {
+ super.addRule(rule);
+
+ for (final IList<E> currentCase : cases) {
+ super.addCase(rule, 1, currentCase);
+ }
+ }
+
+ /**
+ * Create a rule with the specified name and cases.
+ *
+ * @param rule
+ * The name of the rule to add.
+ * @param cases
+ * The cases to add for this rule.
+ */
+ public void makeRule(final E rule, final IList<IList<E>> cases) {
+ if (cases == null) throw new NullPointerException("Cases must not be null");
+
+ super.addRule(rule);
+
+ cases.forEach(currentCase -> super.addCase(rule, 1, currentCase));
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gen/WeightedGrammar.java b/base/src/main/java/bjc/utils/gen/WeightedGrammar.java
new file mode 100644
index 0000000..7777ad8
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gen/WeightedGrammar.java
@@ -0,0 +1,573 @@
+package bjc.utils.gen;
+
+import java.util.Random;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import bjc.utils.data.IPair;
+import bjc.utils.data.Pair;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.FunctionalMap;
+import bjc.utils.funcdata.IList;
+import bjc.utils.funcdata.IMap;
+
+/**
+ * A random grammar, where certain rules will come up more often than others.
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The values that make up sentences of this grammar.
+ */
+public class WeightedGrammar<E> {
+ /**
+ * The initial rule of the grammar
+ */
+ protected String initialRule;
+
+ /**
+ * The rules currently in this grammar
+ */
+ protected IMap<E, WeightedRandom<IList<E>>> rules;
+
+ /**
+ * The random number generator used for random numbers
+ */
+ private Random rng;
+
+ /**
+ * All of the subgrammars of this grammar
+ */
+ protected IMap<E, WeightedGrammar<E>> subgrammars;
+
+ /**
+ * Rules that require special handling
+ */
+ private IMap<E, Supplier<IList<E>>> specialRules;
+
+ /**
+ * Predicate for marking special tokens
+ */
+
+ private Predicate<E> specialMarker;
+
+ /**
+ * Action for special tokens
+ */
+ private BiFunction<E, WeightedGrammar<E>, IList<E>> specialAction;
+
+ /**
+ * Create a new weighted grammar.
+ */
+ public WeightedGrammar() {
+ rules = new FunctionalMap<>();
+ subgrammars = new FunctionalMap<>();
+ specialRules = new FunctionalMap<>();
+ }
+
+ /**
+ * Create a new weighted grammar that uses the specified source of
+ * randomness.
+ *
+ * @param source
+ * The source of randomness to use
+ */
+ public WeightedGrammar(final Random source) {
+ this();
+
+ if (source == null) throw new NullPointerException("Source of randomness must be non-null");
+
+ rng = source;
+ }
+
+ /**
+ * Configure the action to perform on special tokens.
+ *
+ * @param marker
+ * The marker to find special tokens.
+ *
+ * @param action
+ * The action to take on those tokens.
+ */
+ public void configureSpecial(final Predicate<E> marker,
+ final BiFunction<E, WeightedGrammar<E>, IList<E>> action) {
+ specialMarker = marker;
+ specialAction = action;
+ }
+
+ /**
+ * Adds a special rule to the grammar.
+ *
+ * @param ruleName
+ * The name of the special rule.
+ *
+ * @param cse
+ * The case for the rule.
+ */
+ public void addSpecialRule(final E ruleName, final Supplier<IList<E>> cse) {
+ if (ruleName == null)
+ throw new NullPointerException("Rule name must not be null");
+ else if (cse == null) throw new NullPointerException("Case must not be null");
+
+ specialRules.put(ruleName, cse);
+ }
+
+ /**
+ * Add a case to an already existing rule.
+ *
+ * @param ruleName
+ * The rule to add a case to.
+ * @param probability
+ * The probability for this rule to be chosen.
+ * @param cse
+ * The case being added.
+ */
+ public void addCase(final E ruleName, final int probability, final IList<E> cse) {
+ if (ruleName == null)
+ throw new NullPointerException("Rule name must be not null");
+ else if (cse == null) throw new NullPointerException("Case body must not be null");
+
+ rules.get(ruleName).addProbability(probability, cse);
+ }
+
+ /**
+ * Add a alias for an existing subgrammar
+ *
+ * @param name
+ * The name of the subgrammar to alias
+ * @param alias
+ * The alias of the subgrammar
+ * @return Whether the alias was succesfully created
+ */
+ public boolean addGrammarAlias(final E name, final E alias) {
+ if (name == null)
+ throw new NullPointerException("Subgrammar name must not be null");
+ else if (alias == null) throw new NullPointerException("Subgrammar alias must not be null");
+
+ if (subgrammars.containsKey(alias)) return false;
+
+ if (subgrammars.containsKey(name)) {
+ subgrammars.put(alias, subgrammars.get(name));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add a new rule with no cases.
+ *
+ * @param name
+ * The name of the rule to add.
+ * @return Whether or not the rule was successfully added.
+ */
+ public boolean addRule(final E name) {
+ if (rng == null) {
+ rng = new Random();
+ }
+
+ if (name == null) throw new NullPointerException("Rule name must not be null");
+
+ return addRule(name, new WeightedRandom<>(rng));
+ }
+
+ /**
+ * Add a new rule with a set of cases.
+ *
+ * @param name
+ * The name of the rule to add.
+ * @param cases
+ * The set of cases for the rule.
+ * @return Whether or not the rule was succesfully added.
+ */
+ public boolean addRule(final E name, final WeightedRandom<IList<E>> cases) {
+ if (name == null)
+ throw new NullPointerException("Name must not be null");
+ else if (cases == null) throw new NullPointerException("Cases must not be null");
+
+ if (rules.containsKey(name)) return false;
+
+ rules.put(name, cases);
+ return true;
+ }
+
+ /**
+ * Add a subgrammar.
+ *
+ * @param name
+ * The name of the subgrammar.
+ * @param subgrammar
+ * The subgrammar to add.
+ * @return Whether or not the subgrammar was succesfully added.
+ */
+ public boolean addSubgrammar(final E name, final WeightedGrammar<E> subgrammar) {
+ if (name == null)
+ throw new NullPointerException("Subgrammar name must not be null");
+ else if (subgrammar == null) throw new NullPointerException("Subgrammar must not be null");
+
+ if (subgrammars.containsKey(name)) return false;
+
+ subgrammars.put(name, subgrammar);
+ return true;
+ }
+
+ /**
+ * Remove a rule with the specified name.
+ *
+ * @param name
+ * The name of the rule to remove.
+ */
+ public void deleteRule(final E name) {
+ if (name == null) throw new NullPointerException("Rule name must not be null");
+
+ rules.remove(name);
+ }
+
+ /**
+ * Remove a subgrammar with the specified name.
+ *
+ * @param name
+ * The name of the subgrammar to remove.
+ */
+ public void deleteSubgrammar(final E name) {
+ if (name == null) throw new NullPointerException("Rule name must not be null");
+
+ subgrammars.remove(name);
+ }
+
+ /**
+ * Generate a set of debug sentences for the specified rule.
+ *
+ * Only generates sentences one layer deep.
+ *
+ * @param ruleName
+ * The rule to test.
+ * @return A set of sentences generated by the specified rule.
+ */
+ public IList<IList<E>> generateDebugValues(final E ruleName) {
+ if (ruleName == null) throw new NullPointerException("Rule name must not be null");
+
+ final IList<IList<E>> returnedList = new FunctionalList<>();
+
+ final WeightedRandom<IList<E>> ruleGenerator = rules.get(ruleName);
+
+ for (int i = 0; i < 10; i++) {
+ returnedList.add(ruleGenerator.generateValue());
+ }
+
+ return returnedList;
+ }
+
+ /**
+ * Generate a generic sentence from a initial rule.
+ *
+ * @param <T>
+ * The type of the transformed output
+ *
+ * @param initRules
+ * The initial rule to start with.
+ *
+ * @param tokenTransformer
+ * The function to transform grammar output into
+ * something.
+ *
+ * @param spacer
+ * The spacer element to add in between output tokens.
+ *
+ * @return A randomly generated sentence from the specified initial
+ * rule.
+ */
+ public <T> IList<T> generateGenericValues(final E initRules, final Function<E, T> tokenTransformer,
+ final T spacer) {
+ if (initRules == null)
+ throw new NullPointerException("Initial rule must not be null");
+ else if (tokenTransformer == null)
+ throw new NullPointerException("Transformer must not be null");
+ else if (spacer == null) throw new NullPointerException("Spacer must not be null");
+
+ final IList<T> returnedList = new FunctionalList<>();
+
+ IList<E> genRules = new FunctionalList<>(initRules);
+
+ if (specialMarker != null) {
+ if (specialMarker.test(initRules)) {
+ genRules = specialAction.apply(initRules, this);
+ }
+ }
+
+ for (final E initRule : genRules.toIterable()) {
+ if (specialRules.containsKey(initRule)) {
+ for (final E rulePart : specialRules.get(initRule).get().toIterable()) {
+ final Iterable<T> generatedRuleParts = generateGenericValues(rulePart,
+ tokenTransformer, spacer).toIterable();
+
+ for (final T generatedRulePart : generatedRuleParts) {
+ returnedList.add(generatedRulePart);
+ returnedList.add(spacer);
+ }
+ }
+ } else if (subgrammars.containsKey(initRule)) {
+ final Iterable<T> ruleParts = subgrammars.get(initRule)
+ .generateGenericValues(initRule, tokenTransformer, spacer).toIterable();
+
+ for (final T rulePart : ruleParts) {
+ returnedList.add(rulePart);
+ returnedList.add(spacer);
+ }
+ } else if (rules.containsKey(initRule)) {
+ final Iterable<E> ruleParts = rules.get(initRule).generateValue().toIterable();
+
+ for (final E rulePart : ruleParts) {
+ final Iterable<T> generatedRuleParts = generateGenericValues(rulePart,
+ tokenTransformer, spacer).toIterable();
+
+ for (final T generatedRulePart : generatedRuleParts) {
+ returnedList.add(generatedRulePart);
+ returnedList.add(spacer);
+ }
+ }
+ } else {
+ final T transformedToken = tokenTransformer.apply(initRule);
+
+ if (transformedToken == null)
+ throw new NullPointerException("Transformer created null token");
+
+ returnedList.add(transformedToken);
+ returnedList.add(spacer);
+ }
+ }
+
+ return returnedList;
+ }
+
+ /**
+ * Generate a random list of grammar elements from a given initial rule.
+ *
+ * @param initRule
+ * The initial rule to start with.
+ * @param spacer
+ * The item to use to space the list.
+ * @return A list of random grammar elements generated by the specified
+ * rule.
+ */
+ public IList<E> generateListValues(final E initRule, final E spacer) {
+ final IList<E> retList = generateGenericValues(initRule, strang -> strang, spacer);
+
+ return retList;
+ }
+
+ /**
+ * Get the initial rule of this grammar
+ *
+ * @return The initial rule of this grammar
+ */
+ public String getInitialRule() {
+ return initialRule;
+ }
+
+ /**
+ * Returns the number of rules in this grammar
+ *
+ * @return The number of rules in this grammar
+ */
+ public int getRuleCount() {
+ return rules.size();
+ }
+
+ /**
+ * Returns a set containing all of the rules in this grammar
+ *
+ * @return The set of all rule names in this grammar
+ */
+ public IList<E> getRuleNames() {
+ final IList<E> ruleNames = new FunctionalList<>();
+
+ ruleNames.addAll(rules.keyList());
+ ruleNames.addAll(specialRules.keyList());
+
+ return ruleNames;
+ }
+
+ /**
+ * Get the subgrammar with the specified name.
+ *
+ * @param name
+ * The name of the subgrammar to get.
+ * @return The subgrammar with the specified name.
+ */
+ public WeightedGrammar<E> getSubgrammar(final E name) {
+ if (name == null) throw new NullPointerException("Subgrammar name must not be null");
+
+ return subgrammars.get(name);
+ }
+
+ /**
+ * Check if this grammar has an initial rule
+ *
+ * @return Whether or not this grammar has an initial rule
+ */
+ public boolean hasInitialRule() {
+ return initialRule != null && !initialRule.equalsIgnoreCase("");
+ }
+
+ /**
+ * Check if this grammar has a given rule.
+ *
+ * @param ruleName
+ * The rule to check for.
+ *
+ * @return Whether or not the grammar has a rule by that name.
+ */
+ public boolean hasRule(final E ruleName) {
+ return rules.containsKey(ruleName) || specialRules.containsKey(ruleName);
+ }
+
+ /**
+ * Prefix a given rule with a token multiple times
+ *
+ * @param ruleName
+ * The name of the rule to prefix
+ * @param prefixToken
+ * The token to prefix to the rules
+ * @param additionalProbability
+ * The additional probability of the tokens
+ * @param numberOfTimes
+ * The number of times to prefix the token
+ */
+ public void multiPrefixRule(final E ruleName, final E prefixToken, final int additionalProbability,
+ final int numberOfTimes) {
+ if (ruleName == null)
+ throw new NullPointerException("Rule name must not be null");
+ else if (prefixToken == null)
+ throw new NullPointerException("Prefix token must not be null");
+ else if (numberOfTimes < 1)
+ throw new IllegalArgumentException("Number of times to prefix must be positive.");
+
+ final WeightedRandom<IList<E>> rule = rules.get(ruleName);
+
+ final IList<IPair<Integer, IList<E>>> newResults = new FunctionalList<>();
+
+ rule.getValues().forEach((pair) -> {
+ final IList<IList<E>> newRule = new FunctionalList<>();
+
+ for (int i = 1; i <= numberOfTimes; i++) {
+ final IList<E> newCase = pair.merge((left, right) -> {
+ final IList<E> returnVal = new FunctionalList<>();
+
+ for (final E val : right.toIterable()) {
+ returnVal.add(val);
+ }
+
+ return returnVal;
+ });
+
+ for (int j = 1; j <= i; j++) {
+ newCase.prepend(prefixToken);
+ }
+
+ newRule.add(newCase);
+ }
+
+ newRule.forEach((list) -> {
+ final Integer currentProb = pair.merge((left, right) -> left);
+
+ newResults.add(new Pair<>(currentProb + additionalProbability, list));
+ });
+ });
+
+ newResults.forEach((pair) -> {
+ pair.doWith((left, right) -> {
+ addCase(ruleName, left, right);
+ });
+ });
+ }
+
+ /**
+ * Create a series of alternatives for a rule by prefixing them with a
+ * given token
+ *
+ * @param additionalProbability
+ * The amount to adjust the probability by
+ * @param ruleName
+ * The name of the rule to prefix
+ * @param prefixToken
+ * The token to prefix to the rule
+ */
+ public void prefixRule(final E ruleName, final E prefixToken, final int additionalProbability) {
+ if (ruleName == null)
+ throw new NullPointerException("Rule name must not be null");
+ else if (prefixToken == null) throw new NullPointerException("Prefix token must not be null");
+
+ final WeightedRandom<IList<E>> rule = rules.get(ruleName);
+
+ final IList<IPair<Integer, IList<E>>> newResults = new FunctionalList<>();
+
+ rule.getValues().forEach((pair) -> {
+ final IList<E> newCase = pair.merge((left, right) -> {
+ final IList<E> returnVal = new FunctionalList<>();
+
+ for (final E val : right.toIterable()) {
+ returnVal.add(val);
+ }
+
+ return returnVal;
+ });
+
+ newCase.prepend(prefixToken);
+
+ newResults.add(new Pair<>(pair.merge((left, right) -> left) + additionalProbability, newCase));
+ });
+
+ newResults.forEach((pair) -> pair.doWith((left, right) -> addCase(ruleName, left, right)));
+ }
+
+ /**
+ * Set the initial rule of the graphic
+ *
+ * @param initRule
+ * The initial rule of this grammar
+ */
+ public void setInitialRule(final String initRule) {
+ this.initialRule = initRule;
+ }
+
+ /**
+ * Suffix a token to a rule
+ *
+ * @param ruleName
+ * The rule to suffix
+ * @param suffixToken
+ * The token to prefix to the rule
+ * @param additionalProbability
+ * Additional probability of the prefixed rule
+ */
+ public void suffixRule(final E ruleName, final E suffixToken, final int additionalProbability) {
+ if (ruleName == null)
+ throw new NullPointerException("Rule name must not be null");
+ else if (suffixToken == null) throw new NullPointerException("Prefix token must not be null");
+
+ final WeightedRandom<IList<E>> rule = rules.get(ruleName);
+
+ final IList<IPair<Integer, IList<E>>> newResults = new FunctionalList<>();
+
+ rule.getValues().forEach((par) -> {
+ final IList<E> newCase = par.merge((left, right) -> {
+ final IList<E> returnVal = new FunctionalList<>();
+
+ for (final E val : right.toIterable()) {
+ returnVal.add(val);
+ }
+
+ return returnVal;
+ });
+
+ newCase.add(suffixToken);
+
+ newResults.add(new Pair<>(par.merge((left, right) -> left) + additionalProbability, newCase));
+ });
+
+ newResults.forEach((pair) -> pair.doWith((left, right) -> addCase(ruleName, left, right)));
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gen/WeightedRandom.java b/base/src/main/java/bjc/utils/gen/WeightedRandom.java
new file mode 100644
index 0000000..18225ef
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gen/WeightedRandom.java
@@ -0,0 +1,112 @@
+package bjc.utils.gen;
+
+import java.util.Random;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.data.IPair;
+import bjc.utils.data.Identity;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Represents a random number generator where certain results are weighted more
+ * heavily than others.
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of values that are randomly selected.
+ */
+public class WeightedRandom<E> {
+ /*
+ * The list of probabilities for each result
+ */
+ private final IList<Integer> probabilities;
+
+ /*
+ * The list of possible results to pick from
+ */
+ private final IList<E> results;
+
+ /*
+ * The source for any needed random numbers
+ */
+ private final Random source;
+
+ private int totalChance;
+
+ /**
+ * Create a new weighted random generator with the specified source of
+ * randomness
+ *
+ * @param src
+ * The source of randomness to use.
+ */
+ public WeightedRandom(final Random src) {
+ probabilities = new FunctionalList<>();
+ results = new FunctionalList<>();
+
+ if (src == null) throw new NullPointerException("Source of randomness must not be null");
+
+ source = src;
+ }
+
+ /**
+ * Add a probability for a specific result to be given.
+ *
+ * @param chance
+ * The chance to get this result.
+ * @param result
+ * The result to get when the chance comes up.
+ */
+ public void addProbability(final int chance, final E result) {
+ probabilities.add(chance);
+ results.add(result);
+
+ totalChance += chance;
+ }
+
+ /**
+ * Generate a weighted random value.
+ *
+ * @return A random value selected in a weighted fashion.
+ */
+ public E generateValue() {
+ final IHolder<Integer> value = new Identity<>(source.nextInt(totalChance));
+ final IHolder<E> current = new Identity<>();
+ final IHolder<Boolean> picked = new Identity<>(true);
+
+ probabilities.forEachIndexed((index, probability) -> {
+ if (picked.unwrap(bool -> bool)) {
+ if (value.unwrap((number) -> number < probability)) {
+ current.transform((result) -> results.getByIndex(index));
+
+ picked.transform((bool) -> false);
+ } else {
+ value.transform((number) -> number - probability);
+ }
+ }
+ });
+
+ return current.unwrap((result) -> result);
+ }
+
+ /**
+ * Return a list of values that can be generated by this generator
+ *
+ * @return A list of all the values that can be generated
+ */
+ public IList<E> getResults() {
+ return results;
+ }
+
+ /**
+ * Return a list containing values that can be generated paired with the
+ * probability of those values being generated
+ *
+ * @return A list of pairs of values and value probabilities
+ */
+ public IList<IPair<Integer, E>> getValues() {
+ return probabilities.pairWith(results);
+ }
+}