Skip to content

Commit ebbdfa6

Browse files
✨ feat: First draft.
1 parent aee1e2d commit ebbdfa6

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"transform-remove-console",
2828
{
2929
"exclude": [
30+
"debug",
3031
"log",
3132
"error",
3233
"warn"
@@ -77,6 +78,8 @@
7778
},
7879
"dependencies": {},
7980
"devDependencies": {
81+
"@aureooms/js-string": "^0.2.0",
82+
"@aureooms/js-trie": "^0.0.1",
8083
"@babel/cli": "7.11.6",
8184
"@babel/core": "7.11.6",
8285
"@babel/preset-env": "7.11.5",
@@ -101,7 +104,13 @@
101104
"lib"
102105
],
103106
"homepage": "https://aureooms.github.io/js-lempel-ziv",
104-
"keywords": ["compression", "lempel", "lossless", "lz", "ziv"],
107+
"keywords": [
108+
"compression",
109+
"lempel",
110+
"lossless",
111+
"lz",
112+
"ziv"
113+
],
105114
"license": "AGPL-3.0",
106115
"main": "lib/index.js",
107116
"prettier": {

src/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import lz78 from './lz78';
2+
3+
/* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */
4+
export default {
5+
lz78,
6+
};
7+
8+
export {lz78};

src/lz78.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {Trie, ObjectNode} from '@aureooms/js-trie';
2+
import {push, iter} from '@aureooms/js-persistent-stack';
3+
4+
/**
5+
* Simple Lempel-Ziv lossless data compression algorithm implementation.
6+
*/
7+
8+
// TODO read input as async tape
9+
// TODO await trie operations
10+
// TODO use persistent trie
11+
12+
export function* encode({trie, table}, symbols) {
13+
let chunkNumber = table.length - 1;
14+
const itSymbols = symbols[Symbol.iterator]();
15+
while (true) {
16+
const [part, node] = trie.getClosestAncestor(itSymbols);
17+
if (part === undefined) {
18+
yield [node.value(), undefined];
19+
break;
20+
}
21+
22+
node.set(part, ++chunkNumber);
23+
24+
yield [node.value(), part];
25+
}
26+
}
27+
28+
const reify = (chunk) => [...iter(chunk)].reverse().join('');
29+
30+
// TODO use persistent table
31+
32+
export function* decode({table}, tokens) {
33+
for (const [parent, child] of tokens) {
34+
const previous = table[parent];
35+
36+
if (child === undefined) {
37+
yield reify(previous);
38+
} else {
39+
const newChunk = push(previous, child);
40+
yield reify(newChunk);
41+
table.push(newChunk);
42+
}
43+
}
44+
}
45+
46+
// TODO use persistent dict (hence a persistent trie and table)
47+
export const dict = (symbols = ['']) => {
48+
const trie = makeTrie(symbols);
49+
const table = makeTable(trie);
50+
return {trie, table};
51+
};
52+
53+
const makeTable = (trie) => {
54+
const table = [];
55+
for (const [key, value] of trie) table[value] = key;
56+
return table;
57+
};
58+
59+
const emptyTrie = () => {
60+
const root = new ObjectNode();
61+
const trie = new Trie(root);
62+
return trie;
63+
};
64+
65+
const makeTrie = (symbols) => {
66+
const trie = emptyTrie();
67+
let i = 0;
68+
for (const x of symbols) trie.set(x, i++);
69+
return trie;
70+
};

test/src/lz78.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import test from 'ava';
2+
3+
import {mul} from '@aureooms/js-string';
4+
import {dict, encode, decode} from '../../src/lz78';
5+
6+
const alphabetaL = 'abcdefghijklmnopqrstuvwxyz';
7+
const alphabetaU = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8+
const initEmpty = () => dict();
9+
const initAlphabet = () => dict(['', ...alphabetaL, ...alphabetaU]);
10+
11+
const macro = (t, init, input) => {
12+
// TODO use init only once once persistent data structures are used
13+
const tokens = encode(init(), input);
14+
const chunks = decode(init(), tokens);
15+
const decoded = [...chunks].join('');
16+
17+
t.deepEqual(input, decoded, 'decoded message must be the same as input');
18+
};
19+
20+
const repr = (string) =>
21+
string.length >= 20 ? string.slice(0, 9) + '..' + string.slice(-9) : string;
22+
23+
macro.title = (title, init, input) => `lz78 <${init.name}>: ${repr(input)}`;
24+
25+
for (const init of [initEmpty, initAlphabet]) {
26+
test(macro, init, '');
27+
test(macro, init, 'a');
28+
test(macro, init, 'ab');
29+
test(macro, init, 'abc');
30+
test(macro, init, mul('GZYAGZUAYZGUAYZFAAFAFTAZFTAFZTAFTZFATFA', 4));
31+
test(macro, init, mul('B', 81));
32+
}

0 commit comments

Comments
 (0)