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 &&...