Skip to content

Commit 1c3a87d

Browse files
authored
chore: menambahkan huffman encoding (#57)
huffman encoding adalah losessless compression algorithm yang dikembangkan oleh huffman, tujuannya antara lain mengurangi ukuran file atau data dengan mengganti simbol menjadi kode biner yang lebih pendek, terutama untuk simbol yang sering muncul Signed-off-by: slowy07 <[email protected]>
1 parent 5d19e3e commit 1c3a87d

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed

kompresi/huffman.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package kompresi
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// node merepresentasikan simpla dalam pohon huffman
8+
// setiap node memiliki
9+
// - pointer ke child kiri
10+
// - pointer ke child kanan
11+
// - karakter ASCII jika ini nantinya adalah leaf node
12+
// - frekuensi kemunculan dari simbol
13+
type Node struct {
14+
kiri *Node
15+
kanan *Node
16+
simbol rune
17+
bobot int
18+
}
19+
20+
// FrekuensiSimbol menyimpan informasi kemunculan suatu simbol
21+
type FrekuensiSimbol struct {
22+
Simbol rune // karakter ASCII
23+
Frekuensi int // jumlah kemunculan simbol tersebut
24+
}
25+
26+
// fungsi membangun pohon huffman berdasarkan daftar list frekuensi simbol
27+
// menggunakan dua antrian untuk efisiensi tanpa heap
28+
//
29+
// Parameter:
30+
//
31+
// listFrekuensi []FrekuensiSimbol daftar list pasangan simbol
32+
//
33+
// Return:
34+
// *Node: pointer ke root pohon huffman
35+
// error: jika input tidak valid
36+
func TreeHuffman(listFrekuensi []FrekuensiSimbol) (*Node, error) {
37+
if len(listFrekuensi) < 1 {
38+
return nil, fmt.Errorf("TreeHuffman: list tidak boleh kosong")
39+
}
40+
41+
// inisialisasi antrian pertama (q1) dengan semua leaf node
42+
q1 := make([]Node, len(listFrekuensi))
43+
// q2 digunakan untuk node internal
44+
q2 := make([]Node, 0, len(listFrekuensi))
45+
46+
for i, x := range listFrekuensi {
47+
q1[i] = Node{
48+
kiri: nil, kanan: nil, simbol: x.Simbol, bobot: x.Frekuensi,
49+
}
50+
}
51+
52+
// bangun pohon hingga tersisa satu node
53+
for len(q1)+len(q2) > 1 {
54+
var node1, node2 Node
55+
// ambil node dengan bobot nilai terkecil
56+
node1, q1, q2 = least(q1, q2)
57+
node2, q1, q2 = least(q1, q2)
58+
// buat node internal baru sebagai parent dari kedua node
59+
node := Node{kiri: &node1, kanan: &node2, simbol: -1, bobot: node1.bobot + node2.bobot}
60+
61+
// tambahin nde ke antrian kedua
62+
q2 = append(q2, node)
63+
}
64+
65+
// return root pohon huffman
66+
if len(q1) == 1 {
67+
return &q1[0], nil
68+
}
69+
return &q2[0], nil
70+
}
71+
72+
// fungsi mengambil node dengan bobot nilai terkecil dari antara dua antrian
73+
//
74+
// Parameter:
75+
//
76+
// q1 []Node: Antrian awal dengan leaf node
77+
// q2 []Node: Antrian awal untuk node internal
78+
//
79+
// Return:
80+
// Node: node ndegan bobot terkecil
81+
// []Node: sisa antrian q1 setelah ekstrasi
82+
// []Node: sisa antrian q2 setelah ekstrasi
83+
func least(q1 []Node, q2 []Node) (Node, []Node, []Node) {
84+
if len(q1) == 0 {
85+
// ambil dari q2
86+
return q2[0], q1, q2[1:]
87+
}
88+
if len(q2) == 0 {
89+
// ambil dari q1
90+
return q1[0], q1[1:], q2
91+
}
92+
if q1[0].bobot <= q2[0].bobot {
93+
// ambil dari q1 karena lebih kecil
94+
return q1[0], q1[1:], q2
95+
}
96+
// ambil dari q2 karena lebih kecil
97+
return q2[0], q1, q2[1:]
98+
}
99+
100+
// fungsi yang melakukan traversal pohon huffman untuk membuat tabel
101+
// kode biner
102+
//
103+
// Parameter:
104+
//
105+
// node *Node: root dari pohon huffman
106+
// prefix []bool: jalur dari root ke leaf (false = kiri, true = kanan)
107+
// codes map[rune][]bool: peta simbol ke representasi bitnya
108+
func HuffmanEncoding(node *Node, prefix []bool, codes map[rune][]bool) {
109+
// jika node adalah leaf (memiliki simbol), simpan kode biner
110+
if node.simbol != -1 {
111+
codes[node.simbol] = prefix
112+
return
113+
}
114+
115+
// rekursi ke child kiri
116+
prefixKiri := make([]bool, len(prefix))
117+
copy(prefixKiri, prefix)
118+
prefixKiri = append(prefixKiri, false) // false = maka ke kiri
119+
HuffmanEncoding(node.kiri, prefixKiri, codes)
120+
121+
// rekursi ke child kanan
122+
prefixKanan := make([]bool, len(prefix))
123+
copy(prefixKanan, prefix)
124+
prefixKanan = append(prefixKanan, true) // true = maka ke kanan
125+
HuffmanEncoding(node.kanan, prefixKanan, codes)
126+
}
127+
128+
// fungsi mengubah string menjadi rangkaian bit berdasarkan kode huffman
129+
//
130+
// Parameter:
131+
//
132+
// codes map[rune][]bool: tabel pemetaan simbol ke kode biner
133+
// in string: input teks yang akan dikodekan
134+
//
135+
// Return:
136+
//
137+
// []bool: representasi biner dari teks input
138+
func HuffmanEncode(codes map[rune][]bool, in string) []bool {
139+
out := make([]bool, 0)
140+
for _, s := range in {
141+
out = append(out, codes[s]...)
142+
}
143+
return out
144+
}
145+
146+
// fungsi mengubah rangkaian bit kembali ke bentuk asli menggunakan pohon huffman
147+
//
148+
// Parameter:
149+
//
150+
// root *Node: root poohn huffman
151+
// current *Node: node saat ini dalam traversal
152+
// in []bool: input biner yang akan didekode
153+
// out string: output hasil dekoding
154+
//
155+
// Return:
156+
//
157+
// string: hasil dekoding dari bit input
158+
func HuffmanDecode(root, current *Node, in []bool, out string) string {
159+
// jika sudah mencapai leaf node, tambahkan simbol ke hasil
160+
if current.simbol != -1 {
161+
out += string(current.simbol)
162+
// lanjutkan dari akar lagi untuk karakter berikutnya
163+
return HuffmanDecode(root, root, in, out)
164+
}
165+
166+
// jika input habis, return hasil
167+
if len(in) == 0 {
168+
return out
169+
}
170+
171+
// geser ke child kiri atau kanan sesuai bit saat ini
172+
if in[0] {
173+
return HuffmanDecode(root, current.kanan, in[1:], out)
174+
}
175+
return HuffmanDecode(root, current.kiri, in[1:], out)
176+
}

tests/kompresi/huffman_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package kompresi
2+
3+
import (
4+
"sort"
5+
"testing"
6+
7+
"github.com/bellshade/Golang/kompresi"
8+
)
9+
10+
func FrekuensiSimbol(pesan string) []kompresi.FrekuensiSimbol {
11+
hitungRune := make(map[rune]int)
12+
for _, s := range pesan {
13+
hitungRune[s]++
14+
}
15+
listFrekuensi := make([]kompresi.FrekuensiSimbol, len(hitungRune))
16+
i := 0
17+
for s, n := range hitungRune {
18+
listFrekuensi[i] = kompresi.FrekuensiSimbol{Simbol: s, Frekuensi: n}
19+
i++
20+
}
21+
sort.Slice(listFrekuensi, func(i, j int) bool {
22+
return listFrekuensi[i].Frekuensi < listFrekuensi[j].Frekuensi
23+
})
24+
return listFrekuensi
25+
}
26+
27+
func TestFungsiHuffman(t *testing.T) {
28+
pesan := []string{
29+
"bellshade bahasa pemograman Golang",
30+
"indonesia adalah negara kesatuan berbentuk republik",
31+
}
32+
33+
for _, kata := range pesan {
34+
t.Run("huffman: "+kata, func(t *testing.T) {
35+
tree, _ := kompresi.TreeHuffman(FrekuensiSimbol(kata))
36+
codes := make(map[rune][]bool)
37+
kompresi.HuffmanEncoding(tree, nil, codes)
38+
messageTerkode := kompresi.HuffmanEncode(codes, kata)
39+
pesanHuffmanDecoded := kompresi.HuffmanDecode(tree, tree, messageTerkode, "")
40+
if pesanHuffmanDecoded != kata {
41+
t.Errorf("error: %q\n ekspetasi: %q", pesanHuffmanDecoded, kata)
42+
}
43+
})
44+
}
45+
}

0 commit comments

Comments
 (0)