╔╗ ╦╔═╗╔╗╔╦╔═╗ ╦═╗╔═╗╔═╗╔╦╗╔═╗╦═╗ ╠╩╗║║ ║║║║║║ ╠╦╝║╣ ╠═╣ ║║║╣ ╠╦╝ ╚═╝╩╚═╝╝╚╝╩╚═╝ ╩╚═╚═╝╩ ╩═╩╝╚═╝╩╚═ ============================================================================ Bionic reading. The idea is simple: bold the first part of each word and your brain fills in the rest. Supposedly speeds up comprehension. Whether it works is debatable. But implementing it in Perl? That's just fun. perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[1m$&\033[0m~' file.txt 58 characters. Processes any text file. Outputs bolded words to your terminal. The result looks like this (bold shown in brackets): [Th]e [su]n [wa]s [shi]ning [bri]ghtly [as] [Ali]ce [wal]ked... Your eyes grab the bold prefix, your brain autocompletes the word. Magic? Snake oil? Either way, it's a neat one-liner. ============================================================================ PART 1: THE SWITCHES -------------------- perl -040pE Three switches doing heavy lifting: SWITCH 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) The -040 is the clever bit. Normally Perl reads line by line (split on newlines). Setting -0 to octal 40 makes it read word by word (split on spaces). Each "line" that -p processes is actually a single word. ============================================================================ PART 2: THE LENGTH CALCULATION ------------------------------ $l=($l=y///c)>2?int$l*.4:1 This is dense. Let's untangle it from the inside out: PIECE 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 The y///c trick deserves its own explanation. ============================================================================ PART 3: THE COUNTING TRICK -------------------------- y///c The y/// operator (also called tr///) does transliteration. But with empty search and replace lists plus the /c flag, it becomes a counter. y/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) When both lists are empty, every character "matches." The /c flag returns the count instead of doing replacement. It's the shortest way to get string length in a one-liner: y///c # Returns length of $_ length # Same thing, more readable, more typing In code golf, y///c wins. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 4: THE TERNARY LOGIC ------------------------- ($l=y///c)>2 ? int$l*.4 : 1 A word-by-word breakdown: IF the word is longer than 2 characters Bold 40% of it (rounded down) ELSE Bold just 1 character Why the threshold? Short words like "a" or "be" don't need the bionic treatment. One bold character is enough. Longer words get proportional bolding. WORD 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 The int() truncates. No rounding, just chop off the decimal. ============================================================================ PART 5: THE SUBSTITUTION ------------------------ s~.{$l}~\033[1m$&\033[0m~ This wraps the first $l characters in ANSI bold codes: PIECE 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 The .{$l} quantifier is key. It matches exactly as many characters as we calculated. Not greedy, not minimal, just that exact count. ============================================================================ PART 6: ANSI ESCAPE CODES ------------------------- \033[1m Bold on \033[0m Reset (all formatting off) These are terminal control sequences. \033 is the escape character (octal 33 = decimal 27 = ESC). When your terminal sees ESC[1m, it starts rendering bold text. When it sees ESC[0m, it goes back to normal. Other codes you might use: \033[4m Underline \033[7m Reverse video \033[31m Red text \033[32m Green text \033[1;31m Bold red The bionic reader uses just bold, but you could get creative. ============================================================================ PART 7: PUTTING IT TOGETHER --------------------------- Let's trace through the word "reading": 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 The space gets included in the count (8 chars not 7) but doesn't affect the visual much. Whitespace in bold looks the same. ============================================================================ PART 8: THE EXPANDED VERSION ---------------------------- #!/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; } Same logic, twenty times the characters. The one-liner wins. ============================================================================ PART 9: VARIATIONS ------------------ Bold 50% instead of 40%: perl -040pE'$l=($l=y///c)>2?int$l*.5:1;s~.{$l}~\033[1m$&\033[0m~' file Underline instead of bold: perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[4m$&\033[0m~' file Colored (red) instead of bold: perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~\033[31m$&\033[0m~' file HTML output instead of terminal: perl -040pE'$l=($l=y///c)>2?int$l*.4:1;s~.{$l}~$&~' file That last one generates HTML you can paste anywhere. ============================================================================ PART 10: DOES IT ACTUALLY WORK? ------------------------------- Bionic reading is controversial. Some studies show improvement, others show nothing. YMMV. But as a Perl exercise? It's gold: * -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 All packed into 58 characters. Whether it helps you read faster is beside the point. It definitely helps you appreciate Perl's density. ============================================================================ _____ | | | TXT | |_____| | V .-----. / \ | PERL | \ / '-----' | V _______ | | | [BI]O | | [NI]C | |_______| ============================================================================ japh.codes