Этот сайт — моя персональная записная книжка. Интересны мне, по большей части, программирование, история и события из моей жизни.

Какой RLE лучше?

У меня тут в комментариях на сайте интересная дискуссия развернулась — есть ли преимущество у реализации RLE, которую я описывал, по сравнению с другим алгоритмом, который вспомнил один из читателей.

Для меня вполне очевидно, что на разных данных каждый из них может выиграть. Но что насчёт реальной жизни?

Я написал небольшую программу на Пайтоне, которая считает сколько занял бы файл, если его сжимать каждым из алгоритмов, а потом прогнал её на каждой из 7000+ файлов в моей папке «Загрузки».

Какой же итог? В 1811 случаях выиграл алгоритм, который вспомнил я, в 2152 — который вспомнил читатель, в 3193 случаях оба алгоритма сделали только хуже — файл стал длиннее.

В итоге, если бы мы писали утилиту для сжатия, можно было бы использовать все три метода (включая сохранение файла в исходном виде), указав в начале файла кодовый байт означающий метод сжатия.

from itertools import groupby
from math import ceil
import os
import sys

try:
    filename = sys.argv[1]
except IndexError:
    filename = __file__

def reader(filepath):
    with open(filepath, "rb") as f:
        while chunk := f.read(4096): yield from chunk

def mark(x, y):
    if x == y: return (x, y)

    c1, c2 = (32, 31) if x < y else (31, 32)
    return (f'\x1B[{c1}m{x:+d}', f'\x1B[{c2}m{y:+d}')

flen = os.path.getsize(filename)

grouped_same = (tuple(x[1]) for x in groupby(reader(filename)))
chains = groupby(grouped_same, lambda it: len(it) == 1 or object())

bolk = maks = 0

for diff, x in chains:
    x = tuple(x) if diff is True else tuple(x)[0]
    l = len(x)

    if diff is True:
        bolk  += l                       #                {x} {y} {z}…
        maks  += l + ceil(l / (128 + 1)) # {l & 0x7F - 1} {x} {y} {z}…
    else:
        bolk  += (2 + 1) * ceil(l / (255 + 2)) # {x}            {x} {l}
        maks  += (1 + 1) * ceil(l / (128 + 2)) # {l & 0x7F - 2} {x}


bdiff, mdiff = mark(bolk - flen, maks - flen)

print(f'Len\t{flen}')
print(f"Bolk's\t{bolk}\t{bdiff}\x1B[0m")
print(f"Maks'\t{maks}\t{mdiff}\x1B[0m")

Добавлено позднее: в комментариях есть замечания к реализации, которые я учёл позднее.

4 комментария
Макс 4 мес

Риспект за дотошность) тут она уместна

Евгений Степанищев 4 мес

👍

Макс 3 мес

Позанудствую. В моем алгоритме есть вариативность в том какие последовательности упаковывать, а какие нет. Например:
11 11 22 22 33 33
прямолинейно по твоей реализации «моего» алгоритма кодирует так:
01 11 11 01 22 22 01 33 33
но короче можно и так записать:
85 11 11 22 22 33 33
твой вариант:
11 11 00 22 22 00 33 33 00

Т.е в моем варианте плохие последовательности можна втупую не кодировать и по сути разбить на незакодированные блоки с префиксом 7f. Тогда файл увеличится на 1/128 в самом худшем случае. А в твоем худшем случае на 50%

Евгений Степанищев 3 мес

Хм, похоже это действительно стоило бы учесть.

Макс 3 мес

Акт занудства 2: возможно твой алгоритм так много побеждал из-за того, что у него максимальна длина последовательности 255. Однако ничто не запрещает сделать ее гибкой, например при ff считать что за ним будут идти 2 байта на длину последовательности. Ну типа того как утф8 построен

Евгений Степанищев 3 мес

Ну конечно, как и в том алгоритме, который я вспомнил. Можно сделать флаг когда мы кодируем двумя байтами.

Макс 3 мес

Зануда мод: ну да, я и имел ввиду, что после введение данного расширения, по данному параметру твой алгоритм уже не будет иметь преимущества по длине последовательности и будет ещё больше проигрывать в целом.

Зато поддержал активность в комментариях)