├── .gitignore ├── media └── octopi.png ├── plugins ├── has.js └── delete.js ├── test ├── test_has.js ├── test_delete.js ├── test.html └── test.js ├── bower.json ├── README.md └── octopi.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /media/octopi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-eeo/octopi/HEAD/media/octopi.png -------------------------------------------------------------------------------- /plugins/has.js: -------------------------------------------------------------------------------- 1 | Octopi.prototype.has = function(key) { 2 | return !!this.get(key).length; 3 | }; 4 | -------------------------------------------------------------------------------- /test/test_has.js: -------------------------------------------------------------------------------- 1 | describe('Octopi.has', function() { 2 | var oct = new Octopi(['abc', 'def']); 3 | 4 | it('returns true if the key is present', function() { 5 | assert(oct.has('abc') === true); 6 | assert(oct.has('def') === true); 7 | }); 8 | 9 | it('returns false if the key is not present', function() { 10 | assert(oct.has('ghi') === false); 11 | }); 12 | 13 | it('returns true for partial matches', function() { 14 | assert(oct.has('a') === true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/test_delete.js: -------------------------------------------------------------------------------- 1 | describe('Octopi.delete', function() { 2 | var oct = new Octopi(['bid', 'bi', 'b']); 3 | 4 | it('deletes the entries from the table', function() { 5 | oct.delete('bi'); 6 | for (var i in this.table) 7 | assert.notOk(/bi/.test(this.table[i])); 8 | }); 9 | 10 | it(".get doesn't include the entry", function() { 11 | assert.deepEqual(oct.get(''), ['b']); 12 | }); 13 | 14 | it('compacts the tree', function() { 15 | assert.notOk('i' in oct.tree['b']); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octopi", 3 | "main": "octopi.js", 4 | "authors": [ 5 | "Eeo Jun <141bytes@gmail.com>" 6 | ], 7 | "description": "micro client-side suggestions library", 8 | "moduleType": [], 9 | "keywords": [ 10 | "trie" 11 | ], 12 | "license": "MIT", 13 | "homepage": "github.com/eugene-eeo/octopi", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests", 20 | "media" 21 | ], 22 | "devDependencies": { 23 | "mocha": "~2.3.4", 24 | "chai": "~3.4.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/delete.js: -------------------------------------------------------------------------------- 1 | Octopi.prototype.delete = function(key) { 2 | var sub = this.tree; 3 | var trees = [sub]; 4 | var length = key.length; 5 | 6 | for (var i = 0; i < length; i++) { 7 | if (!(sub = sub[key[i]])) 8 | return; 9 | trees.push(sub); 10 | } 11 | 12 | var toDelete = sub.$$; 13 | 14 | for (var i = toDelete.length; i--;) { 15 | var id = toDelete[i]; 16 | 17 | for (var j = trees.length; j--;) { 18 | var ids = trees[j].$$; 19 | ids.splice(ids.indexOf(id), 1); 20 | if (j && !ids.length) 21 | delete trees[j-1][key[j-1]]; 22 | } 23 | 24 | delete this.table[id]; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | Micro (0.5kB) trie based suggestion generating
6 | library, made with autocompletion, control, and
7 | performance on mobile browsers in mind. Check out
8 | the [demo](https://eugene-eeo.github.io/octopi/demo.html).
9 |
10 | ```js
11 | var oct = new Octopi(['bird', 'boy']);
12 | oct.add('bid', {'word': 'BID'});
13 | oct.get('bi'); // => ['bird', {'word':'BID'}]
14 | ```
15 |
16 | ### Do you even normalise?
17 |
18 | Octopi allows for arbitrary data to be associated
19 | with words added to the tree. This means that you
20 | have full control over normalisation of the words.
21 | For example:
22 |
23 | ```js
24 | word = 'CoolUseRName';
25 | oct.add(word.toLowerCase(), word);
26 | oct.get(word.toLowerCase());
27 | ```
28 |
29 | ### Deleting entries
30 |
31 | You can use the [delete](plugins/delete.js) plugin:
32 |
33 | ```js
34 | oct.delete('bid');
35 | ```
36 |
37 | ### Installation
38 |
39 | ```sh
40 | $ bower install eugenee-eeo/octopi
41 | ```
42 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var assert = chai.assert;
2 |
3 | describe('Octopi(words)', function() {
4 | it('indexes the words', function() {
5 | var o = new Octopi(['bi', 'bir', 'bird']);
6 | assert.deepEqual(o.get('b'), ['bi', 'bir', 'bird']);
7 | });
8 | });
9 |
10 | describe('Octopi constructor', function() {
11 | it('returns the constructor function', function() {
12 | var o = new Octopi();
13 | assert.deepEqual(o.constructor, Octopi);
14 | });
15 | });
16 |
17 | describe('Octopi.add', function() {
18 | it('allows arbitrary data to be added', function() {
19 | var o = new Octopi();
20 | o.add('bird', 1);
21 | assert.deepEqual(o.get('bird'), [1]);
22 | });
23 | });
24 |
25 | describe('Octopi.get', function() {
26 | var o = new Octopi();
27 | o.add('bird');
28 | o.add('bid');
29 | o.add('ata');
30 |
31 | it('returns all words starting with the given word', function() {
32 | var s1 = o.get('bi');
33 | var s2 = o.get('b');
34 | assert.deepEqual(s1, s2);
35 |
36 | var s3 = o.get('a');
37 | assert.deepEqual(s3, ['ata']);
38 | });
39 |
40 | it('returns the words in the order they were added', function() {
41 | var s = o.get('');
42 | assert.deepEqual(s, ['bird', 'bid', 'ata']);
43 | });
44 |
45 | it('returns an empty array if there are no matches', function() {
46 | assert.deepEqual(o.get('c'), []);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/octopi.js:
--------------------------------------------------------------------------------
1 | Octopi = function(words) {
2 | this.uid = 0;
3 | this.tree = {$$: []};
4 | this.table = {};
5 | words = (words || []);
6 | for (var i = 0; i < words.length; i++)
7 | this.add(words[i]);
8 | };
9 |
10 | /**
11 | * Take serialized Octopi data as string and initialize the Octopi object
12 | * @param json String The serialized Octopi trie
13 | */
14 | Octopi.load = function(json){
15 | var oct = new Octopi();
16 | var o = JSON.parse(json);
17 | oct.uid = o.uid;
18 | oct.tree = o.tree;
19 | oct.table = o.table;
20 | return oct;
21 | }
22 |
23 | Octopi.prototype = {
24 | constructor: Octopi,
25 | /**
26 | * Add a new element to the trie
27 | * @param key String prefix to look up
28 | * @param data Object returned by trie
29 | */
30 | add: function(key, data) {
31 | var id = ++this.uid;
32 | var sub = this.tree;
33 |
34 | this.table[id] = data || key;
35 | sub.$$.push(id);
36 |
37 | for (var i = 0; i < key.length; i++) {
38 | var c = key[i];
39 | sub = sub[c] || (sub[c] = {$$:[]});
40 | sub.$$.push(id);
41 | }
42 | },
43 |
44 | /**
45 | * Return the list of elements in the trie for a given query
46 | * @param key String The prefix to lookup
47 | */
48 | get: function(key) {
49 | var sub = this.tree;
50 | var tbl = this.table;
51 | for (var i = 0; i < key.length; i++)
52 | if (!(sub = sub[key[i]]))
53 | return [];
54 |
55 | return sub.$$.map(function(id) {
56 | return tbl[id];
57 | });
58 | },
59 | /**
60 | * Serialize the Octopi trie as string
61 | *
62 | * @return String
63 | */
64 | serialize: function(){
65 | var o = { uid: this.uid,
66 | tree: this.tree,
67 | table: this.table
68 | }
69 | return JSON.stringify(o);
70 |
71 | }
72 | };
73 |
--------------------------------------------------------------------------------