problems.pl 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 23 March 2022
  4. # https://github.com/trizen
  5. # Get the list of unsolved ProjectEuler problems and order them by difficulty.
  6. use 5.014;
  7. use strict;
  8. use warnings;
  9. use LWP::UserAgent::Cached;
  10. use HTML::Entities qw(decode_entities encode_entities);
  11. require LWP::UserAgent;
  12. require HTTP::Message;
  13. binmode(STDOUT, ':utf8');
  14. my $problems_count = 792;
  15. use constant {
  16. GET_PROBLEMS_COUNT => 1, # true to retrieve the current number of problems
  17. USE_TOR_PROXY => 0, # true to use the Tor proxy (127.0.0.1:9050)
  18. UNCACHE_RECENT => 1, # remove recent problems from cache
  19. };
  20. my $cache_dir = '.cache';
  21. if (not -d $cache_dir) {
  22. mkdir($cache_dir);
  23. }
  24. my $lwp = LWP::UserAgent::Cached->new(
  25. timeout => 60,
  26. show_progress => 1,
  27. agent => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0',
  28. cache_dir => $cache_dir,
  29. ssl_opts => {verify_hostname => 1, SSL_version => 'TLSv1_2'},
  30. nocache_if => sub {
  31. my ($response) = @_;
  32. my $code = $response->code;
  33. return 1 if ($code >= 500); # do not cache any bad response
  34. return 1 if ($code == 401); # don't cache an unauthorized response
  35. return 1 if ($response->request->method ne 'GET'); # cache only GET requests
  36. return;
  37. },
  38. );
  39. my $lwp_uc = LWP::UserAgent->new(
  40. timeout => 60,
  41. show_progress => 1,
  42. agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
  43. ssl_opts => {verify_hostname => 1, SSL_version => 'TLSv1_2'},
  44. );
  45. {
  46. state $accepted_encodings = HTTP::Message::decodable();
  47. $lwp->default_header('Accept-Encoding' => $accepted_encodings);
  48. $lwp_uc->default_header('Accept-Encoding' => $accepted_encodings);
  49. require LWP::ConnCache;
  50. my $cache = LWP::ConnCache->new;
  51. $cache->total_capacity(undef); # no limit
  52. $lwp->conn_cache($cache);
  53. $lwp_uc->conn_cache($cache);
  54. }
  55. if (USE_TOR_PROXY) {
  56. $lwp->proxy(['http', 'https'], "socks://127.0.0.1:9050");
  57. $lwp_uc->proxy(['http', 'https'], "socks://127.0.0.1:9050");
  58. }
  59. if (GET_PROBLEMS_COUNT) {
  60. my $content = $lwp_uc->get("https://projecteuler.net/recent")->decoded_content;
  61. if ($content =~ m{<td class="id_column">(\d+)</td>}) {
  62. $problems_count = $1 + 0;
  63. say ":: Found $problems_count problems.";
  64. }
  65. else {
  66. warn ":: Failed to extract the current number of problems.\n";
  67. }
  68. }
  69. my %solved_problems;
  70. my @languages = ('Sidef', 'Perl', 'Julia', 'PARI GP', 'Raku', 'Go');
  71. foreach my $lang (@languages) {
  72. foreach my $file (glob("$lang/*")) {
  73. if ($file =~ m{^\Q$lang\E/(\d+) }) {
  74. my $id = $1 + 0;
  75. $solved_problems{$id} = 1;
  76. }
  77. }
  78. }
  79. my $solved_count = keys %solved_problems;
  80. say sprintf(":: Count of solved problems: %s (%.2f%% of %s problems)", $solved_count, $solved_count / $problems_count * 100, $problems_count);
  81. my @problems;
  82. foreach my $id (1 .. $problems_count) {
  83. my $url = sprintf("https://projecteuler.net/problem=%s", $id);
  84. my $content = $lwp->get($url)->decoded_content;
  85. my $difficulty;
  86. if ($content =~ m{\bDifficulty rating: (\d+)%}) {
  87. $difficulty = $1;
  88. }
  89. elsif (UNCACHE_RECENT) {
  90. say ":: Uncaching recent problem: $id";
  91. $lwp->uncache;
  92. }
  93. else {
  94. say ":: Problem $id does not have a difficulty rating";
  95. }
  96. my $title;
  97. if ($content =~ m{<title>#\d+\s+(.*?) - Project Euler</title>}) {
  98. $title = $1;
  99. $title =~ s/\$(.*?)\$/$1/g;
  100. }
  101. else {
  102. warn "Could not extract title for problem: $url\n";
  103. }
  104. my $solve_count;
  105. if ($content =~ m{\bSolved by (\d+)}) {
  106. $solve_count = $1;
  107. }
  108. my $published;
  109. if ($content =~ m{\bPublished on (.*?);}) {
  110. $published = $1;
  111. }
  112. my %info = (
  113. id => $id,
  114. solve_count => $solve_count,
  115. published => $published,
  116. title => $title,
  117. difficulty => $difficulty,
  118. );
  119. push @problems, \%info;
  120. }
  121. my @unsolved_problems = grep { not $solved_problems{$_->{id}} } @problems;
  122. say sprintf(":: Found %s unsolved problems", scalar(@unsolved_problems));
  123. #<<<
  124. @problems = sort {
  125. (($a->{difficulty} // 'inf') <=> ($b->{difficulty} // 'inf'))
  126. || ($b->{solve_count} <=> $a->{solve_count})
  127. || ($a->{id} <=> $b->{id})
  128. } @problems;
  129. #>>>
  130. open my $solved_fh, '>:utf8', 'solved.html';
  131. open my $unsolved_fh, '>:utf8', 'unsolved.html';
  132. foreach my $pair (["Solved", $solved_fh], ["Unsolved", $unsolved_fh]) {
  133. my ($title, $fh) = @$pair;
  134. print $fh <<"EOF";
  135. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
  136. <html>
  137. <head>
  138. <style>
  139. tt { font-family: monospace; font-size: 100%; }
  140. p.editing { font-family: monospace; margin: 10px; text-indent: -10px; word-wrap:break-word;}
  141. p { word-wrap: break-word; }
  142. table, th, td {
  143. border: 5px solid black;
  144. border-collapse: collapse;
  145. }
  146. th, td {
  147. border-color: #96D4D4;
  148. }
  149. </style>
  150. <meta http-equiv="content-type" content="text/html; charset=utf-8">
  151. <title>$title Project-Euler problems</title>
  152. </head>
  153. <body bgcolor=#ffffff>
  154. <table>
  155. <tr><th><br><strong><span> ID </span></strong><br><br></th><th> Title </th><th> Solved by </th><th> Difficulty </th></tr>
  156. EOF
  157. }
  158. foreach my $problem (@problems) {
  159. my $row = sprintf(
  160. "<tr>%s</tr>",
  161. join(
  162. '',
  163. map { sprintf("<td>%s</td>", $_) } (
  164. $problem->{id},
  165. sprintf(
  166. q{<a href="https://projecteuler.net/problem=%s" title="%s" target="_blank" rel="noopener noreferrer">%s</a>},
  167. $problem->{id}, $problem->{published}, $problem->{title}
  168. ),
  169. $problem->{solve_count},
  170. (defined($problem->{difficulty}) ? sprintf("%s%%", $problem->{difficulty}) : '-'),
  171. )
  172. )
  173. );
  174. if ($solved_problems{$problem->{id}}) {
  175. say $solved_fh $row;
  176. }
  177. else {
  178. say $unsolved_fh $row;
  179. }
  180. }
  181. foreach my $fh ($solved_fh, $unsolved_fh) {
  182. say $fh "</table></body></html>";
  183. }