Upgrade to Pro — share decks privately, control downloads, hide ads and more …

grouping_unclassified_-_tprc_2026.pdf

Avatar for Bruce Gray Bruce Gray
June 26, 2026
7

 grouping_unclassified_-_tprc_2026.pdf

Classification is a powerful tool for data munging. Easy to do with raw Perl code, and even easier with a Perl module (List::Categorize) or Raku methods (.classify, .categorize) . Sail past SQL’s GROUP BY with Hashes of Arrays (HoA), HoHoA, HoHoHoA … ∞ !

Avatar for Bruce Gray

Bruce Gray

June 26, 2026

Transcript

  1. Grouping: UNCLASSIFIED ( with a waypoint on Counting )
 


    { in 2 + 2 languages } [ in less than 50 minutes ] The Perl and Raku Conference
 
 2026-06-26
  2. Grand Design • Manual code, long-form and minimal • Drop-in

    builder routines • Modules / Built-in • Example
  3. Pets Perl my @pets = qw<dog cat dog cat dog

    dog>; Raku my @pets = <dog cat dog cat dog dog>; Python pets = 'dog cat dog cat dog dog'.split() Javascript const qw = (s) => s[0].trim().split(/\s+/); const pets = qw`dog cat dog cat dog dog`; 14 …
  4. Hardcoded : Pseudocode dogs <- 0 cats <- 0 for

    pet in pets { if pet == 'dog' { dogs += 1 } elsif pet == 'cat' { cats += 1 } else { complain } } 15
  5. Hardcoded : Perl use v5.36; use Data::Dumper; $Data::Dumper::Sortkeys = 1;

    $Data::Dumper::Useqq = 1; use utf8; use open qw<:std :utf8>; my @pets = qw<dog cat dog cat dog dog>; my ( $dogs, $cats ) = (0, 0); for my $pet (@pets) { if ($pet eq 'dog') { $dogs++ } elsif ($pet eq 'cat') { $cats++ } else { warn } } print Data::Dumper->Dump([$dogs, $cats], [qw<dogs cats>]); 16
  6. Hardcoded : Raku my @pets = <dog cat dog cat

    dog dog>; my ( $dogs, $cats ) = 0, 0; for @pets { when 'dog' { $dogs++ } when 'cat' { $cats++ } default { warn } } say ( :$dogs, :$cats ); 17
  7. Hardcoded : Perl use v5.36; use Data::Dumper; $Data::Dumper::Sortkeys = 1;

    $Data::Dumper::Useqq = 1; use utf8; use open qw<:std :utf8>; my @pets = qw<dog cat dog cat dog dog>; my $dogs = 0+grep { $_ eq 'dog' } @pets; my $cats = 0+grep { $_ eq 'cat' } @pets; for (@pets) { $_ eq 'dog' or $_ eq 'cat' or warn; } print Data::Dumper->Dump([$dogs, $cats], [qw<dogs cats>]); 18
  8. Hardcoded : Raku my @pets = <dog cat dog cat

    dog dog>; my $dogs = +grep 'dog', @pets; my $cats = +grep 'cat', @pets; $_ eq 'dog'|'cat' or warn for @pets; say ( :$dogs, :$cats ); 19
  9. Bag

  10. "Positive" does not include zero { "foo": "SomeString", "bar": -3,

    "baz": 1.5, "quux": 0, "quaz": 42, "quin": 1 } 21
  11. Softcoded : Pseudocode grouped <- new Hash for pet in

    pets if pet is not already in grouped grouped{pet} = 0 grouped{pet} = grouped{pet} + 1 print grouped{'dogs'} print grouped{'cats'} 23
  12. Softcoded : Pseudocode grouped <- new Hash for pet in

    pets if pet is not already in grouped grouped{pet} = 0 grouped{pet} = grouped{pet} + 1 print grouped{'dogs'} print grouped{'cats'} 24
  13. Bag: Javascript const qw = (s) => s[0].trim().split(/\s+/); const pets

    = qw`dog cat dog cat dog dog`; const h = {}; for ( const pet of pets ) { if ( !(pet in h) ) h[pet] = []; h[pet]++; } console.log(h); 25
  14. Bag: Javascript const qw = (s) => s[0].trim().split(/\s+/); const pets

    = qw`dog cat dog cat dog dog`; const h = {}; for ( const pet of pets ) { h[pet] ??= 0; h[pet]++; } console.log(h); 26
  15. Bag: Javascript const qw = (s) => s[0].trim().split(/\s+/); const pets

    = qw`dog cat dog cat dog dog`; function bag ( arr ) { const counts = {}; for ( const x of arr ) { counts[x] ??= 0; counts[x]++; } return counts; } console.log(h); 27
  16. Bag: Javascript const qw = (s) => s[0].trim().split(/\s+/); const pets

    = qw`dog cat dog cat dog dog`; function bag ( arr ) { const counts = {}; for ( const x of arr ) { counts[x] ??= 0; counts[x]++; } return counts; } const h = bag(pets); console.log(h); 28
  17. Bag: Python pets = 'dog cat dog cat dog dog'.split()

    h = {} for pet in pets: if pet not in h: h[pet] = 0 h[pet] += 1 print(h) 29
  18. Bag: Python pets = 'dog cat dog cat dog dog'.split()

    h = {} for pet in pets: h.setdefault(pet, 0) h[pet] += 1 print(h) 30
  19. Bag: Python pets = 'dog cat dog cat dog dog'.split()

    def bag (arr): counts = {} for x in arr: counts[x] = counts.setdefault(x, 0) + 1 return counts h = bag(pets) print(h) 31
  20. Bag: Python pets = 'dog cat dog cat dog dog'.split()

    from collections import defaultdict def bag (arr): counts = defaultdict(int) for x in arr: counts[x] += 1 return dict(counts) h = bag(pets) print(h) 32
  21. Bag: Python pets = 'dog cat dog cat dog dog'.split()

    from collections import Counter h = Counter(pets) print(h) 33
  22. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for my $pet (@pets) { if ( ! exists $h{$pet} ) { $h{$pet} = 0; } $h{$pet}++; } print Dumper \%h; 34
  23. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for my $pet (@pets) { $h{$pet} = 0 if !exists $h{$pet}; $h{$pet}++; } print Dumper \%h; 35
  24. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for my $pet (@pets) { $h{$pet} = 0 if !defined $h{$pet}; $h{$pet}++; } print Dumper \%h; 36
  25. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for my $pet (@pets) { $h{$pet} //= 0; $h{$pet}++; } print Dumper \%h; 37
  26. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for my $pet (@pets) { $h{$pet}++; } print Dumper \%h; 38
  27. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; for (@pets) { $h{$_}++; } print Dumper \%h; 39
  28. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; $h{$_}++ for (@pets); print Dumper \%h; 40
  29. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; $h{$_}++ for @pets; print Dumper \%h; 41
  30. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; my %h; $h{$_}++ for @pets; print Dumper \%h; 42
  31. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; sub bag (@arr) { my %counts; $counts{$_}++ for @arr; return %counts; } my %h = bag(@pets); print Dumper \%h; 43
  32. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; use List::MoreUtils qw<frequency>; my %h = frequency @pets; print Dumper \%h; 44
  33. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; use List::MoreUtils qw<frequency>; my %h = frequency @pets; use Set::Bag; print Dumper \%h; say $bag; 45
  34. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; use List::MoreUtils qw<frequency>; my %h = frequency @pets; use Set::Bag; my $bag = Set::Bag->new(@pets); print Dumper \%h; say $bag; 46
  35. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; use List::MoreUtils qw<frequency>; my %h = frequency @pets; use Set::Bag; my $bag = Set::Bag->new(@pets); print Dumper \%h; say $bag; 47
  36. Bag: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    use Data::Dumper; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Useqq = 1; my @pets = qw<dog cat dog cat dog dog>; use List::MoreUtils qw<frequency>; my %h = frequency @pets; use Set::Bag; my $bag = Set::Bag->new( frequency @pets ); print Dumper \%h; say $bag; 48
  37. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h; for @pets -> $pet { if %h{$pet}:!exists { %h{$pet} = 0; } %h{$pet}++; } say %h; 49
  38. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h; %h{$_}++ for @pets; say %h; 50
  39. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; sub bag_it (@arr) { my %counts; %counts{$_}++ for @arr; return %counts; } my %h = bag_it(@pets); say %h; 51
  40. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; sub bag_it (@arr) { my %counts; %counts{$_}++ for @arr; return %counts; } my %h = bag_it(@pets); say %h; 52
  41. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h = bag @pets; say %h; 53
  42. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h1 = bag @pets; my %h2 = @pets.Bag; say (:%h1, :%h2); 54
  43. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h1 = bag @pets; my %h2 = @pets.Bag; %h2{'dog'} += 1/2; say (:%h1, :%h2); 55
  44. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h1 = bag @pets; my %h2 = @pets.Bag; my $bag_o_pets = @pets.Bag; say (:%h1, :%h2, :$bag_o_pets); 56
  45. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h1 = bag @pets; my %h2 = @pets.Bag; my $bag_o_pets = @pets.Bag; #Immutable say (:%h1, :%h2, :$bag_o_pets); 57
  46. Bag: Raku my @pets = <dog cat dog cat dog

    dog>; my %h1 = bag @pets; my %h2 = @pets.Bag; my $bag_o_pets = @pets.BagHash; say (:%h1, :%h2, :$bag_o_pets); 58
  47. Be clear on how you use each Bag. A. Build

    once, use once or more.
 Or, B. Initialize, update gradually, use the totals after all updates.
 Or, C. Initialize, update gradually while using the intermediate values (and maybe the totals after fi nal update too).
  48. Bag : Real World : Perl my $age_error; for my

    $person (@people) { $age_error++ if ...; ... } say "Age error:" $age_error if $age_error; 61
  49. Bag : Real World : Perl my $smo_error; my $age_error;

    for my $person (@people) { $age_error++ if ...; ... } say "Age error:" $age_error if $age_error; 62
  50. Bag : Real World : Perl my $smo_error; my $age_error;

    for my $person (@people) { $age_error++ if ...; $smo_error++ if ...; ... } say "Age error:" $age_error if $age_error; 63
  51. Bag : Real World : Perl my $smo_error; my $age_error;

    for my $person (@people) { $age_error++ if ...; $smo_error++ if ...; ... } say "Age error:" $age_error if $age_error; say "Smo error:" $smo_error if $age_error; 64
  52. Bag : Real World : Perl my $smo_error; my $age_error;

    for my $person (@people) { $age_error++ if ...; $smo_error++ if ...; ... } say "Age error:" $age_error if $age_error; say "Smo error:" $smo_error if $age_error; 65
  53. Bag : Real World : Perl my $smo_error; my $age_error;

    for my $person (@people) { $age_error++ if ...; $smo_error++ if ...; ... } say "Age error:" $age_error if $age_error; say "Smo error:" $smo_error if $smo_error; 66
  54. Bag : Real World : Perl my %err; # Bag

    of error counts for my $person (@people) { $err{age}++ if ...; $err{smo}++ if ...; ... } say "$_ error: $err{$_}" for sort keys %err; 67
  55. Counting : Covered • Manual code, long-form and minimal •

    Drop-in builder routines • Modules / Built-in • Example
  56. Unicode Rocks! 🂡 🂱 🃁 🃑 🂢 🂲 🃂 🃒

    🂣 🂳 🃃 🃓 🂤 🂴 🃄 🃔 🂥 🂵 🃅 🃕 🂦 🂶 🃆 🃖 🂧 🂷 🃇 🃗 🂨 🂸 🃈 🃘 🂩 🂹 🃉 🃙 🂪 🂺 🃊 🃚 🂫 🂻 🃋 🃛 🂭 🂽 🃍 🃝 🂮 🂾 🃎 🃞 71
  57. Not playing with a full deck 72 🂩 🃉 🃙

    🂹 🂪 🃊 🃚 🂺 🂭 🃍 🃝 🂽 🂮 🃎 🃞 🂾
  58. Cards: Graphic Output 73 🂩 🂪 🂭 🂮 🂹 🂺

    🂽 🂾 🃉 🃊 🃍 🃎 🃙 🃚 🃝 🃞 – : ‖ : ― : — :
  59. Cards: JSON Input Output # Input: Flat array of cards

    [ "9♠", "9♦", "9♣", "9♥", "10♠", "10♦", "10♣", "10♥", "Q♠", "Q♦", "Q♣", "Q♥", "K♠", "K♦", "K♣", "K♥" ] # Output: Hash of Arrays (HoA) { "♠": [ "9♠", "10♠", "Q♠", "K♠" ], "♦": [ "9♦", "10♦", "Q♦", "K♦" ], "♣": [ "9♣", "10♣", "Q♣", "K♣" ], "♥": [ "9♥", "10♥", "Q♥", "K♥" ] } 74
  60. Cards: Deck Suit Rank my @deck = <9 10 Q

    K> X~ <♠ ♦ ♣ ♥>; # Raku sub suit { $^card.substr( *-1) } sub rank { $^card.substr(0,*-1) } my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; # Perl sub suit ($card) { substr($card, -1) } sub rank ($card) { substr($card, 0,-1) } deck = [x+y for x in ('9 10 Q K').split() # Python for y in ('♠ ♦ ♣ ♥').split()] def suit (card): return card[-1] def rank (card): return card[:-1] const deck = ['9♠','9♦','9♣','9♥','10♠','10♦','10♣','10♥', # JS 'Q♠','Q♦','Q♣','Q♥', 'K♠', 'K♦', 'K♣', 'K♥']; function suit (card) { return card.substring( card.length - 1) } function rank (card) { return card.substring(0, card.length - 1) } 76
  61. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for my $card (@deck) { my $key = suit($card); push @{ $h{$key} }, $card; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 77
  62. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for my $card (@deck) { my $key = suit($card); push @{ $h{$key} }, $card; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 78
  63. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for (@deck) { my $key = suit($_); push @{ $h{$key} }, $_; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 79
  64. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for (@deck) { push @{ $h{ suit($_) } }, $_; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 80
  65. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; push @{$h{suit($_)}}, $_ for @deck; say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 81
  66. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for my $card (@deck) { my $key = suit($card); push @{ $h{$key} }, $card; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 82
  67. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } my %h; for my $card (@deck) { my $key = suit($card); push @{ $h{$key} }, $card; } say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 83
  68. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify (@items) { my %h; for my $card (@deck) { my $key = suit($card); push @{ $h{$key} }, $card; } } # Will be non-functional until we finish the refactor 84
  69. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify (@items) { my %ret; for my $card (@deck) { my $key = suit($card); push @{ $ret{$key} }, $card; } } # Will be non-functional until we finish the refactor 85
  70. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify (@items) { my %ret; for my $item (@items) { my $key = suit($item); push @{ $ret{$key} }, $item; } } # Will be non-functional until we finish the refactor 86
  71. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify (@items) { my %ret; for my $item (@items) { my $key = suit($item); push @{ $ret{$key} }, $item; } } # Will be non-functional until we finish the refactor 87
  72. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify ($key_maker, @items) { my %ret; for my $item (@items) { my $key = $key_maker->($item); push @{ $ret{$key} }, $item; } } # Will be non-functional until we finish the refactor; 88
  73. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify ($key_maker, @items) { my %ret; for my $item (@items) { my $key = $key_maker->($item); push @{ $ret{$key} }, $item; } return %ret; } # Will be non-functional until we finish the refactor 89
  74. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } sub classify ($key_maker, @items) { my %ret; for my $item (@items) { my $key = $key_maker->($item); push @{ $ret{$key} }, $item; } return %ret; } my %h = classify \&suit, @deck; say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 90
  75. Cards: Perl use v5.36; use utf8; use open qw<:std :utf8>;

    my @deck = map {("9$_","10$_","Q$_","K$_")} qw<♠ ♦ ♣ ♥>; sub suit ($card) { substr($card, -1) } sub val ($card) { substr($card, 0,-1) } use List::Categorize qw<categorize>; my %h = categorize { suit($_) } @deck; say "$_ : ", join(" ", @{ $h{$_} }) for sort keys %h; 91
  76. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; for @deck -> $card { my $key = suit($card); %h{$key}.push: $card; } .say for %h.sort; 93
  77. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; for @deck { my $key = suit($_); %h{$key}.push: $_; } .say for %h.sort; 94
  78. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; for @deck { my $key = .&suit; %h{$key}.push: $_; } .say for %h.sort; 95
  79. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; for @deck { %h{ .&suit }.push: $_; } .say for %h.sort; 96
  80. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; for @deck { %h{ .&suit }.push($_); } .say for %h.sort; 97
  81. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; %h{ .&suit }.push($_) for @deck; .say for %h.sort; 98
  82. Cards: Raku sub classify_it ( @items, &key_maker ) { my

    %ret; %ret{.&key_maker}.push: $_ for @items; return %ret; } my @deck = <9 10 Q K> X~ <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h; %h = classify_it(@deck, &suit); %h = @deck.&classify_it(&suit); .say for %h.sort; 99
  83. Cards: Raku my @deck = <9 10 Q K> X~

    <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h = @deck.classify(&suit); .say for %h.sort; 100
  84. Cards: Javascript const qw = (s) => s[0].trim().split(/\s+/); const deck

    = qw`9♠ 9♦ 9♣ 9♥ 10♠ 10♦ 10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥`; function suit (card) { return card.substring( card.length - 1) } function val (card) { return card.substring(0, card.length - 1) } const h = {} for ( const card of deck ) { key = suit(card) h[key] ??= [] h[key].push(card) } console.log(h) 101
  85. Cards: Javascript const qw = (s) => s[0].trim().split(/\s+/); const deck

    = qw`9♠ 9♦ 9♣ 9♥ 10♠ 10♦ 10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥`; function suit (card) { return card.substring( card.length - 1) } function val (card) { return card.substring(0, card.length - 1) } function classify ( array, code ) { const ret = {}; for ( const item of array ) { key = code(item) ret[key] ??= []; ret[key].push(item); } return ret; } console.log( classify(deck, suit) ); 102
  86. Cards: Javascript const qw = (s) => s[0].trim().split(/\s+/); const deck

    = qw`9♠ 9♦ 9♣ 9♥ 10♠ 10♦ 10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥`; function suit (card) { return card.substring( card.length - 1) } function val (card) { return card.substring(0, card.length - 1) } function classify ( array, code ) { const ret = {}; for ( const item of array ) { key = code(item) ret[key] ??= []; ret[key].push(item); } return ret; } console.log( classify(deck, suit) ); console.log( Object.groupBy(deck, suit) ); console.log( Map.groupBy(deck, suit) ); 103
  87. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] h = {} for card in deck: k = suit(card) h.setdefault(k, []).append(card) for k, v in h.items(): print(k, v) 104
  88. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] def classify ( code, array ): ret = {} for x in array: key = code(x) ret.setdefault(key, []).append(x) return ret h = classify(suit, deck) for k, v in h.items(): print(k, v) 105
  89. Cards: remember that Raku? my @deck = <9 10 Q

    K> X~ <♠ ♦ ♣ ♥>; sub suit ($card) { $card.substr( *-1) } sub val ($card) { $card.substr(0,*-1) } my %h = @deck.classify(&suit); .say for %h.sort; 106
  90. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(deck, key=suit) 107
  91. Cards: JSON Wanted Output # Output: Hash of Arrays (HoA)

    { "♠": [ "9♠", "10♠", "Q♠", "K♠" ], "♦": [ "9♦", "10♦", "Q♦", "K♦" ], "♣": [ "9♣", "10♣", "Q♣", "K♣" ], "♥": [ "9♥", "10♥", "Q♥", "K♥" ] } 108
  92. Cards: WTF? { "♠": [ "K♠" ], "♦": [ "K♦"

    ], "♣": [ "K♣" ], "♥": [ "K♥" ] } 109
  93. Cards: WTF? { "♠": [ "9♠" ], "♦": [ "9♦"

    ], "♣": [ "9♣" ], "♥": [ "9♥" ], "♠": [ "10♠" ], "♦": [ "10♦" ], "♣": [ "10♣" ], "♥": [ "10♥" ], "♠": [ "Q♠" ], "♦": [ "Q♦" ], "♣": [ "Q♣" ], "♥": [ "Q♥" ], "♠": [ "K♠" ], "♦": [ "K♦" ], "♣": [ "K♣" ], "♥": [ "K♥" ] } 110
  94. Cards: WTF? % echo "foo\nbar\nfoo" | uniq foo bar foo

    % echo "foo\nbar\nfoo" | sort | uniq bar foo 111
  95. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(deck, key=suit) # Wrong! 112
  96. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(sorted(deck, key=suit),key=suit) 113
  97. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(deck, key=suit) # Wrong! for k, v in h.items(): # Wrong! print(k, v) 114
  98. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(sorted(deck, key=suit),key=suit) for k, v in h.items(): # Wrong! print(k, v) 115
  99. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(sorted(deck, key=suit),key=suit) #for k, v in h.items(): # Wrong! # print(k, v) for [k, hashref] in h: print(k, list(hashref)) 116
  100. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(sorted(deck, key=suit),key=suit) # Change from iterators to simple dict of lists. h = { k: list(v) for k, v in h } for k, v in h.items(): # Works now print(k, v) 117
  101. Cards: Python deck = '9♠ 9♦ 9♣ 9♥ 10♠ 10♦

    10♣ 10♥ Q♠ Q♦ Q♣ Q♥ K♠ K♦ K♣ K♥'.split() def suit (card): return card[-1] def val (card): return card[:-1] from itertools import groupby h = groupby(deck, key=suit) # Workaround to allow a JSON dump of `h` to work, to understand `groupby` behavior when unsorted. list_of_lists = [ [k, list(v)] for k, v in h ] import json print(json.dumps(list_of_lists)) 118
  102. Extension sizes: Raku raku -e 'for dir().classify(*.extension).sort(-*.value>>.s.sum).head(5) -> (:key($k), :value(@f))

    {say @f>>.s.sum.fmt("%15d\t"), $k; say .s.fmt("\t%15d\t"), .path for @f.sort(-*.s).head(5) }' 123
  103. Extension sizes: Raku raku -e 'for dir().classify(*.extension).sort(-*.value>>.s.sum).head(5) -> (:key($k), :value(@f))

    {say @f>>.s.sum.fmt("%15d\t"), $k; say .s.fmt("\t%15d\t"), .path for @f.sort(-*.s).head(5) }' raku -e 'for dir().classify(*.extension).sort(-*.value>>.s.sum).head(5) -> (:key($k), :value(@f)) {say @f>>.s.sum.fmt("%15d\t"), $k; say .s.fmt("\t%15d\t"), .path for @f.sort(-*.s).head(5) }' 124
  104. Extension sizes: Raku raku -e 'for dir().classify(*.extension).sort(-*.value>>.s.sum).head(5) -> (:key($k), :value(@f))

    {say @f>>.s.sum.fmt("%15d\t"), $k; say .s.fmt("\t%15d\t"), .path for @f.sort(-*.s).head(5) }' raku -e 'for dir().classify(*.extension).sort(-*.value>>.s.sum).head(5) -> (:key($k), :value(@f)) {say @f>>.s.sum.fmt("%15d\t"), $k; say .s.fmt("\t%15d\t"), .path for @f.sort(-*.s).head(5) }' for dir().classify(*.extension).sort(-*.value».s.sum) { say (.value».s.sum, .key); say "\t", (.s, .path) for .value.sort(-*.s) } 125
  105. Argentine Tango ### 37881174233 .webm 1154235480 Tango Combination: Sa[Qb5vZ52_ToQ].webm 872053799

    4 Mistakes during Ochos[85CknRy7HSc].webm … ### 12349378480 .mkv 399568606 Advance Tango Figures. [FF_lrKBTW7I].mkv 351719890 Tango turn with lapiz [vyKgibz2wKM].mkv … 128
  106. Extension sizes: Perl use v5.36; use List::Util qw<sum>; use List::Categorize

    qw<categorize>; sub ext ($f) { $f =~ /\.(.+?)$/ ? $1 : '<none>' } my @paths = sort { $b->[0] <=> $a->[0] } map { -f $_ ? [-s _, ext($_), $_] : () } glob '.'; my %by_ext = categorize { $_->[1] } @paths; my %size = map { $_ => sum map { $_->[0] } @{$by_ext{$_}} } keys %by_ext; my @exts = sort { $size{$b} <=> $size{$a} } keys %size; for my $ext (@exts) { say "### $size{$ext} $ext"; say "\t$_->[0] $_->[2]" for @{$by_ext{$ext}}; } 130
  107. Extension sizes: Javascript const dir = '.'; const fs =

    require('fs'); const path = require('path'); const sum = (arr) => arr.reduce( ( (acc, x) => acc + x ), 0 ); function get_file_info (f) { try { const stats = fs.statSync(dir + '/' + f); if (stats.isFile()) { return [ stats.size, path.extname(f), f ]; } } catch (err) { console.error(err); } return null; } const file_info = fs.readdirSync(dir).map(get_file_info).filter((x) => x !== null); const h = Object.groupBy( file_info, ([size,ext,file]) => ext ); const size_e = {}; for (const [ext, fis] of Object.entries(h)) { size_e[ext] = sum(fis.map(([s,e,f]) => s)); } for ( const [ext, size] of Object.entries(size_e).toSorted((a,b) => b[1] - a[1]) ) { console.log(`${size}\t${ext}`); for ( const [s,e,f] of h[ext].toSorted((a, b) => b[0] - a[0]) ) { console.log(`\t${s}\t${f}`); } } 131
  108. Extension sizes: Python from os import listdir from os.path import

    isfile,splitext,getsize files_info = [ [getsize(f), splitext(f)[1], f] for f in listdir('.') if isfile(f) ] def iSizeN (info): return -info[0] def iExt (info): return info[1] from itertools import groupby by_ext_iter = groupby(sorted(files_info, key=iExt), key=iExt) by_ext = { k: list(v) for k, v in by_ext_iter } size = { e : sum(x[0] for x in arr) for e, arr in by_ext.items() } for ext in sorted(size.keys(), key=lambda x: -size[x]): print("### ", size[ext], ext) for t in sorted(by_ext[ext], key=iSizeN): print("\t", t[0], t[2]) 133
  109. Extension sizes: Python with comments from os import listdir from

    os.path import isfile,splitext,getsize # Get all files and their extensions and sizes. files_info = [ [getsize(f), splitext(f)[1], f] for f in listdir('.') if isfile(f) ] # Slots of (negative) size and extension, for sorting. def iSizeN (info): return -info[0] def iExt (info): return info[1] # Group by extension from itertools import groupby by_ext_iter = groupby(sorted(files_info, key=iExt), key=iExt) # Change from iterators to simple dict of lists. by_ext = { k: list(v) for k, v in by_ext_iter } # Make a dict of total sizes per extension. size = { e : sum(x[0] for x in arr) for e, arr in by_ext.items() } # Print heading and detail lines for each group. for ext in sorted(size.keys(), key=lambda x: -size[x]): print("### ", size[ext], ext) for t in sorted(by_ext[ext], key=iSizeN): print("\t", t[0], t[2]) 134
  110. Cards : Covered • Manual code, long-form and minimal •

    Drop-in builder routines • Modules / Built-in • Example
  111. MD5: Raku in 1 raku -e 'say "\n", .join("\n") for

    lines()».split(" ", 2).classify(*.[0], :as{.[1]}).values.grep(*.elems > 1).sort(-*.elems);' md5s 140
  112. Perl my %h; while (<>) { chomp; my ($md5, $path)

    = split ' ', $_, 2; push @{ $h{$md5} }, $path; } my @v = grep { @{$_} > 1 } values %h; @v = sort { $#$b <=> $#$a } @v; for my $aref (@v) { say ''; say for $aref->@*; } 141
  113. Perl my %h; while (<>) { chomp; my ($md5, $path)

    = split ' ', $_, 2; push @{ $h{$md5} }, $path; } 142
  114. Perl use List::Categorize qw<categorize>; my @md5_paths; while (<>) { chomp;

    push @md5_paths, [split ' ', $_, 2]; } my %h = categorize { my ($md5, $path) = @{$_}; $_ = $path; # Transform $md5; # First-level category } @md5_paths; 143
  115. MD5 : Raku w/o .classify my %h; for lines() {

    my ($md5, $path) = .split(" ", 2); %h{$md5}.push: $path; } my @AoAoPaths = %h.values .grep(*.elems > 1) .sort(-*.elems); for @AoAoPaths -> @paths { say ''; .say for @paths; } 144
  116. MD5 : Raku raku -e ' say "\n", .join("\n") for

    lines().map(*.split(" ", 2)) .classify(*.[0], :as{.[1]}) .values .grep(*.elems > 1) .sort(-*.elems); ' file_with_md5s.txt 145
  117. Python import fileinput md5_paths = [] for line in fileinput.input():

    md5_paths.append( line.rstrip().split(' ', 1) ) get_md5 = lambda x: x[0] from itertools import groupby iter1 = groupby(sorted(md5_paths, key=get_md5), key=get_md5) for [md5, iter2] in iter1: mps = list(iter2) if (len(mps) > 1): print('') for mp in mps: print(mp[1]) # Note the `[1]` 146
  118. Grouping++ : Covered • Manual code, long-form and minimal •

    Drop-in builder routines • Modules / Built-in • Example
  119. Built-in Language features and modules 151 Count Group Multi-category Multi-level

    :as :into Raku Bag, BagHash .classify .categorize .classify
 .categorize Y Y Perl Set::Bag, List::MoreUtils#frequency List::Categorize List::Categorize Y JS Object.groupBy() Map.groupBy() Python collections.Counter itertools.groupby
 defaultdict + append
 Pandas:df.groupby Ruby Enumerable#group_by Java groupingBy partitionBy
  120. Helpful links • https://en.wikipedia.org/wiki/Playing_cards_in_Unicode • Raku • https://docs.raku.org/language/setbagmix • https://docs.raku.org/type/Bag

    • Perl • https://metacpan.org/pod/List::MoreUtils#frequency-LIST • https://metacpan.org/pod/List::Categorize • https://metacpan.org/pod/Set::Bag • Python • https://docs.python.org/3/library/collections.html#collections.Counter • https://docs.python.org/3/library/itertools.html#itertools.groupby • Javascript • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy • Software used to generate the video clips: • Youtube Downloader • https://github.com/yt-dlp/yt-dlp • VLC player • https://www.videolan.org/vlc/features.html
  121. Copyright Information: Images • Camelia • © 2009 by Larry

    Wall
 http://github.com/perl6/mu/raw/master/misc/camelia.txt • "Catch your breath" clip
 Extracted from https://www.youtube.com/watch?v=Bqli_zdxri8 Fate/Stay Night UBW Abridged - Ep10: All Aias On You
 Downloaded with `yt-dlp`, then clipped with `vlc`:
 vlc -I dummy 'Fate⧸Stay Night UBW Abridged - Ep10: All Aias On You [Bqli_zdxri8].webm' -- start-time=473 --stop-time=482 --sout "#gather:std{access= fi le,dst=breath.webm}" vlc://quit • https://www.youtube.com/shorts/bFPNQLiwfDA
 50 - "OH GOD NO" (UBW:A Ep9) #fate #fatestaynight #anime • https://www.youtube.com/shorts/rzW1_HISa3c
 48 - "Words" (UBW:A Ep9) #fate #fatestaynight #anime • https://www.youtube.com/shorts/ALsGFB2zPKY
 45 - "Manic Pixie Dream Girl" (UBW:A Ep8) #fate #fatestaynight #anime
  122. LICENSE • © 2026 Bruce Gray <[email protected]>
 This work is

    openly licensed under CC BY 4.0.
 https://creativecommons.org/licenses/by/4.0/