Search Part VII: Autocomplete w/Wild Cards

by rocketegg0

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.1/angular-sanitize.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<div ng-controller="MyCtrl">
    <h2>Part 7: Autocomplete with Wild Cards</h2>
    <div>Search: <input type="text" ng-model="textIn"></input></div>
    <div>Limit: <input type="number" ng-model="limit"></input>
        <p>Use 0 for unlimited, Use '*' for wildcard</p>
    </div>
    <div>Use BFS: <input type="radio" ng-model="bfs" ng-value=
"true"></input>
        Use DFS: <input type="radio" ng-model="bfs" ng-value="false"></input>
    </div>
    <div class="row">
        <div class="col-xs-12">
            <table>
                <caption>Search Results ({{ intersectedResults.length }})</caption>
                <tr>
                    <th>Words (Intersected)</th>
                </tr>
                <tr ng-repeat="word in intersectedResults">
                    <td><a ng-href="#{{word}}" ng-click="select(word)">{{::word}}</a></td>
                </tr>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="table-responsive col-xs-6">
            <table>
                <caption>Search Results ({{ results.length }})</caption>
                <tr>
                    <th>Words (Prefix)</th>
                </tr>
                <tr ng-repeat="word in results">
                    <td><a ng-href="#{{word}}" ng-click="select(word)">{{::word}}</a></td>
                </tr>
            </table>
        </div>
        <div class="table-responsive col-xs-6">
            <table>
                <caption>Search Results ({{ reverseResults.length }})</caption>
                <tr>
                    <th>Words (Suffix)</th>
               ...

CSS

h2 {
    font-size: 150%;
}

table {
    margin-top: 10px;
    border: 1px solid #ccc;
    width: 100%;
}

table th {
    font-weight: bold;
}

em {
    font-style:italic;
}

strong {
    font-weight: bold;
}

JavaScript

var myApp = angular.module('myApp',['ngSanitize']);

myApp.controller('MyCtrl', function($scope, $http) {
    
    // Compute unique hash words
    var toHash = function(dict) {
        return Object.keys(dict.split(' ').reduce(function(memo, word) {
			if (!memo.hasOwnProperty(word)) { memo[word] = true; }
            return memo;
        }, {})).sort();
    };
    
    // Compute prefix trie
    var toTrie = function(words) {
    	var root = new Node();
        words.forEach(function(word) {
            root.insert(word.toLowerCase());
        });
		return root;
    };
    
    var reverse = function(words) {
        return words.map(function(word) {
            return word.reverse();
        });
    };
    
    var wildcard = function(textIn) {
        if (!textIn) { return; }
        var substrs = textIn.split('*');
        var p = textIn[0] === '*' ? 1 : 0;
        var s = textIn[textIn.length-1] === '*' ? 1 : 0;
        var prefixes = substrs.length > 1 ? substrs.slice(p, substrs.length - 1 + s) : substrs;
        var suffixes = substrs.length > 1 ? substrs.slice(1 - p, substrs.length - s) : substrs;
        
        $scope.results = _(prefixes).map(function(prefix) {
            return $scope.autocomplete(prefix, $scope.root);
        }).flatten().value();
        $scope.reverseResults = reverse(_(suffixes).map(function(suffix) {
            return $scope.autocomplete(suffix.reverse(), $scope.reverseRoot);
        }).flatten().value());
        $scope.intersectedResults = _.intersection($scope.results, $scope.reverseResults);
    };
    
    $scope.select = function(word) { $scope.textIn = word; };
    
    $scope.autocomplete = function(textIn, root) {
        if (!textIn || textIn.length < 2) {
            return [];
        }
        var node = root;
        var path = '';
        var chars = textIn.split('').map(function(c) { return c.toLowerCase(); })
        for (var i = 0; i < chars.length; i++) {
            if (node &&...