pyZX0/optimize.py

105 lines
4.5 KiB
Python
Raw Normal View History

from typing import List, Optional
class Block:
def __init__(self, bits=None, index=None, offset=None, chain=None):
self.chain = chain
self.bits = bits
self.index = index
self.offset = offset
INITIAL_OFFSET = 1
MAX_SCALE = 50
def offset_ceiling(index, offset_limit):
return offset_limit if index > offset_limit else (INITIAL_OFFSET if index < INITIAL_OFFSET else index)
def elias_gamma_needed_bits(value):
bits = 1
while value > 1:
value >>= 1
bits += 2
return bits
def optimize(input_data, skip, offset_limit):
# The algorithm has a floating window of size window_size describing the previous chain of matches
input_size = len(input_data)
window_size = offset_ceiling(input_size - 1, offset_limit) + 1
last_literal: List[Optional[Block]] = [None] * window_size
last_match: List[Optional[Block]] = [None] * window_size
match_length = [0] * window_size
# The algorithm is looking for the best match for each index of the input data
optimal = [None] * input_size
best_length = [0] * input_size
if input_size > 2:
best_length[2] = 2
# Kickstart the algorithm by assigning a fake block
last_match[INITIAL_OFFSET] = Block(-1, skip - 1, INITIAL_OFFSET, None)
# The algorithm is checking for the best match for each index of the input data (skipping the skip part)
for index in range(skip, input_size):
best_length_size = 2 # It's useless to check for a match of length 1
max_offset = offset_ceiling(index, offset_limit)
for offset in range(1, max_offset + 1):
# Checking for a match in the previous part of the input data, backwards
if index != skip and index >= offset and input_data[index] == input_data[index - offset]:
current_literal = last_literal[offset]
if current_literal is not None:
length = index - current_literal.index
bits = current_literal.bits + 1 + elias_gamma_needed_bits(length)
# Chain the current match to the previous literal
last_match[offset] = Block(bits, index, offset, current_literal)
# Update the best match
if not optimal[index] or optimal[index].bits > bits:
optimal[index] = last_match[offset]
match_length[offset] += 1
if match_length[offset] > 1:
if best_length_size < match_length[offset]:
bits = (optimal[index - best_length[best_length_size]].bits +
elias_gamma_needed_bits(best_length[best_length_size] - 1))
while True:
best_length_size += 1
bits2 = (optimal[index - best_length_size].bits +
elias_gamma_needed_bits(best_length_size - 1))
if bits2 <= bits:
best_length[best_length_size] = best_length_size
bits = bits2
else:
best_length[best_length_size] = best_length[best_length_size - 1]
if best_length_size >= match_length[offset]:
break
length = best_length[match_length[offset]]
bits = (optimal[index - length].bits + 8 +
elias_gamma_needed_bits((offset - 1) // 128 + 1) +
elias_gamma_needed_bits(length - 1))
if not last_match[offset] or last_match[offset].index != index or last_match[offset].bits > bits:
last_match[offset] = Block(bits, index, offset, optimal[index - length])
if not optimal[index] or optimal[index].bits > bits:
optimal[index] = last_match[offset]
else:
match_length[offset] = 0 # Resetting the match length
if last_match[offset]:
length = index - last_match[offset].index
bits = last_match[offset].bits + 1 + elias_gamma_needed_bits(length) + length * 8
last_literal[offset] = Block(bits, index, 0, last_match[offset])
if not optimal[index] or optimal[index].bits > bits:
optimal[index] = last_literal[offset]
return optimal[input_size - 1]