Explorer.js

/*Copyright (C) 2019-2022 Crawford Currie http://c-dot.co.uk*/

import { Dictionary } from "./Dictionary.js";

/**
 * Different ways to explore a dictionary
 */
class Explorer {

  /**
   * Determine if the words passed are valid sub-sequences of any
   * word in the dictionary e.g. 'UZZL' is a valid sub-sequence in
   * an English dictionary as it is found in 'PUZZLE', but 'UZZZL'
   * isn't.
   * @param {Dictionary} dictionary dawg to explore
   * @param {string[]?} words list of words to check
   * @param {function} report reporter function(word). This function
   * will be called each time a matching word is found, passing the word
   * as a string.
   */
  static sequences(dictionary, words, report) {
    if (!(dictionary instanceof Dictionary))
      throw Error("Not a Dictionary");
    const valid = [];
    //report(`Valid sequences:`);
    for (let w of words) {
      if (dictionary.hasSequence(w))
        report(w);
    }
  }

  /**
   * Find anagrams of the words that use all the letters. `.`
   * works as a character wildcard.
   * @param {Dictionary} dictionary dawg to explore
   * @param {string[]?} words list of words to check
   * @param {function} report reporter function(word). This function
   * will be called each time a matching word is found, passing the word
   * as a string.
   */
  static anagrams(dictionary, words, report) {
    if (!words || words.length === 0)
      throw Error("Need letters to find anagrams of");

    for (const w of words) {
      let anag = Object.keys(dictionary.findAnagrams(w.replace(/\./g, " ")));
      anag = anag.filter(word => word.length === w.length);
      //report(`${anag.length} words found in "${w}":`);
      anag.forEach(w => report(w));
    }
  }

  /**
   * Find words that hangman match all the letters. `.`
   * works as a character wildcard.
   * @param {Dictionary} dictionary dawg to explore
   * @param {string[]?} words list of words to check
   * @param {function} report reporter function(word). This function
   * will be called each time a matching word is found, passing the word
   * as a string.
   */
  static hangmen(dictionary, words, report) {
    if (!words || words.length === 0)
      throw Error("Need letters to find hangman matches for");

    for (const w of words) {
      const matches =
            dictionary.findHangmen(w.replace(/\./g, " "));
      matches.forEach(w => report(w));
    }
  }

  /**
   * Find arrangements of the letters in the words e.g. `UIE` is a
   * an arrangement of the letters in `QUIET`, as is `EIU` and `IUE`.
   * @param {Dictionary} dictionary dawg to explore
   * @param {string[]?} words list of words to check
   * @param {function} report reporter function(word). This function
   * will be called each time a matching word is found, passing the word
   * as a string.
   */
  static arrangements(dictionary, words, report) {
    if (!words || words.length === 0)
      throw Error("Need letters to find arrangements of");

    for (const w of words) {
      let anag = Object.keys(dictionary.findAnagrams(w));
      //report(`${anag.length} words found in "${w}":`);
      anag.forEach(w => report(w));
    }
  }

  /**
   * List all the words in the dictionary. If `words` is given,
   * list all dictionary entries that start with one of the words.
   * @param {Dictionary} dictionary dawg to explore
   * @param {string[]?} words list of words to check
   * @param {function} report reporter function(word). This function
   * will be called once for each word in the dictionary.
   * as a string.
   */
  static list(dictionary, words, report) {
    if (!words || words.length === 0) {
      dictionary.eachWord((s, n) => report(s));
      return;
    }

    // Dump of words that match words
    const biglist = {};
    words.map(w => {
      const word = w.toUpperCase();
      const node = dictionary.match(word);
      if (node)
        return { word: word, node: node };
      return undefined;
    })
    .filter(r => r)
    .sort((a, b) => {
      return a.word.length > b.word.length ? -1 :
      a.word.length === b.word.length ? 0 : 1;
    })
    .map(root => {
      if (root.node.child) {
        let list = [];
        biglist[root.word] = true;
        root.node.child.eachWord(root.word, w => list.push(w));

        list = list.filter(w => !biglist[w]);
        list.forEach(w => {
          biglist[w] = true;
          report(w);
        });
      }
    });
  }
}

export { Explorer };