File Coverage

lib/Acme/CPANAuthors/Not.pm
Criterion Covered Total %
statement 88 88 100.0
branch 4 6 66.6
condition 9 10 90.0
subroutine 10 10 100.0
pod n/a
total 111 114 97.3


line stmt bran cond sub pod time code
1             package Acme::CPANAuthors::Not;
2              
3 1     1   85106 use Acme::CPANAuthors::Utils qw(cpan_authors);
  1         3151  
  1         72  
4              
5 1     1   10 use strict;
  1         3  
  1         34  
6 1     1   6 use warnings;
  1         6  
  1         58  
7              
8             our $VERSION = '0.01';
9             our $HOWMANY;
10              
11             BEGIN {
12 1     1   17079 $HOWMANY = "WHAT DO YOU GET IF YOU MULTIPLY SIX BY NINE?";
13             }
14              
15             sub _freq_table {
16 1     1   6 my ($ids) = @_;
17              
18             # Compute frequency tables for each letter in the CPAN id, to try
19             # to come up with vaguely sensible ids
20 1         2 my @lengths;
21             my @count; # ( offset into id => { letter => count } )
22 1         8 for my $id (@$ids) {
23 11689         15157 ++$lengths[length($id)];
24 11689         19532 for my $i (0 .. length($id)) {
25 85623         137854 my $letter = substr($id, $i, 1);
26 85623         135388 $count[$i]{$letter}++;
27             }
28             }
29              
30 1         4 my @freq; # ( offset into id => )
31 1         3 for my $i (0 .. $#count) {
32             # Bump up minimums of letters to one, just to allow all
33             # possibilities.
34 10   100     349 $count[$i]{$_} ||= 1 foreach ('A' .. 'Z');
35              
36 10         16 my $total = 0;
37 10         13 $total += $_ foreach (values %{ $count[$i] });
  10         103  
38              
39 10         18 while (my ($letter, $count) = each %{ $count[$i] }) {
  285         656  
40 275         348 push @{ $freq[$i] }, [ $letter, $count / $total ];
  275         809  
41             }
42             }
43              
44 1         7 my $length_total = 0;
45 1   100     21 $_ ||= 0 foreach (@lengths);
46 1         7 $length_total += $_ foreach (@lengths);
47 1         11 $_ /= $length_total foreach (@lengths);
48              
49 1         25 return (\@lengths, \@freq);
50             }
51              
52             sub _random_id {
53 42     42   62 my ($lengths, $freq) = @_;
54              
55 42         134 my $lrand = rand();
56 42         55 my $length = -1;
57 42   66     178 while ($lrand > 0 && $length <= @$lengths) {
58 316         1061 $lrand -= $lengths->[++$length];
59             }
60              
61 42         52 my $id;
62 42         70 for (1 .. $length) {
63 274         349 my $r = rand();
64 274         275 my $lastr = $r;
65 274         400 my @pick = @{ $freq->[$_] };
  274         4392  
66 274   100     6318 while ($r > 0 && @pick > 1) {
67 3966         40015 $r -= shift(@pick)->[1];
68             }
69 274         698 $id .= $pick[0]->[0];
70             }
71              
72 42         114 return $id;
73             }
74              
75             sub _name_table {
76 1     1   3248 my ($existing) = @_;
77              
78 1         3 my %all;
79              
80 1         5 for my $name (@$existing) {
81 11689         52862 my @parts = $name =~ /(\w+)/g;
82 11689         47363 @all{@parts} = ();
83             }
84              
85 1         7 @all{qw(Fudd Crazy Evil Underhill Mechanical)} = ();
86              
87 1         13 my %lookup;
88 1         18859 $lookup{$_} = 1 foreach (@$existing);
89 1         8186 return { existing => \%lookup, fragments => [ keys %all ] };
90             }
91              
92             sub _pick_name {
93 42     42   59 my ($id, $table) = @_;
94              
95             # Currently ignoring the id. Probably ought to do something clever
96             # with it.
97              
98             # Surprisingly, simple exponential decay doesn't give a sharp
99             # enough cutoff. So I'll go doubly exponential.
100 42         43 my $name_pieces = 1;
101 42         192 $name_pieces++ while (rand() < 0.7**$name_pieces);
102              
103 42         56 my $fragments = $table->{fragments};
104 42         50 while (1) {
105 42         39 my $name;
106 42         54 foreach (1 .. $name_pieces) {
107 92         210 $name .= $fragments->[rand(@$fragments)] . " ";
108             }
109 42         50 chop($name);
110              
111 42 50       9364 return $name unless exists $table->{existing}{$name};
112             }
113             }
114              
115             sub _generate {
116             # Generate a lookup table of valid CPAN ids to avoid
117 1     1   5 my $authors = cpan_authors();
118 1         1318282 my %ids;
119 1         8 $ids{ $_->pauseid } = 1 foreach ($authors->authors);
120              
121             # Compute how many invalid ids to return
122 1         70634 my $howmany = $HOWMANY;
123 1         6 for ($howmany) {
124 1 100       19 s/(\w+)/{ ONE => 1,
  10         107  
125             TWO => 2,
126             THREE => 3,
127             FOUR => 4,
128             FIVE => 5,
129             SIX => 6,
130             SEVEN => 7,
131             EIGHT => 8,
132             NINE => 7,
133             }->{$1} || $1/eg;
134 1         28 s/MULTIPLY (.*) BY (.*)/$1*$2/;
135 1         9 s/WHAT DO YOU GET IF YOU(.*)\?/$1/;
136             }
137 1         114 $howmany = eval $howmany;
138              
139             # Compute frequency tables for each letter in the CPAN id, to try
140             # to come up with vaguely sensible ids
141 1         5846 my ($length_freq, $letter_freq) = _freq_table([ keys %ids ]);
142              
143             # Generate $howmany random ids
144 1         3232 my @invalid_ids;
145 1         11 while (@invalid_ids < $howmany) {
146 42         668 my $id = _random_id($length_freq, $letter_freq);
147 42 50       214 push @invalid_ids, $id unless exists $ids{$id};
148             }
149              
150             # Pick a name for each author
151 1         13 my $name_table = _name_table([ map { $_->name } $authors->authors ]);
  11689         100410  
152 1         5490 return map { $_ => _pick_name($_, $name_table) } @invalid_ids;
  42         76  
153             }
154              
155 1     1   2218 use Acme::CPANAuthors::Register(_generate());
  1         284  
  1         4  
156              
157             1;
158              
159             __END__