Data Compression: Lossless vs Lossy, with Run-Length Encoding
Compression rewrites data so it takes fewer bits, which means it fits in less storage and travels across a network faster. Almost every photo, song, video and app download on your phone is compressed. The trick is to spot patterns in the data and describe them more briefly, and the big decision is whether you need every bit back exactly.
What does compression do?
Data compression reduces the size of data, measured as a number of bits, before it is stored or sent. A smaller file leaves more room on your phone and downloads in less time. A four-minute song stored as raw sound samples takes about 40 megabytes; as a typical music file it takes about 4, a tenth of the space. The other half of the job is decompression: turning the compressed version back into data an app can use.
Here is one row of pixels from a pixel-art game, where W is a white pixel and B is a black one:
WWWWWWWWWWBBBBBWWWWWThat is 20 symbols. You could describe the same row as "10 white, then 5 black, then 5 white", or 10W5B5W, which is 7 symbols. Nothing is missing: anyone who knows the rule can rebuild the row exactly. Fewer bits does not necessarily mean less information. The short version says the same thing in less space.
How does run-length encoding work?
The pixel row compresses well because it is full of redundancy: parts of the data repeat or are easy to predict. Ten W's in a row carry little more information than "ten W's".
Run-length encoding (RLE) uses exactly that kind of redundancy. A run is a stretch of the same symbol repeated. RLE replaces each run with its length and its symbol. The encoder below walks through the text one symbol at a time. If the symbol matches the current run, the run gets longer; if not, a new run starts.
[[10, 'W'], [5, 'B'], [5, 'W']] 10W5B5W
This rle_encode(text) should return the run-length code of text as a string: the count followed by the symbol, for every run. For 'AAAABBC' it should return '4A2B1C', but it returns '4A2B'. Find the bug and fix it.
The text never contains digits.
How much space does compression save?
To measure a compression algorithm, compare the size before and after. The program below counts characters; each one takes the same number of bits, so the percentage saved is the same either way. It tries RLE on three kinds of data: a row of solid sky, a row across a striped flag, and a checkerboard row where the color changes at every pixel.
sky 50 -> 3 characters (94% saved) striped flag 45 -> 9 characters (80% saved) checkerboard 40 -> 80 characters (-100% saved)
Write percent_saved(original, compressed). Both arguments are strings, and size is measured in characters. Return the percentage of the original size that compression saved, rounded to 1 decimal place. If the compressed version is bigger, the result is negative.
percent_saved('AAAAAAAAAA', '10A') -> 70.0Does the algorithm matter as much as the data?
How much compression saves depends on two things: how much redundancy the original data has, and which compression algorithm is applied. RLE only notices symbols repeated side by side. Other algorithms look for longer patterns, such as a word that shows up again and again.
Python includes zlib, which uses the same method (called DEFLATE) as ZIP files and PNG images. One of its ideas is to replace a repeat of something it has already seen with a short note that points back to it. In go team go! go team go!, the second go team go! becomes something like "go back 12 characters and copy 11". The note is much shorter than the text it replaces.
Here are both algorithms on the same three pieces of data. Sizes are in bytes, and zlib's results include a few bytes it always adds to describe its own format.
data original RLE zlib striped flag 45 9 17 checkerboard 40 80 13 team chant 240 480 23
RLE wins on the flag, where the data is a few long runs. zlib wins easily on the checkerboard and the chant, because it notices that BW and go team go! repeat, even though no symbol repeats next to itself. Picking the best algorithm means knowing what your data looks like.
Can you compress a file twice?
If compression saves space once, it is tempting to run it again on its own output. Try it:
original: 240 bytes compressed once: 23 bytes compressed twice: 32 bytes
The second pass makes the data bigger. The first pass already removed the redundancy, so the output looks almost random, and there is nothing left for the second pass to describe more briefly. This is why putting photos or videos in a ZIP file barely shrinks them: those formats are compressed already.
What is the difference between lossless and lossy compression?
Lossless compression algorithms can usually reduce the number of bits stored or sent while guaranteeing that the original can be rebuilt exactly, bit for bit. RLE and zlib are both lossless, and so are ZIP files and PNG images. Decoding RLE is a matter of repeating each symbol by its count:
WWWWWWWWWWBBBBBWWWWW Exact copy of the original: True
Write rle_decode(code), which takes run-length code written as a string, such as '4A2B1C', and returns the original text, 'AAAABBC'. A count can have more than one digit ('12W' means twelve W's), and the original text never contains digits.
Lossy compression algorithms throw some detail away on purpose. They can reduce the number of bits significantly, usually much more than lossless algorithms can, but they only let you rebuild an approximation of the original. Photos (JPEG), music (MP3) and streaming video use lossy compression.
Good lossy formats choose what to throw away by how people see and hear. Photo formats keep changes in brightness sharper than changes in color, because eyes notice brightness more. Music formats drop very high pitches that most people cannot hear, and quiet sounds that a louder sound at the same moment would cover up. Most of the time you cannot tell, until the compression is pushed too far and photos turn blocky or music sounds watery.
The demo below shows the idea on eight pixel brightness values from 0 to 255, which take 8 bits each. It keeps only which of 8 brightness bands each value falls in, which takes 3 bits.
original: [12, 15, 14, 200, 203, 201, 90, 88] stored: [0, 0, 0, 6, 6, 6, 2, 2] restored: [16, 16, 16, 208, 208, 208, 80, 80] size: 64 bits -> 24 bits largest error: 10
The file shrank by more than half, and the restored values are close. But 12, 15 and 14 all became 16, and nothing in the stored data can tell them apart again. That loss is permanent.
Which kind of compression should you choose?
Ask what matters most in the situation.
To compare specific algorithms for a job, three questions settle most cases. Must the original come back exactly? How small does the data need to be, and how fast must it travel? What does the data look like: long runs, repeated words, or natural photos and sound?
Take a drawing app that saves pixel-art sprites. Artists edit the same sprite for weeks, so every pixel must survive (lossless), and the sprites have big areas of one color, so a run-based method like RLE does well. Now take a doorbell camera that streams live video to a phone over a cell network. The owner needs to see who is at the door right now, not every leaf on the hedge, so a lossy video format is the better fit.
| Situation | Usual choice | Why |
|---|---|---|
| Sending an app update | Lossless | Every bit of the program must arrive unchanged |
| A spreadsheet of test scores | Lossless | Numbers must be exact |
| A live video call on slow Wi-Fi | Lossy | Speed matters more than perfect detail |
| Storing thousands of songs on a phone | Lossy | Saves the most space, and small losses are hard to hear |
The same photo can go both ways. A photographer might keep a lossless original for editing and post a lossy copy online.
Check your understanding
A student club keeps a spreadsheet of who has paid their dues and how much. The treasurer wants to compress it before emailing it. Which choice is best?
A video chat app needs calls to stay smooth for people on slow home connections. Which compression approach fits best?
Which of the following statements about data compression are true? Select two answers.
Select 2 answers.
Each image below is stored row by row and compressed with run-length encoding. Which image would most likely shrink the most?
The code segment below rebuilds a row of pixels from run-length data, where 0 is a white pixel and 1 is a black one. What does the list pixels contain after it runs?
counts ← [3, 1, 2]colors ← [0, 1, 0]pixels ← []i ← 1REPEAT LENGTH(counts) TIMES{REPEAT counts[i] TIMES{APPEND(pixels, colors[i])}i ← i + 1}
Two files of the same size are compressed with the same lossless algorithm. File A shrinks by 80%, and file B shrinks by only 5%. Which of the following best explains the difference?
A 9,000-bit message is compressed with a lossless algorithm to 3,000 bits. Which statement is true?
Exam topics: 2.2 Data Compression (DAT-1.D)