╔═╗╔═╗╔═╗╔═╗╔═╗╔═╗╦ ╦╦╔═╗ ╚═╗╠═╝╠═╣║ ║╣ ╚═╗╠═╣║╠═╝ ╚═╝╩ ╩ ╩╚═╝╚═╝╚═╝╩ ╩╩╩ ╔═╗╔═╗╔═╗╦═╗╔═╗╔╦╗╔═╗╦═╗ ║ ║╠═╝║╣ ╠╦╝╠═╣ ║ ║ ║╠╦╝ ╚═╝╩ ╚═╝╩╚═╩ ╩ ╩ ╚═╝╩╚═ ============================================================================ Sorting. You'll do a lot of it. And when you do, you'll need this: <=> The spaceship operator. Three characters that tell you how two numbers relate to each other. 5 <=> 10 # -1 (less than) 10 <=> 5 # 1 (greater than) 7 <=> 7 # 0 (equal) Three-way comparison. Returns -1, 0, or 1. Perfect for sort. ============================================================================ PART 1: THE BASICS ------------------ Spaceship compares two numbers and returns: -1 if left is less than right 0 if they're equal 1 if left is greater than right That's exactly what sort needs: my @nums = (5, 2, 9, 1, 7); my @sorted = sort { $a <=> $b } @nums; # (1, 2, 5, 7, 9) Without <=> you get string sorting: my @wrong = sort @nums; # (1, 2, 5, 7, 9) - looks right... my @nums = (5, 2, 19, 1, 7); my @wrong = sort @nums; # (1, 19, 2, 5, 7) - WRONG! String "19" < "2" ============================================================================ PART 2: ASCENDING VS DESCENDING ------------------------------- Swap $a and $b to reverse the order: # Ascending (smallest first) my @asc = sort { $a <=> $b } @nums; # Descending (largest first) my @desc = sort { $b <=> $a } @nums; The trick is remembering which is which. Think of it like subtraction: $a <=> $b # $a - $b: positive means a is bigger, goes later $b <=> $a # $b - $a: positive means b is bigger, b goes later .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 3: THE STRING VERSION -------------------------- Spaceship is for numbers. For strings, use cmp: 'apple' cmp 'banana' # -1 (a before b) 'banana' cmp 'apple' # 1 (b after a) 'apple' cmp 'apple' # 0 (equal) Same return values, but compares as strings. my @words = qw(cherry apple banana); my @sorted = sort { $a cmp $b } @words; # (apple, banana, cherry) Actually, for simple string sorting, you can skip the block: my @sorted = sort @words; # Same result Perl's default sort is string-based cmp. ============================================================================ PART 4: MULTI-LEVEL SORTING --------------------------- The real power shows up in complex sorts. Chain comparisons with ||: my @people = ( { name => 'Alice', age => 30 }, { name => 'Bob', age => 25 }, { name => 'Carol', age => 30 }, ); my @sorted = sort { $a->{age} <=> $b->{age} # First: by age || $a->{name} cmp $b->{name} # Then: by name } @people; Result: Bob (25), Alice (30), Carol (30) The || short-circuits: if age comparison returns 0 (equal), it falls through to the name comparison. ============================================================================ PART 5: THREE-LEVEL AND BEYOND ------------------------------ Keep chaining for more levels: my @sorted = sort { $a->{department} cmp $b->{department} || $b->{salary} <=> $a->{salary} # Descending salary || $a->{hire_date} cmp $b->{hire_date} || $a->{name} cmp $b->{name} } @employees; Each level only matters when previous levels are equal. ============================================================================ PART 6: SORTING HASH KEYS BY VALUE ---------------------------------- Classic pattern - sort keys by their corresponding values: my %scores = ( alice => 85, bob => 92, carol => 78, ); # Sort names by score (highest first) my @ranking = sort { $scores{$b} <=> $scores{$a} } keys %scores; # (bob, alice, carol) The sort block compares values, but you're sorting the keys. ============================================================================ PART 7: CUSTOM COMPARISONS -------------------------- Spaceship works with any numeric expression: # Sort by string length my @by_length = sort { length($a) <=> length($b) } @words; # Sort by last character my @by_last = sort { ord(substr($a,-1)) <=> ord(substr($b,-1)) } @words; # Sort IPs numerically my @ips = sort { my @a = split /\./, $a; my @b = split /\./, $b; $a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2] || $a[3] <=> $b[3] } @ip_addresses; ============================================================================ PART 8: THE SCHWARTZIAN TRANSFORM --------------------------------- When sorting requires expensive computation: # Slow: computes length every comparison my @sorted = sort { length($a) <=> length($b) } @strings; # Fast: compute once, sort, extract my @sorted = map { $_->[0] } # 3. Extract original sort { $a->[1] <=> $b->[1] } # 2. Sort by cached value map { [ $_, length($_) ] } # 1. Cache expensive calc @strings; Named after Randal Schwartz. The spaceship sorts the middle step. ============================================================================ PART 9: GOTCHAS --------------- Undefined values: my @nums = (5, undef, 3, undef, 1); my @sorted = sort { $a <=> $b } @nums; # Warnings! undef used in numeric comparison Fix with defined checks: my @sorted = sort { ($a // 0) <=> ($b // 0) } @nums; Non-numeric strings: my @mixed = ('5', 'ten', '3'); my @sorted = sort { $a <=> $b } @mixed; # 'ten' becomes 0 numerically, with warnings Spaceship is for numbers. Know your data. ============================================================================ PART 10: VS OTHER LANGUAGES --------------------------- Many languages stole Perl's spaceship: Ruby: 5 <=> 10 PHP: 5 <=> 10 (as of PHP 7) Groovy: 5 <=> 10 Python uses cmp() functions differently. Perl had it first. Version 5, 1994. ============================================================================ PART 11: THE NAME ----------------- Look at it: <=> It's a spaceship. Viewed from above. The < and > are wings, the = is the body. <=> / \ < = > \ / Okay, maybe you need to use your imagination. But it stuck, and now every language that copied it uses the same name. ============================================================================ <=> / \ < = > \ / / \ / \ Three-way comparison since 1994 ============================================================================ japh.codes