package edu.stanford.nlp.sempre.interactive; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import edu.stanford.nlp.sempre.Derivation; import edu.stanford.nlp.sempre.Rule; import edu.stanford.nlp.sempre.interactive.GrammarInducer.ParseStatus; import fig.basic.LogInfo; import fig.basic.Option; /** * Takes the definition and the head, then induce rules through alignment * * @author sidaw */ public class DefinitionAligner { public static class Options { @Option(gloss = "categories that can serve as rules") public Set<String> alignedCats = new HashSet<String>(); @Option(gloss = "phrase size") public int phraseSize = 2; @Option(gloss = "max length difference") public int maxLengthDifference = 3; @Option(gloss = "max set exclusion length") public int maxSetExclusionLength = 2; @Option(gloss = "max exact exclusion length") public int maxExactExclusionLength = 5; @Option(gloss = "window size") public int windowSize = 1; @Option(gloss = "strategies") public Set<Strategies> strategies = Sets.newHashSet(Strategies.SetExclusion, Strategies.ExactExclusion); @Option(gloss = "maximum matches") public int maxMatches = 3; @Option(gloss = "verbose") public int verbose = 0; } public enum Strategies { SetExclusion, ExactExclusion, cmdSet }; public static Options opts = new Options(); public class Match { @Override public String toString() { return "Match [deriv=" + deriv + ", start=" + start + ", end=" + end + "]"; } public Match(Derivation def, int start, int end) { deriv = def; this.start = start; this.end = end; deriv.grammarInfo.start = start; deriv.grammarInfo.end = end; } Derivation deriv; int start; int end; } List<String> headTokens; List<String> defTokens; public static List<Rule> getRules(List<String> head, List<String> def, Derivation deriv, List<Derivation> chartList) { if (opts.verbose > 0) LogInfo.logs("DefinitionAligner.chartList: %s", chartList); DefinitionAligner aligner = new DefinitionAligner(head, def, deriv, chartList); List<Rule> allAlignedRules = Lists.newArrayList(); if (opts.verbose > 0) LogInfo.logs("DefinitionAligner.allMatches.size(): %d", aligner.allMatches.size()); for (int i = 0; i < aligner.allMatches.size() && i <= opts.maxMatches; i++) { Match match = aligner.allMatches.get(i); List<Derivation> filteredList = chartList.stream() .filter(d -> d.start >= match.deriv.start && d.end <= match.deriv.end).collect(Collectors.toList()); // filter out core List<Derivation> currentParses = chartList.stream().filter(d -> { if (opts.verbose > 1) LogInfo.logs("DefinitionAligner.chartList.d: %s", d); return (d.start == match.start && d.end == match.end); }).collect(Collectors.toList()); if (opts.verbose > 1) LogInfo.logs("DefinitionAligner.Match: %s", match); if (opts.verbose > 1) LogInfo.logs("DefinitionAligner.currentParses: %s", currentParses); if (GrammarInducer.getParseStatus(currentParses) != ParseStatus.Core) { if (opts.verbose > 1) LogInfo.logs("DefinitionAligner.NotCore: %s", currentParses); GrammarInducer grammarInducer = new GrammarInducer(head, match.deriv, filteredList); allAlignedRules.addAll(grammarInducer.getRules()); } } return allAlignedRules; } public List<Match> allMatches = new ArrayList<>(); private Map<String, List<Derivation>> chartMap; public DefinitionAligner(List<String> headTokens, List<String> defTokens, Derivation def, List<Derivation> chartList) { this.headTokens = headTokens; this.defTokens = defTokens; this.chartMap = GrammarInducer.makeChartMap(chartList); if (opts.verbose > 0) LogInfo.logs("DefinitionAligner: head '%s' as body: '%s'", headTokens, defTokens); if (Math.abs(headTokens.size() - defTokens.size()) >= 4) return; recursiveMatch(def); } void recursiveMatch(Derivation def) { // LogInfo.logs("Considering (%d,%d): %s", def.start, def.end, def); for (int start = 0; start < headTokens.size(); start++) { for (int end = headTokens.size(); end > start; end--) { // LogInfo.logs("Testing (%d,%d)", start, end); if (end == headTokens.size() && start == 0) continue; if (isMatch(def, start, end)) { if (opts.verbose > 0) LogInfo.logs("Matched head(%d,%d)=%s with deriv(%d,%d)=%s: %s", start, end, headTokens.subList(start, end), def.start, def.end, defTokens.subList(def.start, def.end), def); allMatches.add(new Match(def, start, end)); return; } } } for (Derivation d : def.children) { recursiveMatch(d); } } boolean isMatch(Derivation def, int start, int end) { if (def.start == -1 || def.end == -1) return false; if (chartMap.containsKey(GrammarInducer.catFormulaKey(def))) return false; if (opts.verbose > 0) LogInfo.logs("checkingLengths (%d, %d) - (%d, %d)", start, end, def.start, def.end); if (Math.abs((end - start) - (def.end - def.start)) >= opts.maxLengthDifference) return false; if (opts.strategies.contains(Strategies.ExactExclusion) && exactExclusion(def, start, end)) return true; if (opts.strategies.contains(Strategies.SetExclusion) && setExclusion(def, start, end)) return true; if (opts.strategies.contains(Strategies.cmdSet) && cmdSet(def, start, end)) return true; return false; } private boolean setExclusion(Derivation def, int start, int end) { // the span under consideration does not match anythign if (end - start > opts.maxSetExclusionLength) return false; if (!headTokens.subList(start, end).stream().noneMatch(t -> defTokens.contains(t))) return false; if (!defTokens.subList(def.start, def.end).stream().noneMatch(t -> headTokens.contains(t))) return false; // everything before and afterwards are accounted for if (!headTokens.subList(0, start).stream().allMatch(t -> defTokens.contains(t))) return false; if (!headTokens.subList(end, headTokens.size()).stream().allMatch(t -> defTokens.contains(t))) return false; return true; } private List<String> window(int lower, int upper, List<String> list) { List<String> ret = new ArrayList<>(); for (int i = lower; i < upper; i++) { if (i < 0 || i >= list.size()) ret.add("(*)"); else ret.add(list.get(i)); } return ret; } private boolean exactExclusion(Derivation def, int start, int end) { if (opts.verbose > 0) LogInfo.log("In exactExclusion"); if (end - start > opts.maxExactExclusionLength) return false; boolean prefixEq = window(start - opts.windowSize, start, headTokens) .equals(window(def.start - opts.windowSize, def.start, defTokens)); boolean sufixEq = window(end, end + opts.windowSize, headTokens) .equals(window(def.end, def.end + opts.windowSize, defTokens)); if (opts.verbose > 0) LogInfo.logs("%b : %b", prefixEq, sufixEq); if (opts.verbose > 0) LogInfo.logs("(%d,%d)-head(%d,%d): %b %b %s %s", def.start, def.end, start, end, prefixEq, sufixEq, window(end, end + opts.windowSize, headTokens), window(def.end, def.end + opts.windowSize, defTokens)); if (!prefixEq || !sufixEq) return false; if (headTokens.subList(start, end).equals(defTokens.subList(def.start, def.end))) return false; return true; } // exact match plus big private boolean cmdSet(Derivation def, int start, int end) { if (opts.verbose > 0) LogInfo.log("In exactPlusBig"); // match only beginning and end boolean cmdSet = (end == headTokens.size()) && (start > 0) && def.end == defTokens.size() && def.start > 0; if (cmdSet && headTokens.subList(0, start).equals(defTokens.subList(0, start))) return true; return false; } }