package edu.stanford.nlp.sempre; import java.util.ArrayList; import java.util.List; import fig.basic.*; /** * Maps a string to a number (double). * * @author Percy Liang */ public class NumberFn extends SemanticFn { public static class Options { @Option(gloss = "Omit units") public boolean unitless = false; @Option(gloss = "Also test numbers by try converting to float (instead of using NER tags)") public boolean alsoTestByConversion = false; @Option(gloss = "Also test numbers by applying NER on just the phrase") public boolean alsoTestByIsolatedNER = false; @Option(gloss = "range of allowed numbers. e.g. null: no limits, Lists.newArrayList(0,100): 0-100 inclusive") public List<Double> allowedRange = null; } public static Options opts = new Options(); private List<String> requests; // List of types of fields to get (e.g., NUMBER) private boolean request(String req) { return requests == null || requests.contains(req); } public void init(LispTree tree) { super.init(tree); if (tree.children.size() > 1) { requests = new ArrayList<String>(); for (int i = 1; i < tree.children.size(); i++) requests.add(tree.child(1).value); } } // TODO(pliang): handle measurements too (e.g., 3cm) public DerivationStream call(final Example ex, final Callable c) { return new SingleDerivationStream() { public Derivation createDerivation() { // Test using NER span Derivation deriv = check(ex.languageInfo, c.getStart(), c.getEnd()); if (deriv != null) return deriv; // Test by converting string to number directly (don't look at NER) if (opts.alsoTestByConversion && request("NUMBER") & c.getEnd() - c.getStart() == 1) { String value = ex.languageInfo.tokens.get(c.getStart()); if (value != null) { try { NumberValue numberValue = new NumberValue(Double.parseDouble(value)); SemType type = numberValue.value == (int) numberValue.value ? SemType.intType : SemType.floatType; return new Derivation.Builder() .withCallable(c) .formula(new ValueFormula<>(numberValue)) .type(type) .createDerivation(); } catch (NumberFormatException e) { // Don't issue warnings; most spans are not numbers } } } // Test by applying NER on just the phrase if (opts.alsoTestByIsolatedNER) { String phrase = ex.phraseString(c.getStart(), c.getEnd()); LanguageInfo languageInfo = LanguageAnalyzer.getSingleton().analyze(phrase); deriv = check(languageInfo, 0, languageInfo.numTokens()); if (deriv != null) return deriv; } return null; } public Derivation check(LanguageInfo languageInfo, int start, int end) { // Numbers: If it is an integer, set its type to integer. Otherwise, use float. if (request("NUMBER")) { String value = languageInfo.getNormalizedNerSpan("NUMBER", start, end); if (value != null) { try { NumberValue numberValue = new NumberValue(Double.parseDouble(value)); if (opts.allowedRange != null) { if (numberValue.value < opts.allowedRange.get(0) || numberValue.value > opts.allowedRange.get(1)) { LogInfo.warnings("NumberFn: %f is outside of the allowed range %s", numberValue.value, opts.allowedRange); return null; } } SemType type = numberValue.value == (int) numberValue.value ? SemType.intType : SemType.floatType; return new Derivation.Builder() .withCallable(c) .formula(new ValueFormula<>(numberValue)) .type(type) .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } } } // Ordinals if (request("ORDINAL")) { String value = languageInfo.getNormalizedNerSpan("ORDINAL", start, end); if (value != null) { try { NumberValue numberValue = (opts.unitless ? new NumberValue(Double.parseDouble(value)) : new NumberValue(Double.parseDouble(value), "fb:en.ordinal_number")); SemType type = SemType.intType; return new Derivation.Builder() .withCallable(c) .formula(new ValueFormula<>(numberValue)) .type(type) .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } } } // Percents if (request("PERCENT")) { String value = languageInfo.getNormalizedNerSpan("PERCENT", start, end); if (value != null) { try { NumberValue numberValue = (opts.unitless ? new NumberValue(Double.parseDouble(value.substring(1))) : new NumberValue(0.01 * Double.parseDouble(value.substring(1)))); SemType type = SemType.floatType; return new Derivation.Builder() .withCallable(c) .formula(new ValueFormula<>(numberValue)) .type(type) .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } } } // Money if (request("MONEY")) { String value = languageInfo.getNormalizedNerSpan("MONEY", start, end); if (value != null) { try { NumberValue numberValue = (opts.unitless ? new NumberValue(Double.parseDouble(value.substring(1))) : new NumberValue(Double.parseDouble(value.substring(1)), "fb:en.dollar")); SemType type = SemType.floatType; return new Derivation.Builder() .withCallable(c) .formula(new ValueFormula<>(numberValue)) .type(type) .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } } } return null; } }; } }