Skip to content

Commit e8882e1

Browse files
committed
✅ test: update tests & update README
1 parent be5ff5e commit e8882e1

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

Diff for: packages/huffman/README.md

+71
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,77 @@ A typescript implementation of the **huffman** coding.
105105
```
106106

107107

108+
## Example
109+
110+
* Compress uri data
111+
112+
```typescript
113+
const data = { name: 'alice', age: 33, gender: 'female' }
114+
115+
compress(JSON.stringify(data))
116+
// => 'WyIzIiwyMCwiLCIsMTYsIn0iLDM0LCJpIiwzNSwiZyIsMTgsIm4iLDE5LCJsIiwyMSwiciIsNDQsImMiLDkwLCJkIiw5MSwiOiIsMjMsIlwiIiw2LCJlIiwxNCwiYSIsMzAsInsiLDEyNCwiZiIsMTI1LCJtIiw2M10_-BvI)(p7lG1oLi06IEWNvMnvd(l0C'
117+
118+
decompress('WyIzIiwyMCwiLCIsMTYsIn0iLDM0LCJpIiwzNSwiZyIsMTgsIm4iLDE5LCJsIiwyMSwiciIsNDQsImMiLDkwLCJkIiw5MSwiOiIsMjMsIlwiIiw2LCJlIiwxNCwiYSIsMzAsInsiLDEyNCwiZiIsMTI1LCJtIiw2M10_-BvI)(p7lG1oLi06IEWNvMnvd(l0C')
119+
// => '{"name":"alice","age":33,"gender":"female"}'
120+
121+
function compress(text) {
122+
const { encodedData, encodingTable } = huffman.encode(text)
123+
const cipherBuffer = huffman.compress(encodedData)
124+
const cipherText = base64.encode(cipherBuffer)
125+
126+
const textEncoder = new TextEncoder('utf-8')
127+
const encodingTableText = base64.encode(
128+
textEncoder.encode(JSON.stringify(compressEncodingTable(encodingTable))),
129+
)
130+
const result = (encodingTableText + '-' + cipherText)
131+
.replace(/\//g, '(')
132+
.replace(/\+/g, ')')
133+
.replace(/=/g, '_')
134+
return result
135+
}
136+
137+
function decompress(text) {
138+
const [encodingTableText, cipherText] = text
139+
.replace(/\(/g, '/')
140+
.replace(/\)/g, '+')
141+
.replace(/_/g, '=')
142+
.split('-')
143+
const textDecoder = new TextDecoder('utf-8')
144+
const encodingTable = decompressEncodingTable(
145+
JSON.parse(textDecoder.decode(base64.decode(encodingTableText))),
146+
)
147+
const tree = huffman.buildHuffmanTree(encodingTable)
148+
149+
const cipherData = huffman.decompress(base64.decode(cipherText))
150+
const plaintext = huffman.decode(cipherData, tree)
151+
return plaintext
152+
}
153+
154+
function compressEncodingTable(table) {
155+
const entries = Object.entries(table)
156+
.map(([value, path]) => {
157+
let p = 1
158+
for (const x of path) p = (p << 1) | x
159+
return [value, p]
160+
})
161+
.flat()
162+
return entries
163+
}
164+
165+
function decompressEncodingTable(entries) {
166+
const table = {}
167+
for (let i = 0; i < entries.length; i += 2) {
168+
const value = entries[i]
169+
const p = entries[i + 1]
170+
const path = []
171+
for (let x = p; x > 1; x >>= 1) path.push(x & 1)
172+
table[value] = path.reverse()
173+
}
174+
return table
175+
}
176+
```
177+
178+
108179
## Related
109180

110181
* [Huffman coding | Wikipedia](https://en.wikipedia.org/wiki/Huffman_coding)

Diff for: packages/huffman/__test__/uri-compress.spec.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import base64 from '@algorithm.ts/base64'
2+
import { TextDecoder, TextEncoder } from 'util'
3+
import type { IHuffmanEncodingTable } from '../src'
4+
import huffman from '../src'
5+
6+
describe('uri-compress', function () {
7+
const data = { name: 'alice', age: 33, gender: 'female' }
8+
9+
test('compress', function () {
10+
expect(compress(JSON.stringify(data))).toEqual(
11+
'WyIzIiwyMCwiLCIsMTYsIn0iLDM0LCJpIiwzNSwiZyIsMTgsIm4iLDE5LCJsIiwyMSwiciIsNDQsImMiLDkwLCJkIiw5MSwiOiIsMjMsIlwiIiw2LCJlIiwxNCwiYSIsMzAsInsiLDEyNCwiZiIsMTI1LCJtIiw2M10_-BvI)(p7lG1oLi06IEWNvMnvd(l0C',
12+
)
13+
})
14+
15+
test('decompress', function () {
16+
expect(
17+
decompress(
18+
'WyIzIiwyMCwiLCIsMTYsIn0iLDM0LCJpIiwzNSwiZyIsMTgsIm4iLDE5LCJsIiwyMSwiciIsNDQsImMiLDkwLCJkIiw5MSwiOiIsMjMsIlwiIiw2LCJlIiwxNCwiYSIsMzAsInsiLDEyNCwiZiIsMTI1LCJtIiw2M10_-BvI)(p7lG1oLi06IEWNvMnvd(l0C',
19+
),
20+
).toEqual('{"name":"alice","age":33,"gender":"female"}')
21+
})
22+
})
23+
24+
function compress(text: string): string {
25+
const { encodedData, encodingTable } = huffman.encode(text)
26+
const cipherBuffer = huffman.compress(encodedData)
27+
const cipherText = base64.encode(cipherBuffer)
28+
29+
// const textEncoder = new TextEncoder('utf-8')
30+
const textEncoder = new TextEncoder()
31+
const encodingTableText = base64.encode(
32+
textEncoder.encode(JSON.stringify(compressEncodingTable(encodingTable))),
33+
)
34+
const compressedText = (encodingTableText + '-' + cipherText)
35+
.replace(/\//g, '(')
36+
.replace(/\+/g, ')')
37+
.replace(/=/g, '_')
38+
return compressedText
39+
}
40+
41+
function decompress(compressedText: string): string {
42+
const [encodingTableText, cipherText] = compressedText
43+
.replace(/\(/g, '/')
44+
.replace(/\)/g, '+')
45+
.replace(/_/g, '=')
46+
.split('-')
47+
48+
// const textDecoder = new TextDecoder('utf-8')
49+
const textDecoder = new TextDecoder()
50+
const encodingTable = decompressEncodingTable(
51+
JSON.parse(textDecoder.decode(base64.decode(encodingTableText))),
52+
)
53+
const tree = huffman.buildHuffmanTree(encodingTable)
54+
55+
const cipherData = huffman.decompress(base64.decode(cipherText))
56+
const plaintext = huffman.decode(cipherData, tree)
57+
return plaintext
58+
}
59+
60+
function compressEncodingTable(table: IHuffmanEncodingTable): Array<string | number> {
61+
const entries = Object.entries(table)
62+
.map(([value, path]) => {
63+
let p = 1
64+
for (const x of path) p = (p << 1) | x
65+
return [value, p] as [string, number]
66+
})
67+
.flat()
68+
return entries
69+
}
70+
71+
function decompressEncodingTable(entries: Array<string | number>): IHuffmanEncodingTable {
72+
const table = {}
73+
for (let i = 0; i < entries.length; i += 2) {
74+
const value = entries[i] as string
75+
const p = entries[i + 1] as number
76+
const path = []
77+
for (let x = p; x > 1; x >>= 1) path.push(x & 1)
78+
table[value] = path.reverse()
79+
}
80+
return table
81+
}

0 commit comments

Comments
 (0)