Commit 19727a73 authored by Aleksandra Dobrosavljevic's avatar Aleksandra Dobrosavljevic
Browse files

uploading projects

parent 7c19c1e8
File added
from collections import Counter
import math
# Definiše klasu Entropy koja prima ime fajla kao argument
class Entropy:
def __init__(self, filename):
self.filename = filename # Konstruktor klase koji postavlja ime fajla
def calculate_entropy(self): # Funkcija za računanje entropije
with open(self.filename, "r", encoding="utf-8") as f: # Otvara fajl u režimu čitanja sa kodiranjem UTF-8
data = f.read() # Učitavanje sadržaja fajla
N = len(data) # Broj karaktera u fajlu
byte_counts = Counter(data) # Brojanje pojavljivanja svakog karaktera
probabilities = {char: Ni / N for char, Ni in byte_counts.items()} # Računanje verovatnoća za svaki karakter
chr_array = [char for char, count in byte_counts.items() if count > 0] # Kreiranje niza karaktera
probability_array = [probabilities[char] for char in chr_array] # Kreiranje niza verovatnoća
# Dodavanje ispisa za svaki karakter
for char, count in byte_counts.items():
p_i = probabilities[char] # Verovatnoća pojavljivanja karaktera
print(f'Karakter {char} se pojavljuje {count} puta, sa verovatnocom: {p_i:.2f}.') # Ispis karaktera, broja pojavljivanja i verovatnoće
entropy_final = -sum(p * math.log2(p) if p > 0 else 0 for p in probabilities.values()) # Računanje entropije
return entropy_final, probabilities, chr_array # Vraćanje konačne entropije, verovatnoća i niza karaktera
from Node import Node # Uvozimo klasu Node iz modula Node
from UcitaniSimboli import UcitaniSimbol # Uvozimo klasu UcitaniSimbol iz modula UcitaniSimbol
import filecmp # Uvozimo modul filecmp za poređenje fajlova
class Huffman: # Definišemo klasu Huffman
def __init__(self, chr_array, probability_array): # Konstruktor klase
self.chr_array = chr_array # Inicijalizujemo niz karaktera
self.probability_array = probability_array # Inicijalizujemo niz verovatnoća
self.huffman_root = self.build_huffman_tree() # Kreiramo Huffmanovo stablo
def build_huffman_tree(self): # Funkcija za kreiranje Huffmanovog stabla
nodes = [Node(self.probability_array[char], char) for char in self.chr_array] # Kreiramo listu čvorova sa verovatnoćama
while len(nodes) > 1: # Dok imamo više od jednog čvora
nodes.sort(key=lambda x: x.probability) # Sortiramo čvorove po verovatnoći
left = nodes.pop(0) # Uzimamo čvor sa najmanjom verovatnoćom kao levi
right = nodes.pop(0) # Uzimamo čvor sa sledećom najmanjom verovatnoćom kao desni
left.code = 0 # Dodeljujemo kod 0 levom čvoru
right.code = 1 # Dodeljujemo kod 1 desnom čvoru
new_node = Node(left.probability + right.probability, left.symbol + right.symbol, left, right) # Kreiramo novi čvor sa zbirnom verovatnoćom i simbolima
nodes.append(new_node) # Dodajemo novi čvor u listu
return nodes[0] # Vraćamo korenski čvor stabla
def print_nodes(self, node, value=''): # Funkcija za ispis kodova čvorova
new_value = value + str(node.code) # Dodajemo trenutni kod čvora vrednosti
if node.left: # Ako čvor ima levi podčvor
self.print_nodes(node.left, new_value) # Rekurzivno pozivamo funkciju za levi podčvor
if node.right: # Ako čvor ima desni podčvor
self.print_nodes(node.right, new_value) # Rekurzivno pozivamo funkciju za desni podčvor
if not node.left and not node.right: # Ako čvor nema podčvorove (list čvor)
print(f"Simbol: {node.symbol} Kod: {new_value}") # Ispisujemo simbol i kod
def encode_array(self, filename): # Funkcija za kodiranje niza iz fajla
code_array = "" # Inicijalizujemo prazan string za kodirani niz
with open(filename, "r", encoding="utf-8") as f: # Otvaramo fajl za čitanje
data = f.read() # Čitamo podatke iz fajla
for char in data: # Iteriramo kroz svaki karakter u podacima
code_array += self.find_code(char, self.huffman_root) # Dodajemo kod karaktera u kodirani niz
return code_array # Vraćamo kodirani niz
def find_code(self, symbol, node, current_code=""): # Funkcija za pronalaženje koda za dati simbol
if not node: # Ako je čvor prazan
return None # Vraćamo None
if symbol in node.symbol: # Ako je simbol u čvoru
if len(node.symbol) == 1: # Ako je čvor list (jedan simbol)
return current_code # Vraćamo trenutni kod
else:
left_code = self.find_code(symbol, node.left, current_code + "0") # Rekurzivno tražimo kod u levom podčvoru
right_code = self.find_code(symbol, node.right, current_code + "1") # Rekurzivno tražimo kod u desnom podčvoru
if left_code: # Ako je kod pronađen u levom podčvoru
return left_code # Vraćamo levi kod
elif right_code: # Ako je kod pronađen u desnom podčvoru
return right_code # Vraćamo desni kod
def save_string_nodes(self, node, value=''): # Funkcija za čuvanje informacija o čvorovima kao string
new_value = value + str(node.code) # Dodajemo trenutni kod čvora vrednosti
nodes_info = "" # Inicijalizujemo prazan string za informacije o čvorovima
if node.left: # Ako čvor ima levi podčvor
nodes_info += self.save_string_nodes(node.left, new_value) # Rekurzivno pozivamo funkciju za levi podčvor
if node.right: # Ako čvor ima desni podčvor
nodes_info += self.save_string_nodes(node.right, new_value) # Rekurzivno pozivamo funkciju za desni podčvor
if not node.left and not node.right: # Ako čvor nema podčvorove (list čvor)
nodes_info += f"{node.symbol}:{new_value} " # Dodajemo simbol i kod u string
return nodes_info # Vraćamo string sa informacijama o čvorovima
@staticmethod
def load_first_line_binary_Huffman(path): # Funkcija za učitavanje prvog reda iz binarnog fajla
with open(path, 'rb') as f: # Otvaramo fajl za čitanje u binarnom modu
first_line = f.readline().decode('utf-8').strip() # Čitamo prvi red i dekodiramo ga
symbols_only = first_line.split() # Delimo red na pojedinačne simbole
symbols_array = [] # Inicijalizujemo praznu listu za simbole
for symbol_only in symbols_only: # Iteriramo kroz svaki simbol
value, code = symbol_only.split(":") # Delimo simbol na vrednost i kod
symbol = UcitaniSimbol(value, code) # Kreiramo objekat UcitaniSimbol sa vrednošću i kodom
symbols_array.append(symbol) # Dodajemo simbol u listu
return symbols_array # Vraćamo listu simbola
@staticmethod
def load_rest_binary_Huffman(path, lengthHuffman): # Funkcija za učitavanje ostatka binarnog fajla
rest = lengthHuffman % 8 # Računamo ostatak pri deljenju dužine sa 8
with open(path, 'rb') as f: # Otvaramo fajl za čitanje u binarnom modu
f.readline() # Preskačemo prvi red
data = f.read() # Čitamo ostatak fajla
byte_array = [] # Inicijalizujemo praznu listu za bajtove
for b in data: # Iteriramo kroz svaki bajt u podacima
bites = bin(b)[2:].rjust(8, '0') # Pretvaramo bajt u binarni string sa popunom od 0
byte_array.extend(map(int, bites)) # Dodajemo bitove u listu
important_string = ''.join(map(str, byte_array)) # Pretvaramo listu bitova u string
if rest > 0: # Ako postoji ostatak
part = 8 - rest # Računamo deo koji nedostaje
help_array = important_string[-8:] # Uzimamo poslednjih 8 bitova
important_string = important_string[:-8] # Uklanjamo poslednjih 8 bitova iz glavnog stringa
help_array = help_array[part:] # Smanjujemo pomoćni niz za deo koji nedostaje
important_string += help_array # Dodajemo modifikovani pomoćni niz nazad u glavni string
return important_string # Vraćamo konačan string bitova
@staticmethod
def decode_Huffman(encoded_array, symbols): # Funkcija za dekodiranje niza kodova
decoded_output = "" # Inicijalizujemo prazan string za dekodirani izlaz
current_code = "" # Inicijalizujemo prazan string za trenutni kod
for bit in encoded_array: # Iteriramo kroz svaki bit u kodiranom nizu
current_code += bit # Dodajemo bit trenutnom kodu
for symbol in symbols: # Iteriramo kroz sve simbole
if current_code == symbol.code: # Ako trenutni kod odgovara kodu simbola
decoded_output += symbol.value # Dodajemo vrednost simbola u dekodirani izlaz
current_code = "" # Resetujemo trenutni kod
break # Prekidamo unutrašnju petlju
return decoded_output # Vraćamo dekodirani izlaz
@staticmethod
def save_decoded_output(decoded_output, filename): # Funkcija za čuvanje dekodiranog izlaza u fajl
with open(filename, 'wb') as f: # Otvaramo fajl za pisanje u binarnom modu
f.write(decoded_output.encode('utf-8')) # Pišemo dekodirani izlaz u fajl
@staticmethod
def compare_files(file1, file2): # Funkcija za poređenje dva fajla
return filecmp.cmp(file1, file2) # Vraćamo rezultat poređenja fajlova koristeći funkciju cmp iz modula filecmp
import filecmp # Uvozimo modul filecmp za poređenje fajlova
class LZ77: # Definišemo klasu LZ77
def __init__(self, window_size): # Konstruktor klase
self.window_size = window_size # Postavljamo veličinu prozora
def compress(self, input_string): # Funkcija za kompresiju
compressed_output = [] # Lista za čuvanje kompresovanih podataka
i = 0 # Indeks trenutnog karaktera u ulaznom stringu
while i < len(input_string): # Dok ne obradimo ceo ulazni string
max_match_length = 0 # Maksimalna dužina poklapanja
max_match_index = 0 # Indeks maksimalnog poklapanja
# Provera najdužeg poklapanja unazad u okviru veličine prozora
for j in range(1, min(self.window_size + 1, i + 1)): # Iteracija unazad unutar prozora
match_length = 0 # Dužina trenutnog poklapanja
while (i + match_length < len(input_string) and
input_string[i + match_length] == input_string[i - j + match_length]): # Provera poklapanja
match_length += 1 # Povećanje dužine poklapanja
if match_length > max_match_length: # Ako je dužina poklapanja veća od trenutne maksimalne
max_match_length = match_length # Ažuriramo maksimalnu dužinu poklapanja
max_match_index = i - j # Ažuriramo indeks maksimalnog poklapanja
if max_match_length > 0: # Ako postoji poklapanje
# Dodajemo tuple (1, pomeraj, dužina) u izlaz
compressed_output.append((1, i - max_match_index, max_match_length))
i += max_match_length # Povećavamo indeks za dužinu poklapanja
else:
# Ako nema poklapanja, dodajemo tuple (0, trenutni karakter) u izlaz
compressed_output.append((0, input_string[i]))
i += 1 # Povećavamo indeks za 1
return compressed_output # Vraćamo kompresovani izlaz
def write_to_binary_file(self, compressed_output, filename): # Funkcija za pisanje kompresovanog izlaza u binarni fajl
with open(filename, "wb") as file: # Otvaramo fajl za pisanje u binarnom modu
for element in compressed_output: # Iteriramo kroz kompresovani izlaz
if element[0] == 0: # Ako nema poklapanja
file.write(b'\x00') # Pišemo marker za nema poklapanja
file.write(element[1].encode('utf-8')) # Pišemo karakter
elif element[0] == 1: # Ako postoji poklapanje
file.write(b'\x01') # Pišemo marker za poklapanje
file.write(element[1].to_bytes(2, byteorder='big')) # Pišemo pomeraj kao 2 bajta
file.write(element[2].to_bytes(2, byteorder='big')) # Pišemo dužinu kao 2 bajta
@staticmethod
def read_from_binary_file(filename): # Funkcija za čitanje kompresovanog izlaza iz binarnog fajla
output = [] # Lista za čuvanje kompresovanih podataka
with open(filename, 'rb') as file: # Otvaramo fajl za čitanje u binarnom modu
while True: # Beskonačna petlja za čitanje fajla
marker = file.read(1) # Čitamo marker (1 bajt)
if not marker: # Ako nema više podataka za čitanje (kraj fajla)
break # Prekidamo petlju
if marker == b'\x00': # Ako marker ukazuje na nema poklapanja
character = file.read(1).decode('utf-8') # Čitamo karakter
output.append((0, character)) # Dodajemo tuple (0, karakter) u izlaz
elif marker == b'\x01': # Ako marker ukazuje na poklapanje
offset = int.from_bytes(file.read(2), byteorder='big') # Čitamo pomeraj (2 bajta)
length = int.from_bytes(file.read(2), byteorder='big') # Čitamo dužinu (2 bajta)
output.append((1, offset, length)) # Dodajemo tuple (1, pomeraj, dužina) u izlaz
return output # Vraćamo kompresovani izlaz
@staticmethod
def decompress(encoded_data): # Funkcija za dekompresiju
decompressed_string = "" # Inicijalizujemo prazan string za dekompresovani izlaz
buffer = "" # Inicijalizujemo prazan string za bafer
for entry in encoded_data: # Iteriramo kroz kompresovane podatke
if entry[0] == 0: # Ako nema poklapanja
decompressed_string += entry[1] # Dodajemo karakter u dekompresovani izlaz
buffer += entry[1] # Dodajemo karakter u bafer
else: # Ako postoji poklapanje
start_index = len(buffer) - entry[1] # Računamo početni indeks poklapanja u baferu
for i in range(entry[2]): # Iteriramo kroz dužinu poklapanja
decompressed_string += buffer[start_index + i] # Dodajemo karakter iz bafera u dekompresovani izlaz
buffer += buffer[start_index + i] # Dodajemo karakter u bafer
return decompressed_string # Vraćamo dekompresovani izlaz
@staticmethod
def compare_files(file1, file2): # Funkcija za poređenje dva fajla
return filecmp.cmp(file1, file2) # Vraćamo rezultat poređenja fajlova koristeći funkciju cmp iz modula filecmp
@staticmethod
def save_decompressed_output(decoded_output, filename): # Funkcija za čuvanje dekompresovanog izlaza u fajl
with open(filename, 'wb') as file: # Otvaramo fajl za pisanje u binarnom modu
file.write(decoded_output.encode('utf-8')) # Pišemo dekompresovani izlaz u fajl
import filecmp #Importujemo potrebni module filecmp
class LZW: #Definisemo klasu LZW
def __init__(self): #Konstruktor klase
self.unique_symbols = [] # Inicijalizujemo listu za jedinstvene simbole
def encode(self, input_string): #kodiramo ulazni string koristeći LZW algoritam i vraćamo kodirani izlaz i rečnik
self.unique_symbols = [] # Resetujemo listu jedinstvenih simbola za svaki novi ulazni string
for x in input_string:
if x not in self.unique_symbols:
self.unique_symbols.append(x) # Dodajemo jedinstvene simbole u listu
dictionary = {x: i + 1 for i, x in enumerate(self.unique_symbols)} # Kreiramo početni rečnik simbola
output = [] # Lista za čuvanje kodiranih izlaznih vrednosti
current_sequence = input_string[0] # Početni niz je prvi simbol ulaznog stringa
for x in input_string[1:]:
if current_sequence + x in dictionary:
current_sequence = current_sequence + x # Ako kombinacija postoji u rečniku, produžujemo trenutni niz
else:
output.append(dictionary[current_sequence]) # Dodajemo indeks trenutnog niza u izlaz
dictionary[current_sequence + x] = len(dictionary) + 1 # Dodajemo novi niz u rečnik
current_sequence = x # Postavljamo trenutni niz na trenutni simbol
output.append(dictionary[current_sequence]) # Dodajemo indeks poslednjeg niza u izlaz
return output, dictionary # Vraćamo kodirani izlaz i rečnik
def write_to_binary_file(self, encoded_output, filename): #zapisujemo kodirani izlaz u binarni fajl
symbols_string = ' '.join(map(str, self.unique_symbols)) # Pretvaramo jedinstvene simbole u string
indices_string = ' '.join(map(str, encoded_output)) # Pretvaramo kodirane indekse u string
with open(filename, 'wb') as file:
file.write(symbols_string.encode() + b'\n') # Pišemo simbole u fajl
file.write(indices_string.encode()) # Pišemo kodirane indekse u fajl
@staticmethod
def read_from_binary_file(filename): #čitamo simbole i kodirane indekse iz binarnog fajla
with open(filename, 'rb') as file:
first_line = file.readline().decode('utf-8').strip() # Čitamo prvu liniju (simboli)
second_line = file.readline().decode('utf-8').strip() # Čitamo drugu liniju (indeksi)
symbols = first_line.split() # Delimo simbole u listu
indices = list(map(int, second_line.split())) # Pretvaramo indekse u listu brojeva
return symbols, indices # Vraćamo simbole i indekse
@staticmethod
def decode(initial_symbols, index_list): # Dekompresujemo kodirani niz koristeći LZW algoritam
dictionary = {i + 1: symbol for i, symbol in enumerate(initial_symbols)} # Kreiramo početni rečnik iz simbola
current_index = len(dictionary) + 1 # Postavljamo sledeći indeks za rečnik
result = [dictionary[index_list[0]]] # Početni rezultat je prvi simbol iz liste indeksa
current_sequence = dictionary[index_list[0]] # Postavljamo trenutni niz na prvi simbol
for index in index_list[1:]:
if index in dictionary:
new_sequence = dictionary[index] # Ako indeks postoji u rečniku, uzimamo novi niz
elif index == current_index:
new_sequence = current_sequence + current_sequence[0] # Ako indeks ne postoji, kreiramo novi niz
result.append(new_sequence) # Dodajemo novi niz u rezultat
dictionary[current_index] = current_sequence + new_sequence[0] # Dodajemo novi niz u rečnik
current_index += 1 # Uvećavamo trenutni indeks za rečnik
current_sequence = new_sequence # Postavljamo trenutni niz na novi niz
return ''.join(result) # Vraćamo dekompresovani string
@staticmethod
def compare_files(file1, file2): #upoređujemo dva fajla
return filecmp.cmp(file1, file2) # Upoređujemo dva fajla
@staticmethod
def save_decompressed_output(decoded_output, filename): # zapisujemo dekompresovani izlaz u fajl
with open(filename, 'wb') as file:
file.write(decoded_output.encode('utf-8')) # Pišemo dekompresovani izlaz u fajl
from Entropy import Entropy # Uvoz Entropy klase iz Entropy modula
from Huffman import Huffman # Uvoz Huffman klase iz Huffman modula
from ShanonFano import ShannonFano # Uvoz ShannonFano klase iz ShanonFano modula
from StepenKompresije import CompressionMetrics # Uvoz CompressionMetrics klase iz StepenKompresije modula
from LZ77 import LZ77 # Uvoz LZ77 klase iz LZ77 modula
from LZW import LZW # Uvoz LZW klase iz LZW modula
import os # Uvoz os modula za rad sa fajl sistemom
class Main: # Definisanje glavne klase programa
def __init__(self, entropy_filename, shannon_fano_encoded_filename, huffman_encoded_filename, lz77_encoded_filename, window_size, lzw_encoded_filename): # Konstruktor klase Main
self.entropy_filename = entropy_filename # Postavljanje imena fajla za entropiju
self.shannon_fano_encoded_filename = shannon_fano_encoded_filename # Postavljanje imena fajla za Shannon-Fano kodiranje
self.huffman_encoded_filename = huffman_encoded_filename # Postavljanje imena fajla za Huffman kodiranje
self.lz77_encoded_filename = lz77_encoded_filename # Postavljanje imena fajla za LZ77 kodiranje
self.lzw_encoded_filename = lzw_encoded_filename # Postavljanje imena fajla za LZW kodiranje
self.window_size = window_size # Postavljanje veličine prozora za LZ77
def run(self): # Metoda za pokretanje glavne logike programa
"""
Entropija:
Analiziramo ulazni niz podataka kako bi se utvrdila učestalost pojavljivanja svakog simbola,
zatim na osnovu učestalosti, izračunava se verovatnoća pojavljivanja svakog simbola.
Koristi se formula entropije H(X)= - sum(p(x)log_2 p(x)),
gde je p(x) verovatnoća pojavljivanja simbola x. Rezultat predstavlja prosečnu količinu informacija po simbolu.
"""
entropy_instance = Entropy(self.entropy_filename) # Kreiranje instance Entropy klase
entropy, probabilities, chr_array = entropy_instance.calculate_entropy() # Računanje entropije i dobijanje verovatnoća i niza karaktera
print(f"ENTROPIJA: {entropy:.2f}") # Ispisivanje vrednosti entropije
"""
Shannon-Fano:
Za postupak kompresije neophodno je sortirati simbole u opadajucem redosledu verovatnoce pojavljivanja, zatim
skup simbola se deli na dva podskupa tako da je ukupna verovatnoća podskupova približno jednaka.
Levom podskupu se dodeljuje '0', a desnom '1'. Proces se ponavlja rekurzivno za svaki podskup dok svi simboli ne dobiju kodove.
Za postupak dekompresije neophodno je da iz binarne datoteke se učitavamo simbole i njihove kodove,
binarnom nizu se pristupa sekvencijalno, prevodeći kodove nazad u originalne simbole.
"""
print("\nKODIRANJE SHANNON-FANO:\n")
shannon_fano = ShannonFano(chr_array, probabilities) # Kreiranje instance ShannonFano klase
shannon_fano.print_symbols() # Ispisivanje simbola i njihovih kodova
encoded_array = shannon_fano.encode_array(self.entropy_filename) # Kodiranje niza karaktera korišćenjem Shannon-Fano algoritma
lengthShanonFano = len(encoded_array) # Dobijanje dužine kodiranog niza
byte_array = bytearray() # Kreiranje praznog niza bajtova
for i in range(0, len(encoded_array), 8): # Iteracija kroz kodirani niz u koracima od 8
byte = int(encoded_array[i:i+8], 2) # Konvertovanje 8-bitnih delova niza u bajtove
byte_array.append(byte) # Dodavanje bajta u niz bajtova
with open(self.shannon_fano_encoded_filename, "wb") as f: # Otvaranje fajla za zapisivanje u binarnom režimu
symbol_info = shannon_fano.save_string_symbols() # Dobijanje niza simbola kao stringa
f.write(symbol_info.encode("utf-8")) # Zapisivanje simbola u fajl
f.write(b"\n") # Zapisivanje novog reda
f.write(byte_array) # Zapisivanje niza bajtova u fajl
print("\nDEKODIRANJE SHANNON-FANO:\n")
symbols_binary = ShannonFano.load_first_line_from_binary_Shannon_Fano(self.shannon_fano_encoded_filename) # Učitavanje prvog reda iz binarnog fajla
decoded_content = ShannonFano.load_rest_binary_Shannon_Fano(self.shannon_fano_encoded_filename, lengthShanonFano) # Učitavanje ostatka binarnog fajla
for symbol in symbols_binary: # Iteracija kroz učitane simbole
print(f"Simbol: {symbol.value} Kod: {symbol.code}") # Ispisivanje simbola i njihovih kodova
decoded_array = ShannonFano.decode_shanon_fano(decoded_content, symbols_binary) # Dekodiranje sadržaja korišćenjem Shannon-Fano algoritma
ShannonFano.save_decoded_output(decoded_array, "decoded_shannon_fano.bin") # Zapisivanje dekodiranog sadržaja u fajl
if ShannonFano.compare_files("brojevi.txt","decoded_shannon_fano.bin"): # Poređenje originalnog i dekodiranog fajla
print("\nShanon-Fano: Fajlovi su isti.")
else:
print("\nShanon-Fano: Fajlovi nisu isti.")
"""
Huffman:
Za postupak kompresije neophodno je kreirati čvor za svaki simbol sa njegovom verovatnoćom,zatim
se spajaju čvorovi sa najmanjom verovatnoćom u novi čvor čija je verovatnoća zbir verovatnoća spojenih čvorova.
Proces se ponavlja dok se ne dobije jedno stablo, krećući se kroz stablo, dodeljuju se kodovi (0 za levo, 1 za desno) svakom simbolu.
Za postupak dekompresije neophodno je da iz binarne datoteke učitamo strukturu Huffmanovog stabla, zatim
binarnom nizu se pristupa sekvencijalno, prevodeći kodove nazad u originalne simbole pomoću Huffmanovog stabla.
"""
print("\nKODIRANJE HUFFMAN:\n")
huffman = Huffman(chr_array, probabilities) # Kreiranje instance Huffman klase
huffman.print_nodes(huffman.huffman_root) # Ispisivanje Huffmanovog stabla
coded_array_h = huffman.encode_array(self.entropy_filename) # Kodiranje niza karaktera korišćenjem Huffman algoritma
lengthHuffman = len(coded_array_h) # Dobijanje dužine kodiranog niza
byte_array_h = bytearray() # Kreiranje praznog niza bajtova
for i in range(0, len(coded_array_h), 8): # Iteracija kroz kodirani niz u koracima od 8
byte_h = int(coded_array_h[i:i+8], 2) # Konvertovanje 8-bitnih delova niza u bajtove
byte_array_h.append(byte_h) # Dodavanje bajta u niz bajtova
with open(self.huffman_encoded_filename, "wb") as f: # Otvaranje fajla za zapisivanje u binarnom režimu
node_info_h = huffman.save_string_nodes(huffman.huffman_root) # Dobijanje niza Huffmanovih čvorova kao stringa
f.write(node_info_h.encode("utf-8")) # Zapisivanje čvorova u fajl
f.write(b"\n") # Zapisivanje novog reda
f.write(byte_array_h) # Zapisivanje niza bajtova u fajl
print("\nDEKODIRANJE HUFFMAN:\n")
symbols_binary_h = Huffman.load_first_line_binary_Huffman(self.huffman_encoded_filename) # Učitavanje prvog reda iz binarnog fajla
decoded_content_h = Huffman.load_rest_binary_Huffman(self.huffman_encoded_filename, lengthHuffman) # Učitavanje ostatka binarnog fajla
for symbol in symbols_binary_h: # Iteracija kroz učitane simbole
print(f"Simbol: {symbol.value} Kod: {symbol.code}") # Ispisivanje simbola i njihovih kodova
decoded_array_h = Huffman.decode_Huffman(decoded_content_h, symbols_binary_h) # Dekodiranje sadržaja korišćenjem Huffman algoritma
Huffman.save_decoded_output(decoded_array_h, "decoded_huffman.bin") # Zapisivanje dekodiranog sadržaja u fajl
if Huffman.compare_files("brojevi.txt","decoded_huffman.bin"): # Poređenje originalnog i dekodiranog fajla
print("\nHuffman: Fajlovi su isti.")
else:
print("\nHuffman: Fajlovi nisu isti.")
"""
LZ77:
Za postupak kompresije neophodno je podesiti odgovarajući prozor za pretragu (koji sadrži već viđene simbole)
i koristi se za pronalaženje najdužeg podudaranja sa trenutnim simbolima u prozoru.
Takođe kodiramo dužinu podudaranja, udaljenost od trenutne pozicije i sledeći simbol, kodirane podatke pišemo u binarnu datoteku.
Za postupak dekompresije neophodno je da iz binarne datoteke pročitamo dužinu podudaranja, udaljenost i sledeći simbol,
na osnovu kodiranih podataka, rekonstruiše se originalni niz ponavljanjem podudaranja i dodavanjem sledećih simbola.
"""
print("\nLZ77 ALGORITAM: (Aproksimativno vreme izvrsavanja programa je oko 10 minuta.)\n")
# Otvaramo ulazni fajl direktno koristeći ime fajla prosleđeno konstruktoru
with open(self.entropy_filename, 'rb') as f:
dataLZ77 = f.read()
# Inicijalizujemo LZ77 instancu i kompresujemo podatke
lz77 = LZ77(self.window_size)
compressed_output = lz77.compress(dataLZ77.decode('utf-8'))
# Pišemo kompresovani izlaz u binarni fajl
lz77.write_to_binary_file(compressed_output, self.lz77_encoded_filename)
# Čitamo kompresovane podatke iz binarnog fajla i dekompresujemo ih
encoded_data = LZ77.read_from_binary_file(self.lz77_encoded_filename)
decompressed_output = LZ77.decompress(encoded_data)
# Definišemo ime za dekompresovani fajl
decoded_LZ77 = 'decoded_LZ77.bin'
# Pišemo dekompresovani izlaz u fajl
LZ77.save_decompressed_output(decompressed_output, decoded_LZ77)
# Poređenje originalnog i dekompresovanog fajla
if LZ77.compare_files(self.entropy_filename, decoded_LZ77):
print("\nFajlovi imaju isti sadržaj.")
else:
print("\nFajlovi nemaju isti sadržaj.")
"""
LZW:
Za postupak kompresije neophodno je inicijalizovati rečnik sa svim pojedinačnim simbolima ulaznog niza, zatim
tražimo najduže podudaranje trenutnog podniza sa rečnikom, i dodajemo novi podniz u rečnik, indekse podudaranja
pišemo u binarnu datoteku.
Za postupak dekompresije neophodno je pročitati kodove iz binarne datoteke i prevesti u odgovarajuće podnizove iz rečnika,
zatim nove podnizove dodajemo u rečnik tokom dekodiranja.
"""
print("\nLZW ALGORITAM:\n")
# Otvaramo ulazni fajl direktno koristeći ime fajla prosleđeno konstruktoru
with open(self.entropy_filename, 'rb') as fajl:
dataLZW = fajl.read()
# Inicijalizujemo LZW instancu i kompresujemo podatke
lzw = LZW()
compressed_output, _ = lzw.encode(dataLZW.decode('utf-8'))
# Pišemo kompresovani izlaz u binarni fajl
lzw.write_to_binary_file(compressed_output, self.lzw_encoded_filename)
# Čitamo kompresovane podatke iz binarnog fajla i dekompresujemo ih
symbols, indices = LZW.read_from_binary_file(self.lzw_encoded_filename)
decompressed_output = LZW.decode(symbols, indices)
# Definišemo ime za dekompresovani fajl
decoded_LZW = 'decoded_LZW.bin'
# Pišemo dekompresovani izlaz u fajl
LZW.save_decompressed_output(decompressed_output, decoded_LZW)
# Poređenje originalnog i dekompresovanog fajla
if LZW.compare_files(self.entropy_filename, decoded_LZW):
print("\nFajlovi imaju isti sadržaj.")
else:
print("\nFajlovi nemaju isti sadržaj.")
"""
Stepen kompresije je mera koliko je veličina podataka smanjena tokom procesa kompresije.
Korisno je znati stepen kompresije za procenu efikasnosti kompresionog algoritma, odnosno koliko je algoritam uspešan
u smanjenju količine podataka. Racunamo ga prema sledećoj formuli:
kompresioni_odnos = Originalna_veličina_podataka / Kompresovana_veličina_podataka
"""
print("\nSTEPEN KOMPRESIJE:\n")
CompressionMetrics.calculate_compression_shannon_fano(self.entropy_filename, self.shannon_fano_encoded_filename) # Računanje i ispisivanje stepena kompresije za Shannon-Fano
CompressionMetrics.calculate_compression_huffman(self.entropy_filename, self.huffman_encoded_filename) # Računanje i ispisivanje stepena kompresije za Huffman
CompressionMetrics.calculate_compression_lz77(self.entropy_filename, self.lz77_encoded_filename) # Računanje i ispisivanje stepena kompresije za LZ77
CompressionMetrics.calculate_compression_lzw(self.entropy_filename, self.lzw_encoded_filename) # Računanje i ispisivanje stepena kompresije za LZW
if __name__ == "__main__": # Provera da li je skripta pokrenuta kao glavni program
main_program = Main("brojevi.txt", "encoded_shanon_fano.bin", "encoded_huffman.bin", "encoded_lz77.bin", 1000, "encoded_lzw.bin") #Pozivamo konstruktor Main klase i saljemo potrebne podatke
main_program.run() #pokretanje celokupnog programa
\ No newline at end of file
class Node:
def __init__(self, probability, symbol, left=None, right=None):
self.probability = probability
self.symbol = symbol
self.left = left
self.right = right
self.code = ''
\ No newline at end of file
from Symbol import Symbol # Uvozimo klasu Symbol iz modula Symbol
from UcitaniSimboli import UcitaniSimbol # Uvozimo klasu UcitaniSimbol iz modula UcitaniSimbol
import filecmp # Uvozimo modul filecmp za poređenje fajlova
class ShannonFano: # Definišemo klasu ShannonFano
def __init__(self, chr_array, probability_array): # Konstruktor klase
self.symbols = [Symbol(k, probability_array[k]) for k in chr_array] # Kreiramo listu simbola sa verovatnoćama
self.symbols.sort(key=lambda x: x.probability, reverse=True) # Sortiramo simbole po verovatnoći u opadajućem redosledu
self.coding_shanon_fano(self.symbols) # Pokrećemo proces kodiranja Shannon-Fano algoritmom
def coding_shanon_fano(self, symbols): # Funkcija za kodiranje Shannon-Fano algoritmom
if len(symbols) == 1: # Ako imamo samo jedan simbol, završavamo funkciju
return
probability_sum = sum(symbol.probability for symbol in symbols) # Računamo ukupnu verovatnoću
current_sum = 0 # Inicijalizujemo trenutnu sumu na 0
differences = [] # Lista za razlike između suma
for i, symbol in enumerate(symbols): # Iteriramo kroz sve simbole
current_sum += symbol.probability # Dodajemo verovatnoću trenutnog simbola na trenutnu sumu
reminder = probability_sum - current_sum # Računamo ostatak
differences.append(round(abs(current_sum - reminder), 3)) # Dodajemo apsolutnu vrednost razlike u listu
division_element = min(differences) # Nalazimo najmanju razliku
split_index = differences.index(division_element) # Nalazimo indeks elementa sa najmanjom razlikom
for i in range(len(symbols)): # Iteriramo kroz sve simbole
symbols[i].code += "0" if i <= split_index else "1" # Dodeljujemo kodove "0" ili "1" na osnovu indeksa
self.coding_shanon_fano(symbols[:split_index + 1]) # Rekurzivno pozivamo funkciju za levu polovinu
self.coding_shanon_fano(symbols[split_index + 1:]) # Rekurzivno pozivamo funkciju za desnu polovinu
def print_symbols(self): # Funkcija za ispis kodova simbola
for symbol in self.symbols: # Iteriramo kroz sve simbole
print(f"Simbol: {symbol.value} Kod: {symbol.code}") # Ispisujemo vrednost i kod simbola
def encode_array(self, filename): # Funkcija za kodiranje niza
code_array = "" # Inicijalizujemo prazan string za kodirani niz
with open(filename, "r", encoding="utf-8") as f: # Otvaramo fajl za čitanje
data = f.read() # Čitamo podatke iz fajla
for symbol in data: # Iteriramo kroz svaki simbol u podacima
for s in self.symbols: # Iteriramo kroz sve simbole u kodiranju
if s.value == symbol: # Ako vrednost simbola odgovara simbolu u podacima
code_array += s.code # Dodajemo kod simbola u kodirani niz
break # Prekidamo unutrašnju petlju
return code_array # Vraćamo kodirani niz
def save_string_symbols(self): # Funkcija za čuvanje informacija o simbolima kao string
symbol_info = "" # Inicijalizujemo prazan string za informacije o simbolima
for symbol in self.symbols: # Iteriramo kroz sve simbole
symbol_info += f"{symbol.value}:{symbol.code} " # Dodajemo vrednost i kod simbola u string
return symbol_info # Vraćamo string sa informacijama o simbolima
@staticmethod
def load_first_line_from_binary_Shannon_Fano(path): # Funkcija za učitavanje prvog reda iz binarnog fajla
with open(path, 'rb') as fajl: # Otvaramo fajl za čitanje u binarnom modu
first_line = fajl.readline().decode('utf-8').strip() # Čitamo prvi red i dekodiramo ga
symbols_only = first_line.split() # Delimo red na pojedinačne simbole
symbols_array = [] # Inicijalizujemo praznu listu za simbole
for symbol_only in symbols_only: # Iteriramo kroz svaki simbol
value, code = symbol_only.split(":") # Delimo simbol na vrednost i kod
symbol = UcitaniSimbol(value, code) # Kreiramo objekat UcitaniSimbol sa vrednošću i kodom
symbols_array.append(symbol) # Dodajemo simbol u listu
return symbols_array # Vraćamo listu simbola
@staticmethod
def load_rest_binary_Shannon_Fano(path, lengthShannonFano): # Funkcija za učitavanje ostatka binarnog fajla
rest = lengthShannonFano % 8 # Računamo ostatak pri deljenju dužine sa 8
with open(path, 'rb') as f: # Otvaramo fajl za čitanje u binarnom modu
f.readline() # Preskačemo prvi red
data = f.read() # Čitamo ostatak fajla
bit_array = [] # Inicijalizujemo praznu listu za bitove
for b in data: # Iteriramo kroz svaki bajt u podacima
bites = bin(b)[2:].rjust(8, '0') # Pretvaramo bajt u binarni string sa popunom od 0
bit_array.extend(map(int, bites)) # Dodajemo bitove u listu
important_string = ''.join(map(str, bit_array)) # Pretvaramo listu bitova u string
if rest > 0: # Ako postoji ostatak
part = 8 - rest # Računamo deo koji nedostaje
help_array = important_string[-8:] # Uzimamo poslednjih 8 bitova
important_string = important_string[:-8] # Uklanjamo poslednjih 8 bitova iz glavnog stringa
help_array = help_array[part:] # Smanjujemo pomoćni niz za deo koji nedostaje
important_string += help_array # Dodajemo modifikovani pomoćni niz nazad u glavni string
return important_string # Vraćamo konačan string bitova
@staticmethod
def decode_shanon_fano(encoded_array, symbols): # Funkcija za dekodiranje niza kodova
decoded_output = "" # Inicijalizujemo prazan string za dekodirani izlaz
current_code = "" # Inicijalizujemo prazan string za trenutni kod
for bit in encoded_array: # Iteriramo kroz svaki bit u kodiranom nizu
current_code += bit # Dodajemo bit trenutnom kodu
for symbol in symbols: # Iteriramo kroz sve simbole
if current_code == symbol.code: # Ako trenutni kod odgovara kodu simbola
decoded_output += symbol.value # Dodajemo vrednost simbola u dekodirani izlaz
current_code = "" # Resetujemo trenutni kod
break # Prekidamo unutrašnju petlju
return decoded_output # Vraćamo dekodirani izlaz
@staticmethod
def save_decoded_output(decoded_output, filename): # Funkcija za čuvanje dekodiranog izlaza u fajl
with open(filename, 'wb') as f: # Otvaramo fajl za pisanje u binarnom modu
f.write(decoded_output.encode('utf-8')) # Pišemo dekodirani izlaz u fajl
@staticmethod
def compare_files(file1, file2): # Funkcija za poređenje dva fajla
return filecmp.cmp(file1, file2) # Vraćamo rezultat poređenja fajlova koristeći funkciju cmp iz modula filecmp
import os # Uvoz os modula za rad sa fajl sistemom
class CompressionMetrics: # Definisanje klase za metrike kompresije
@staticmethod
def calculate_compression_ratio(original_file, compressed_file): # Statička metoda za računanje stepena kompresije
original_size = os.path.getsize(original_file) # Dobijanje veličine originalnog fajla u bajtovima
compressed_size = os.path.getsize(compressed_file) # Dobijanje veličine kompresovanog fajla u bajtovima
compression_ratio = original_size / compressed_size # Izračunavanje stepena kompresije kao odnos veličina
return compression_ratio # Vraćanje izračunatog stepena kompresije
@staticmethod
def calculate_compression_shannon_fano(original_file, compressed_file): # Statička metoda za računanje stepena kompresije Shannon-Fano algoritmom
print("Shannon-Fano stepen kompresije:")
ratio = CompressionMetrics.calculate_compression_ratio(original_file, compressed_file) # Računanje stepena kompresije korišćenjem ranije definisane metode
print(f"Velicina originalnog fajla: {os.path.getsize(original_file)} bajta")
print(f"Velicina kompresovanog fajla: {os.path.getsize(compressed_file)} bajta")
print(f"Stepen kompresije: {ratio:.2f}")
print(f"--------------------------------------")
@staticmethod
def calculate_compression_huffman(original_file, compressed_file): # Statička metoda za računanje stepena kompresije Huffman algoritmom
print("Huffman stepen kompresije:")
ratio = CompressionMetrics.calculate_compression_ratio(original_file, compressed_file) # Računanje stepena kompresije korišćenjem ranije definisane metode
print(f"Velicina originalnog fajla: {os.path.getsize(original_file)} bajta")
print(f"Velicina kompresovanog fajla: {os.path.getsize(compressed_file)} bajta")
print(f"Stepen kompresije: {ratio:.2f}")
print(f"--------------------------------------")
@staticmethod
def calculate_compression_lz77(original_file, compressed_file): # Statička metoda za računanje stepena kompresije LZ77 algoritmom
print("LZ77 stepen kompresije:")
ratio = CompressionMetrics.calculate_compression_ratio(original_file, compressed_file) # Računanje stepena kompresije korišćenjem ranije definisane metode
print(f"Velicina originalnog fajla: {os.path.getsize(original_file)} bajta")
print(f"Velicina kompresovanog fajla: {os.path.getsize(compressed_file)} bajta")
print(f"Stepen kompresije: {ratio:.2f}")
print(f"--------------------------------------")
@staticmethod
def calculate_compression_lzw(original_file, compressed_file): # Statička metoda za računanje stepena kompresije LZW algoritmom
print("LZW stepen kompresije:")
ratio = CompressionMetrics.calculate_compression_ratio(original_file, compressed_file) # Računanje stepena kompresije korišćenjem ranije definisane metode
print(f"Velicina originalnog fajla: {os.path.getsize(original_file)} bajta")
print(f"Velicina kompresovanog fajla: {os.path.getsize(compressed_file)} bajta")
print(f"Stepen kompresije: {ratio:.2f}")
print(f"--------------------------------------")
\ No newline at end of file
class Symbol:
def __init__(self,value, probability):
self.value = value
self.probability = probability
self.code = ""
\ No newline at end of file
class UcitaniSimbol:
def __init__(self, value, code):
self.value = value
self.code = code
\ No newline at end of file
File added
File added
File added
File added
File added
File added
File added
File added
File added
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment