http://www.perlmonks.org?node_id=40643

So I'm in my webtech class and rather than listening to the lecture on XML I thought I'd write a Perl script to poll CNN and find out who was ahead and by how much.

Sample output would be "Bush beating Gore by 1750 votes" or vice versa. I supposed there should be a "Bush and Gore tied" message just in case.

Your challenge write the code to do output those types of messages with the fewest characters in Perl

Additional points given for logging it to a permanent data source and e-mailing all the people in a comma-delimited list when the results shift. The list should be accessed from a file.

I'm feeling generous so the person who completes the most of these features in the fewest characters of Perl wins a free PM t-shirt courtesty of me. Deadline is 7:00pm November 7, EST. So you've got about two hours and forty minutes.

LWP and MAIL modules can be used or you can use lynx or connect directly to sendmail. The message text should just be the output of the program

Update: I mean November 8th... or maybe I'm just evil and really don't want to give anyone a free t-shirt

Update: today is not Thursday so the votes prolly won't be tallied until end of business hours on Thursday so the deadline will now be Nov. 9th at 2:00pm EST

Update: I should have decided on the winner by sometime on Monday

Update: I was torn between giving the prize to amelinda and chromatic so I think I'll send each of you one



vroom | Tim Vroom | vroom@cs.hope.edu

Replies are listed 'Best First'.
RE: Post Election Day Perl
by chromatic (Archbishop) on Nov 09, 2000 at 03:25 UTC
    This could be made shorter by removing whitespace, but as the interpreter doesn't care about it, I don't think it should count.

    Also, I focused only on the Florida count, as that's what I thought Fearless Perl Commando meant. (Yes, that's his title at EDC.)

    #!/usr/bin/perl -w use LWP::Simple; my ($c, %d, @o) = get("http://www.cnn.com/ELECTION/2000/results/FL/ind +ex.html"); for ('Bush', 'Gore') { if ($c =~ m!$_</a>.+?dPercent">([0-9,]+)!s) { ($d{$_} = $1) =~ tr/,//d; } } @o = sort { $d{$a} cmp $d{$b} } keys %d; my $d = "$o[1] leads $o[0] by @{[($d{$o[1]} - $d{$o[0]})]} votes!"; $d = "It's a tie!" if $d{$o[1]} == $d{$o[0]}; die unless open(I, "vote.log"); @o = <I>; $_ = $o[-1]; exit if (/$d$/); select O if (open(O, ">>vote.log")); print (localtime() . "\n$d\n"); exit if !(open(A, "addresses")); my @a = <A>; die "No sendmail!" unless open(S, "|/usr/lib/sendmail -t"); print S <<E; Subject: Vote results as of @{[ ''. localtime() ]} Bcc: @{[@a]} $d E

    Update: Hmm, what's the proper balance between error checking, graceful degradation when CNN changes the page format, and Doing the Right Thing with as few errors as possible? This would make a good Discussion.

    /msg me your thoughts.

      How gauche, to respond to my own post. amelinda had the right idea with using lynx instead of LWP::Simple. Here's one that has whitespace and still weighs in at 557 bytes, performs the logging and mailing (but only if something has changed):
      #!/usr/bin/perl $_ = `lynx -dump http://www.cnn.com/ELECTION/2000/results/FL/index.htm +l`; push @c, (/(Bush).{4}([0-9,]{9})/s), (/(Gore).{4}([0-9,]{9})/s); tr/,//d for @c[1,3]; $d = "$c[0] leads $c[2] by @{[$c[1] - $c[3]]} votes!"; $d = "It's a tie!" if $c[1] == $c[3]; die if !open(I, "vote.log"); chomp($_ = $i) while ($i = <I>); exit if (/$d$/); die if !(open(O, ">>vote.log")); $i = localtime(); print O ("$i\n$d\n"); die if !open(A, "addresses"); die if !open(S, "|/usr/lib/sendmail -t"); print S <<E; Subject: Vote results as of $i Bcc: @{[<A>]} $d E
      Removing whitespace would take me down at least to 531. Of that, 29 characters are in the messages (someone leads, someone tied), 26 characters are used in filenames (I could get that down to 3), and the URL is 54 characters. Using +shift or @ARGV and the other optimizations could take me close to 400 bytes.

      ph3@r!

      Update:

      Here's a version with some optimizations that occurred to me in the shower. (See, 'Where Do You Get Your Best Ideas?') It's a slim 516 bytes, without removing whitespace (30 characters or more), trimming filenames, removing error checking (27 characters), but it corrects a subtle flaw in the original.

      #!/usr/bin/perl $_ = `lynx -dump http://www.cnn.com/ELECTION/2000/results/FL/index.htm +l`; push @c, (/(Bush|Gore).{4}([0-9,]{9})/sg); y/,//d for @c[1,3]; $d = "$c[0] leads $c[2] by @{[$c[1] - $c[3]]} votes!"; $d = "It's a tie!" if $c[1] == $c[3]; die if !open(I, "vote.log"); ($_ = $i) while ($i = <I>); exit if /$d/; die if !(open(O, ">>vote.log")); $i = localtime(); print O ("$i\n$d\n"); die if !open(A, "addresses") || !open(S, "|/usr/lib/sendmail -t"); print S "Subject: Vote results as of $i Bcc: @{[<A>]} $d";
      Yeah the florida vote is what I'm interested in. I was grabbing it from the front page but this works too.

      vroom | Tim Vroom | vroom@cs.hope.edu
