Bionic Reader
This page walks through a fifty-eight character Perl one-liner that bolds the first forty percent of each word to mimic bionic reading. You will see how Perl splits input on spaces, counts characters, and wraps the result in terminal escape codes.Whether the reading speed claim holds up is still up for debate, but building it in Perl is a clean exercise in text processing.
Fifty-eight characters. The script reads any plain text file and prints the words back to your terminal with the leading characters bolded.perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[1m$&\033[0m~' file.txt
The output looks like this (bold shown in brackets for clarity):
Your eyes lock onto the bold prefix, and your brain fills in the rest of the word. It might be a neat parlor trick, but the underlying mechanics are solid.[Th]e [su]n [wa]s [shi]ning [bri]ghtly [as] [Ali]ce [wal]ked...
Part 1: THE SWITCHES
Three command line flags handle the heavy lifting here:perl -040pE
TheSWITCH WHAT IT DOES ------ -------------------------------------------------- -0 Set the input record separator (what splits input) 40 Octal 40 = ASCII 32 = space character -p Read input, run code, print result (implicit loop) -E Enable modern features (like say)
-040 flag is the clever part. Perl normally reads text line by line, which means it splits the input on newlines. Setting -0 to octal 40 changes the split point to a space character.
Each "line" that -p processes is actually a single word.
Part 2: THE LENGTH CALCULATION
This line packs a lot of logic into a small space. Let's untangle it from the inside out:$l=($l=y///c)>2?int$l*.4:1
ThePIECE WHAT IT DOES ----------- -------------------------------------------- y///c Count characters in $_ $l=y///c Store the count in $l ($l=...)>2 Is the length greater than 2? int$l*.4 Yes: take 40% of length (truncated) 1 No: just use 1 $l=... Store final result back in $l
y///c trick deserves its own explanation.
Part 3: THE COUNTING TRICK
The y/// operator (also called tr///) normally performs transliteration. With empty search and replace lists plus the /c flag, it becomes a character counter.y///c
When both lists are empty, every character "matches." They/abc/xyz/ Replace a->x, b->y, c->z y/// Replace nothing with nothing (no-op) y///c Count how many "nothings" matched (all chars)
/c flag returns the total count instead of performing a replacement.
It is the shortest way to get string length in a one-liner:
In code golf,y///c # Returns length of $_ length # Same thing, more readable, more typing
y///c wins.
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
Part 4: THE TERNARY LOGIC
A word by word breakdown:($l=y///c)>2 ? int$l*.4 : 1
Why the threshold? Short words like "a" or "be" do not need the bionic treatment. One bold character is enough for them. Longer words get proportional bolding.IF the word is longer than 2 characters Bold 40% of it (rounded down) ELSE Bold just 1 character
TheWORD LENGTH 40% BOLD CHARS ------ ------ ---- ---------- a 1 0.4 1 (minimum) be 2 0.8 1 (minimum) the 3 1.2 1 word 4 1.6 1 hello 5 2.0 2 reading 7 2.8 2 beautiful 9 3.6 3
int() function truncates the decimal. No rounding happens, just a clean chop.
Part 5: THE SUBSTITUTION
This wraps the firsts~.{$l}~\033[1m$&\033[0m~
$l characters in ANSI bold codes:
ThePIECE WHAT IT DOES ----------- -------------------------------------------- s~~~ Substitution (~ delimiters) .{$l} Match exactly $l characters (any char) \033[1m ANSI escape: turn bold ON $& The matched text (first $l chars) \033[0m ANSI escape: reset formatting
.{ $l } quantifier is the key. It matches exactly as many characters as we calculated. It is not greedy, and it is not minimal. It just matches that exact count.
Part 6: ANSI ESCAPE CODES
These are terminal control sequences.\033[1m Bold on \033[0m Reset (all formatting off)
\033 is the escape character (octal 33 equals decimal 27 equals ESC).
When your terminal sees ESC[1m, it starts rendering bold text. When it sees ESC[0m, it returns to normal formatting.
Other codes you might use:
The bionic reader uses just bold, but you could get creative with the rest.\033[4m Underline \033[7m Reverse video \033[31m Red text \033[32m Green text \033[1;31m Bold red
Part 7: PUTTING IT TOGETHER
Let's trace through the word "reading":The space gets included in the count (8 characters instead of 7), but it does not affect the visual much. Whitespace in bold looks the same as normal whitespace.INPUT: "reading " (note trailing space from -040) STEP 1: y///c counts 8 characters STEP 2: 8 > 2, so $l = int(8 * 0.4) = 3 STEP 3: s~.{3}~ matches "rea" STEP 4: Replacement wraps it: \033[1mrea\033[0m STEP 5: -p prints: "\033[1mrea\033[0mding " OUTPUT: "reading " with "rea" bold
Part 8: THE EXPANDED VERSION
The same logic runs here, but it takes twenty times the characters. The one-liner still wins on density.#!/usr/bin/env perl use feature qw|say|; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set word-by-word input mode # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # $/ is input record separator # Space character makes each "line" a word $/ = ' '; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Process each word # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ while (<>) { # Count characters in current word my $length = y///c; # Calculate how many chars to bold # 40% for words > 2 chars, else just 1 my $bold_count = $length > 2 ? int($length * 0.4) : 1; # Wrap first N characters in ANSI bold s~.{$bold_count}~\033[1m$&\033[0m~; # Print the modified word print; }
Part 9: VARIATIONS
Bold 50% instead of 40%:Underline instead of bold:perl -040pE'$l=($l=y///c)>2?int$l*.5:1;s~.{$l}~\033[1m$&\033[0m~' file
Colored (red) instead of bold:perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[4m$&\033[0m~' file
HTML output instead of terminal:perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[31m$&\033[0m~' file
That last one generates HTML you can paste anywhere.perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~<b>$&</b>~' file
Part 10: DOES IT ACTUALLY WORK?
Bionic reading is still controversial. Some studies show improvement, while others show no measurable difference. Your mileage will vary.But as a Perl exercise, it covers a lot of ground:
All of that fits into fifty-eight characters.* -0 flag for custom record separators * y///c as a character counter * Nested assignment in a ternary * **Dynamic quantifiers** in regex * ANSI escape sequences * The -p implicit loop
Whether it helps you read faster is beside the point. It definitely helps you appreciate Perl's density.
perl.gg_____ | | | TXT | |_____| | V .-----. / \ | PERL | \ / '-----' | V _______ | | | [BI]O | | [NI]C | |_______|