RE: Post Election Day Perl
by Fastolfe (Vicar) on Nov 09, 2000 at 02:55 UTC
    Without using any advanced obfuscation techniques that might make this smaller...
    use LWP::Simple;$_=get('http://www.cnn.com');($P,$u,$l)=m~(Bush|Gore)< +/\S+\s*<td\s*\S+ \S+edhead">([\d,]{9}).*?([\d,]{9})~s;$u=~s/,//g;$l=~ +s/,//g;$d=$u-$l;print$d?"$P leads by $d votes!\n":"Tie!\n";
    Update: This does everything requested, and can be made smaller by taking out white space (~25 bytes) (I followed Chromatic's example by keeping it in there) and/or removing the dependency on 'use strict' (~25 more bytes). :) 571 bytes.
    #!/usr/bin/perl -w use strict; use LWP::Simple; use Mail::Mailer; my($P,$u,$l)= get('http://www.cnn.com')=~ m~(Bush|Gore)</\S+\s*<td\s*\S+ \S+edhead">([\d,]{9}).*?([\d,]{ +9})~s; $u=~s/,//g; $l=~s/,//g; my $d=$u-$l; print $d?"$P is leading by $d votes\n":"It's a tie!\n"; open(F,(-f'votes'?"+<":">")."votes"); my @v=<F>; chomp(@v); if ((split(/\t/,$v[-1]))[2]!=$d) { print F localtime()."\t$P\t$d\n"; open(F,"<emails"); chomp(@v=<F>); $l = new Mail::Mailer; $l->open({To=>join(",",@v)}); print $l $d?"$P is leading by $d votes\n":"It's a tie!\n"; $l->close; }
    Put a list of users/email addresses (comma-separated or line-separated) in 'emails' and run. It will report the current winner and the margin of votes and if this is different from last time, it will send an e-mail to everyone with the new information. Keeps a log of each change in 'votes'.
RE: Post Election Day Perl
by amelinda (Friar) on Nov 09, 2000 at 05:35 UTC
    A couple minor assumptions: lynx is in your path, sendmail is in your path, you only need to know who's on top and by how much. Why ass-u-me? lynx's dump is much easier to parse than the source of the page.

    This version without use strict; or any errorchecking weighs in at 469 in the long form, and 429 in the most condensed form:

    #!/usr/bin/perl -w open I,"lynx -dump http://www.cnn.com/ELECTION/2000/results/FL/index.h +tml|"; while(<I>) { @j = split; if($j[1]) {$j[1] =~ s/,//g;} if (/Bush/) { $b = $j[1];} elsif (/Gore/) { $g = $j[1];} } close I; open O, ">out"; $d = abs($b-$g); if ($b == $g) {$r="tied!\n";} $b < $g ? $r="Gore: $d\n" : $r="Bush: $d\n"; print O "$r"; close O; open F,"mfile"; $m = <F>; close F; open(M,"|sendmail -t"); print M <<E; Subject: results Cc: $m $r E close M; exit;
    This one has both use strict; and error checking (though it does still make the above assumptions). It comes in at 612 bytes (according to wc). When I gutted the whitespace, it crawled in at 561 bytes.
    #!/usr/bin/perl -w use strict; my $b; my $g; my $r; die "lynx failed\n" unless open I,"lynx -dump http://www.cnn.com/ELECT +ION/2000/r esults/FL/index.html|"; while(<I>) { my @j = split; if($j[1]) {$j[1] =~ s/,//g;} if (/Bush/) { $b = $j[1];} elsif (/Gore/) { $g = $j[1];} } close I; open O, ">log" or die "log failed\n"; my $d = abs($b-$g); if ($b == $g) {$r="tied!\n";} $b < $g ? $r="Gore: $d\n" : $r="Bush: $d\n"; print O "$r"; close O; open F,"mfile" or die "no mailfile\n"; my $m = <F>; close F; open(M,"|sendmail -t") or die "couldn't sendmail\n"; print M <<E; Subject: results Cc: $m $r E close M; exit;
    And for sheer showing-off-ness, this is that shortest chunk:
    #!/usr/bin/perl -w open I,"lynx -dump http://www.cnn.com/ELECTION/2000/results/FL/index.h +tml|";while(<I>){@j=split;if($j[1]){$j[1]=~s/,//g;} if(/Bush/){$b=$j[ +1];} elsif(/Gore/){$g=$j[1];}} close I;open O,">log";$d=abs($b-$g);if +($b==$g){$r="tied!\n";} $b<$g ? $r="Gore: $d\n" : $r="Bush: $d\n";pri +nt O "$r";close O;open F,"mfile";$m=<F>;close F;open M,"|sendmail -t" +;print M <<E; Subject: results Cc: $m $r E close M; exit
    Heck, even if I don't win a tshirt, I'm pretty damn happy with getting this to work at ALL!
      Ahh, but do these solutions e-mail only when there's a change, or every time they poll for numbers?
        No, but this one does. Based on the "ass-u-me everything" one above, it also includes a bugfix so that it shouldn't break if there is more than one instance of Bush|Gore and appends to the logfile instead of overwriting. 485 bytes.
        #!/usr/bin/perl -w open I,"lynx -dump http://www.cnn.com/ELECTION/2000/results/FL/index.h +tml|";while(<I>){@j=split;if($j[1]){$j[1]=~s/,//g;}if(/\]Bush/){$b=$j +[1];}elsif(/\]Gore/){$g=$j[1];}}close I;$l=`tail -n1 log`;@l=split / +/,$l;$d=abs($b-$g);if($l[1]==$d){die"same\n";}if($b==$g){$r="tied!\n" +;}$b<$g ? $r="Gore: $d\n" : $r="Bush: $d\n";open O,">>out";print O "$ +r";close O;open F,"mfile";$m=<F>;close F;open(M,"|sendmail -t");print + M <<E; Subject: results Cc: $m $r E close M;exit;
RE: Post Election Day Perl
by Adam (Vicar) on Nov 09, 2000 at 03:48 UTC
    Only the electoral votes matter. Here is my offering:
    perl -Mstrict -MLWP::Simple -we "$_=get('http://www.cnn.com');($a)=/Go +re\D*(2\d\d)<\/span>/m;($b)=/Bush\D*(2\d\d)<\/span/m;print+($a>270?'G +ore Wins':$b>270?'Bush Wins':'Undecided'),$/;"
    That's all one line. If your browser added a plus sign, remove it. The only one in my code is print+($a
    Oh, and I already have a T-Shirt, but I really want a Mug!

    Oh, to have this send you the result as an e-mail just append this to the end (on Linux, or wherever 'mail' is available)

    | mail -s 'Election Results' adam@perlmonks.org

    Update: After some thought I realized that you would only want to be e-mailed when the results were in. So how about:

    perl -Mstrict -MLWP::Simple -we "{$_=get('http://www.cnn.com');($a)=/G +ore\D*(2\d\d)<\/spa/m;($b)=/Bush\D*(2\d\d)<\/spa/m;$@=($a>270?'Gore W +ins':$b>270?'Bush Wins':0);die `echo $@ | mail -s Results adam@perlmo +nks.org`,$@,$/ if $@; sleep 600; redo}"
RE: Post Election Day Perl
by Fastolfe (Vicar) on Nov 09, 2000 at 19:21 UTC
    Since you seemed to imply modules were OK, here's my 2nd attempt:
    #!/usr/bin/perl -w use strict; use CNN::GetFloridaVotesAndEmailChanges qw{doit}; doit;
    Weighs in at about 87 bytes. Less if you reduce the length of the module's name and the function names, and if you drop the dependency on 'use strict'. Heck, if I do that:
    perl -MCNN -e go
    16 bytes!

    Update: Since amelinda gets to use external programs in her version, I do too:

    perl -e '`./a.out`'
    Unfortunately, we're a few bytes longer than the previous example, but if you renamed our external program a.out to, say, a, we come in at 15 bytes (13 if you assume our external program is in your PATH, which means we can drop the ./).
      Of course, if you re-read vroom's specs, he doesn't say that you can use any old modules... he does sort of limit it to LWP and MAIL. Pity, these are quite cute.